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     */
020    package org.sonar.java.ast;
021    
022    import com.google.common.collect.Lists;
023    import com.google.common.io.Closeables;
024    import com.puppycrawl.tools.checkstyle.Checker;
025    import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
026    import com.puppycrawl.tools.checkstyle.PropertiesExpander;
027    import com.puppycrawl.tools.checkstyle.api.Configuration;
028    import org.apache.commons.io.FileUtils;
029    import org.apache.commons.io.IOUtils;
030    import org.apache.commons.io.filefilter.FileFilterUtils;
031    import org.slf4j.Logger;
032    import org.slf4j.LoggerFactory;
033    import org.sonar.api.resources.InputFile;
034    import org.sonar.api.resources.InputFileUtils;
035    import org.sonar.java.ast.visitor.*;
036    import org.sonar.java.squid.JavaSquidConfiguration;
037    import org.sonar.squid.api.AnalysisException;
038    import org.sonar.squid.api.CodeScanner;
039    import org.sonar.squid.api.CodeVisitor;
040    import org.sonar.squid.api.SourceCode;
041    import org.xml.sax.InputSource;
042    
043    import java.io.ByteArrayInputStream;
044    import java.io.File;
045    import java.io.IOException;
046    import java.io.InputStream;
047    import java.nio.charset.Charset;
048    import java.util.Arrays;
049    import java.util.Collection;
050    import java.util.List;
051    import 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     */
056    public 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    }