001/*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
019 */
020package org.sonar.java.ast;
021
022import com.google.common.collect.Lists;
023import com.google.common.io.Closeables;
024import com.puppycrawl.tools.checkstyle.Checker;
025import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
026import com.puppycrawl.tools.checkstyle.PropertiesExpander;
027import com.puppycrawl.tools.checkstyle.api.Configuration;
028import org.apache.commons.io.FileUtils;
029import org.apache.commons.io.IOUtils;
030import org.apache.commons.io.filefilter.FileFilterUtils;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033import org.sonar.api.resources.InputFile;
034import org.sonar.api.resources.InputFileUtils;
035import org.sonar.java.ast.visitor.*;
036import org.sonar.java.squid.JavaSquidConfiguration;
037import org.sonar.squid.api.AnalysisException;
038import org.sonar.squid.api.CodeScanner;
039import org.sonar.squid.api.CodeVisitor;
040import org.sonar.squid.api.SourceCode;
041import org.xml.sax.InputSource;
042
043import java.io.ByteArrayInputStream;
044import java.io.File;
045import java.io.IOException;
046import java.io.InputStream;
047import java.nio.charset.Charset;
048import java.util.Arrays;
049import java.util.Collection;
050import java.util.List;
051import java.util.Stack;
052
053/**
054 * Squid uses Checkstyle to get an out-of-the-box java parser with AST generation and visitor pattern support.
055 */
056public class JavaAstScanner extends CodeScanner<JavaAstVisitor> {
057
058  private static final Logger LOG = LoggerFactory.getLogger(JavaAstScanner.class);
059  private JavaSquidConfiguration conf;
060  private SourceCode project;
061
062  public JavaAstScanner(JavaSquidConfiguration conf, SourceCode project) {
063    this.conf = conf;
064    this.project = project;
065  }
066
067  public JavaAstScanner scanDirectory(File javaSourceDirectory) {
068    List<InputFile> inputFiles = Lists.newArrayList();
069    Collection<File> files = FileUtils.listFiles(javaSourceDirectory, FileFilterUtils.fileFileFilter(), FileFilterUtils.directoryFileFilter());
070    for (File file : files) {
071      inputFiles.add(InputFileUtils.create(javaSourceDirectory, file));
072    }
073    return scanFiles(inputFiles);
074  }
075
076  public JavaAstScanner scanFile(InputFile javaFile) {
077    return scanFiles(Arrays.asList(javaFile));
078  }
079
080  public JavaAstScanner scanFiles(Collection<InputFile> inputFiles) {
081    if (LOG.isDebugEnabled()) {
082      LOG.debug("----- Java sources analyzed by Squid:");
083      for (InputFile inputFile : inputFiles) {
084        LOG.debug(inputFile.toString());
085      }
086      LOG.debug("-----");
087    }
088
089    Stack<SourceCode> resourcesStack = new Stack<SourceCode>();
090    resourcesStack.add(project);
091    for (JavaAstVisitor visitor : getVisitors()) {
092      visitor.setSourceCodeStack(resourcesStack);
093    }
094
095    CheckstyleSquidBridgeContext bridgeContext = new CheckstyleSquidBridgeContext()
096        .setASTVisitors(getVisitors())
097        .setSquidConfiguration(conf)
098        .setInputFiles(inputFiles);
099
100    CheckstyleSquidBridge.setContext(bridgeContext);
101    try {
102      launchCheckstyle(InputFileUtils.toFiles(inputFiles), conf.getCharset());
103    } finally {
104      // Garbage collector should be able to do his job, so we must clean context after execution
105      CheckstyleSquidBridge.setContext(null);
106    }
107    return this;
108  }
109
110  private void launchCheckstyle(Collection<File> files, Charset charset) {
111    Checker c = createChecker(charset);
112    ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
113    Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
114    try {
115      c.setClassloader(getClass().getClassLoader());
116      c.setModuleClassLoader(getClass().getClassLoader());
117      c.process(Lists.<File> newArrayList(files));
118      c.destroy();
119    } finally {
120      Thread.currentThread().setContextClassLoader(initialClassLoader);
121    }
122  }
123
124  private Checker createChecker(Charset charset) {
125    String checkstyleConfig = loadCheckstyleConfigToString()
126        .replace("${charset}", charset.toString());
127    try {
128      Configuration config = ConfigurationLoader.loadConfiguration(
129          new InputSource(new ByteArrayInputStream(checkstyleConfig.getBytes())),
130          new PropertiesExpander(System.getProperties()),
131          false);
132      Checker checker = new Checker();
133      final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
134      checker.setModuleClassLoader(moduleClassLoader);
135      checker.configure(config);
136      checker.addListener(new CheckstyleAuditListener());
137      return checker;
138    } catch (Exception e) { // NOSONAR We want to be sure to catch any unexpected exception
139      throw new AnalysisException("Unable to create Checkstyle Checker", e);
140    }
141  }
142
143  private static String loadCheckstyleConfigToString() {
144    InputStream is = null;
145    try {
146      is = JavaAstScanner.class.getClassLoader().getResourceAsStream("checkstyle-configuration.xml");
147      return IOUtils.toString(is);
148    } catch (IOException e) {
149      throw new AnalysisException("Unable to load Checkstyle configuration for Java Squid", e);
150    } finally {
151      Closeables.closeQuietly(is);
152    }
153  }
154
155  @Override
156  public Collection<Class<? extends JavaAstVisitor>> getVisitorClasses() {
157    List<Class<? extends JavaAstVisitor>> visitorClasses = Lists.newArrayList();
158    visitorClasses.add(PackageVisitor.class);
159    visitorClasses.add(FileVisitor.class);
160    visitorClasses.add(ClassVisitor.class);
161    visitorClasses.add(AnonymousInnerClassVisitor.class);
162    visitorClasses.add(MethodVisitor.class);
163    visitorClasses.add(EndAtLineVisitor.class);
164    visitorClasses.add(LinesVisitor.class);
165    visitorClasses.add(BlankLinesVisitor.class);
166    visitorClasses.add(CommentVisitor.class);
167    visitorClasses.add(PublicApiVisitor.class);
168    visitorClasses.add(BranchVisitor.class);
169    visitorClasses.add(StatementVisitor.class);
170    if (conf.isAnalysePropertyAccessors()) {
171      visitorClasses.add(AccessorVisitor.class);
172    }
173    visitorClasses.add(ComplexityVisitor.class);
174    visitorClasses.add(LinesOfCodeVisitor.class);
175    visitorClasses.add(FileLinesVisitor.class);
176    return visitorClasses;
177  }
178
179  @Override
180  public void accept(CodeVisitor visitor) {
181    if (visitor instanceof JavaAstVisitor) {
182      super.accept(visitor);
183    }
184  }
185}