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 }