001    /*
002     * Sonar, open source software quality management tool.
003     * Copyright (C) 2008-2011 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 org.apache.commons.io.FileUtils;
024    import org.apache.commons.io.IOUtils;
025    import org.apache.commons.io.filefilter.FileFilterUtils;
026    import org.slf4j.Logger;
027    import org.slf4j.LoggerFactory;
028    import org.sonar.api.resources.InputFile;
029    import org.sonar.api.resources.InputFileUtils;
030    import org.sonar.java.ast.visitor.*;
031    import org.sonar.java.squid.JavaSquidConfiguration;
032    import org.sonar.squid.api.AnalysisException;
033    import org.sonar.squid.api.CodeScanner;
034    import org.sonar.squid.api.CodeVisitor;
035    import org.sonar.squid.api.SourceCode;
036    import org.xml.sax.InputSource;
037    
038    import com.puppycrawl.tools.checkstyle.Checker;
039    import com.puppycrawl.tools.checkstyle.ConfigurationLoader;
040    import com.puppycrawl.tools.checkstyle.PropertiesExpander;
041    import com.puppycrawl.tools.checkstyle.api.Configuration;
042    
043    import java.io.ByteArrayInputStream;
044    import java.io.File;
045    import java.io.InputStream;
046    import java.nio.charset.Charset;
047    import java.util.*;
048    
049    /**
050     * Squid uses Checkstyle to get an out-of-the-box java parser with AST generation and visitor pattern support.
051     */
052    public class JavaAstScanner extends CodeScanner<JavaAstVisitor> {
053    
054      private static final Logger LOG = LoggerFactory.getLogger(JavaAstScanner.class);
055      private JavaSquidConfiguration conf;
056      private SourceCode project;
057    
058      public JavaAstScanner(JavaSquidConfiguration conf, SourceCode project) {
059        this.conf = conf;
060        this.project = project;
061      }
062    
063      private Checker createChecker(Charset charset) {
064        InputStream checkstyleConfig = null;
065        try {
066          checkstyleConfig = JavaAstScanner.class.getClassLoader().getResourceAsStream("checkstyle-configuration.xml");
067          String readenConfig = IOUtils.toString(checkstyleConfig);
068          readenConfig = readenConfig.replace("${charset}", charset.toString());
069          checkstyleConfig = new ByteArrayInputStream(readenConfig.getBytes());
070          Configuration config = ConfigurationLoader.loadConfiguration(new InputSource(checkstyleConfig), new PropertiesExpander(System
071              .getProperties()), false);
072          Checker c = new Checker();
073          final ClassLoader moduleClassLoader = Checker.class.getClassLoader();
074          c.setModuleClassLoader(moduleClassLoader);
075          c.configure(config);
076          c.addListener(new CheckstyleAuditListener());
077          return c;
078    
079        } catch (Exception e) { // NOSONAR We want to be sure to catch any unexpected exception
080          throw new AnalysisException(
081              "Unable to create Checkstyle Checker object with 'checkstyle-configuration.xml' as Checkstyle configuration file name", e);
082    
083        } finally {
084          IOUtils.closeQuietly(checkstyleConfig);
085        }
086      }
087    
088      public JavaAstScanner scanDirectory(File javaSourceDirectory) {
089        List<InputFile> inputFiles = Lists.newArrayList();
090        Collection<File> files = FileUtils.listFiles(javaSourceDirectory, FileFilterUtils.fileFileFilter(), FileFilterUtils.directoryFileFilter());
091        for (File file : files) {
092          inputFiles.add(InputFileUtils.create(javaSourceDirectory, file));
093        }
094        return scanFiles(inputFiles);
095      }
096    
097      public JavaAstScanner scanFile(InputFile javaFile) {
098        return scanFiles(Arrays.asList(javaFile));
099      }
100    
101      public JavaAstScanner scanFiles(Collection<InputFile> inputFiles) {
102        if (LOG.isDebugEnabled()) {
103          LOG.debug("----- Java sources analyzed by Squid:");
104          for (InputFile inputFile : inputFiles) {
105            LOG.debug(inputFile.toString());
106          }
107          LOG.debug("-----");
108        }
109    
110        Stack<SourceCode> resourcesStack = new Stack<SourceCode>();
111        resourcesStack.add(project);
112        for (JavaAstVisitor visitor : getVisitors()) {
113          visitor.setSourceCodeStack(resourcesStack);
114        }
115        CheckstyleSquidBridge.setASTVisitors(getVisitors());
116        CheckstyleSquidBridge.setSquidConfiguration(conf);
117        CheckstyleSquidBridge.setInputFiles(inputFiles);
118        launchCheckstyle(InputFileUtils.toFiles(inputFiles), conf.getCharset());
119        return this;
120      }
121    
122      private void launchCheckstyle(Collection<File> files, Charset charset) {
123        Checker c = createChecker(charset);
124        ClassLoader initialClassLoader = Thread.currentThread().getContextClassLoader();
125        Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
126        try {
127          c.setClassloader(getClass().getClassLoader());
128          c.setModuleClassLoader(getClass().getClassLoader());
129          c.process(Lists.<File>newArrayList(files));
130          c.destroy();
131        } finally {
132          Thread.currentThread().setContextClassLoader(initialClassLoader);
133        }
134      }
135    
136    
137      @Override
138      public Collection<Class<? extends JavaAstVisitor>> getVisitorClasses() {
139        List<Class<? extends JavaAstVisitor>> visitorClasses = Lists.newArrayList();
140        visitorClasses.add(PackageVisitor.class);
141        visitorClasses.add(FileVisitor.class);
142        visitorClasses.add(ClassVisitor.class);
143        visitorClasses.add(AnonymousInnerClassVisitor.class);
144        visitorClasses.add(MethodVisitor.class);
145        visitorClasses.add(EndAtLineVisitor.class);
146        visitorClasses.add(LinesVisitor.class);
147        visitorClasses.add(BlankLinesVisitor.class);
148        visitorClasses.add(CommentVisitor.class);
149        visitorClasses.add(PublicApiVisitor.class);
150        visitorClasses.add(BranchVisitor.class);
151        visitorClasses.add(StatementVisitor.class);
152        if (conf.isAnalysePropertyAccessors()) {
153          visitorClasses.add(AccessorVisitor.class);
154        }
155        visitorClasses.add(ComplexityVisitor.class);
156        visitorClasses.add(LinesOfCodeVisitor.class);
157        return visitorClasses;
158      }
159    
160      @Override
161      public void accept(CodeVisitor visitor) {
162        if (visitor instanceof JavaAstVisitor) {
163          super.accept(visitor);
164        }
165      }
166    }