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 }