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}