001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2009 SonarSource SA 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.squid.ast; 021 022 import java.io.ByteArrayInputStream; 023 import java.io.File; 024 import java.io.IOException; 025 import java.io.InputStream; 026 import java.io.OutputStream; 027 import java.nio.charset.Charset; 028 import java.util.ArrayList; 029 import java.util.Collection; 030 import java.util.List; 031 import java.util.Stack; 032 033 import org.apache.commons.io.IOUtils; 034 import org.slf4j.Logger; 035 import org.slf4j.LoggerFactory; 036 import org.sonar.squid.Squid; 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.sonar.squid.api.SquidConfiguration; 042 import org.sonar.squid.ast.visitor.AccessorVisitor; 043 import org.sonar.squid.ast.visitor.AstVisitor; 044 import org.sonar.squid.ast.visitor.BlankLinesVisitor; 045 import org.sonar.squid.ast.visitor.BranchVisitor; 046 import org.sonar.squid.ast.visitor.ClassVisitor; 047 import org.sonar.squid.ast.visitor.CommentVisitor; 048 import org.sonar.squid.ast.visitor.ComplexityVisitor; 049 import org.sonar.squid.ast.visitor.EndAtLineVisitor; 050 import org.sonar.squid.ast.visitor.FileVisitor; 051 import org.sonar.squid.ast.visitor.LinesVisitor; 052 import org.sonar.squid.ast.visitor.MethodVisitor; 053 import org.sonar.squid.ast.visitor.LinesOfCodeVisitor; 054 import org.sonar.squid.ast.visitor.PackageVisitor; 055 import org.sonar.squid.ast.visitor.PublicApiVisitor; 056 import org.sonar.squid.ast.visitor.StatementVisitor; 057 058 import com.puppycrawl.tools.checkstyle.Checker; 059 import com.puppycrawl.tools.checkstyle.ConfigurationLoader; 060 import com.puppycrawl.tools.checkstyle.DefaultLogger; 061 import com.puppycrawl.tools.checkstyle.PropertiesExpander; 062 import com.puppycrawl.tools.checkstyle.api.AuditEvent; 063 import com.puppycrawl.tools.checkstyle.api.AuditListener; 064 import com.puppycrawl.tools.checkstyle.api.Configuration; 065 066 /** 067 * Squid uses Checkstyle to get an out-of-the-box java parser with AST generation and visitor pattern support. 068 */ 069 public class JavaAstScanner implements CodeScanner { 070 071 private SquidConfiguration conf; 072 private List<AstVisitor> visitors = new ArrayList<AstVisitor>(); 073 074 public JavaAstScanner(SquidConfiguration conf) { 075 this.conf = conf; 076 } 077 078 /** 079 * Create and execute the Checkstyle engine. 080 * 081 * @param files 082 * collection of files to analyse. This list shouldn't contain and directory. 083 * @param charset 084 * the default charset to use to read files 085 * @param confFileName 086 * the Checkstyle configuration file name to load. 087 */ 088 private void launchCheckstyleEngine(Collection<File> files, Charset charset) throws AnalysisException { 089 ErrorsListener listener = new ErrorsListener(); 090 Checker c = createChecker(charset, listener); 091 File[] processedFiles = new File[files.size()]; 092 files.toArray(processedFiles); 093 c.process(processedFiles); 094 c.destroy(); 095 if (!listener.getErrors().isEmpty()) { 096 throw listener.getErrors().get(0); 097 } 098 } 099 100 /** 101 * Creates the Checkstyle Checker object. 102 * 103 * @return a nice new fresh Checkstyle Checker 104 */ 105 private Checker createChecker(Charset charset, ErrorsListener errorsListener) { 106 try { 107 InputStream checkstyleConfig = JavaAstScanner.class.getClassLoader().getResourceAsStream("checkstyle-configuration.xml"); 108 String readenConfig = IOUtils.toString(checkstyleConfig); 109 readenConfig = readenConfig.replace("${charset}", charset.toString()); 110 checkstyleConfig = new ByteArrayInputStream(readenConfig.getBytes()); 111 Configuration config = ConfigurationLoader.loadConfiguration(checkstyleConfig, new PropertiesExpander(System.getProperties()), false); 112 Checker c = new Checker(); 113 c.configure(config); 114 final Logger logger = LoggerFactory.getLogger(Squid.class); 115 StreamLogger infoLogger = new StreamLogger() { 116 117 @Override 118 public void log(String log) { 119 logger.info(log); 120 } 121 }; 122 StreamLogger errorLogger = new StreamLogger() { 123 124 @Override 125 public void log(String log) { 126 logger.error(log); 127 } 128 }; 129 c.addListener(new DefaultLogger(infoLogger, true, errorLogger, true)); 130 c.addListener(errorsListener); 131 return c; 132 } catch (final Exception e) { 133 throw new RuntimeException( 134 "Unable to create Checkstyle Checker object with 'checkstyle-configuration.xml' as Checkstyle configuration file name", e); 135 } 136 } 137 138 private static class ErrorsListener implements AuditListener { 139 140 private List<AnalysisException> errors = new ArrayList<AnalysisException>(); 141 142 public void addError(AuditEvent evt) { 143 // some projects can have file parsing errors (tapestry for example) 144 // currently do not throw an error. 145 // see 146 // http://sourceforge.net/tracker/?func=detail&atid=397078&aid=1667137&group_id=29721 147 if (evt.getMessage().contains("expecting EOF, found")) 148 return; 149 errors.add(new AnalysisException(evt.getMessage())); 150 } 151 152 public void addException(AuditEvent evt, Throwable throwable) { 153 errors.add(new AnalysisException(evt.getMessage(), throwable)); 154 } 155 156 public void auditFinished(AuditEvent evt) { 157 } 158 159 public void auditStarted(AuditEvent evt) { 160 } 161 162 public void fileFinished(AuditEvent evt) { 163 } 164 165 public void fileStarted(AuditEvent evt) { 166 } 167 168 public List<AnalysisException> getErrors() { 169 return errors; 170 } 171 } 172 private abstract static class StreamLogger extends OutputStream { 173 174 private StringBuilder builder = new StringBuilder(256); 175 176 @Override 177 public void write(int byteToWrite) throws IOException { 178 char character = (char) byteToWrite; 179 if (character == '\n') { 180 logAndResetBuffer(); 181 } else { 182 builder.append(character); 183 } 184 } 185 186 private void logAndResetBuffer() { 187 log(builder.toString().trim()); 188 builder.setLength(0); 189 } 190 191 public abstract void log(String log); 192 193 @Override 194 public void close() throws IOException { 195 if (builder.length() > 0) { 196 logAndResetBuffer(); 197 } 198 super.close(); 199 } 200 } 201 202 public void scanCode(SourceCode project, Collection<File> filesToAnalyse) { 203 Stack<SourceCode> resourcesStack = new Stack<SourceCode>(); 204 resourcesStack.add(project); 205 for (AstVisitor visitor : visitors) { 206 visitor.setResourcesStack(resourcesStack); 207 } 208 CheckstyleSquidBridge.setASTVisitors(visitors); 209 CheckstyleSquidBridge.setSquidConfiguration(conf); 210 launchCheckstyleEngine(filesToAnalyse, conf.getCharset()); 211 } 212 213 public void accept(CodeVisitor visitor) { 214 visitors.add((AstVisitor) visitor); 215 } 216 217 public Collection<Class<? extends CodeVisitor>> getVisitors() { 218 List<Class<? extends CodeVisitor>> visitorClasses = new ArrayList<Class<? extends CodeVisitor>>(); 219 visitorClasses.add(PackageVisitor.class); 220 visitorClasses.add(FileVisitor.class); 221 visitorClasses.add(ClassVisitor.class); 222 visitorClasses.add(MethodVisitor.class); 223 visitorClasses.add(EndAtLineVisitor.class); 224 visitorClasses.add(LinesVisitor.class); 225 visitorClasses.add(BlankLinesVisitor.class); 226 visitorClasses.add(CommentVisitor.class); 227 visitorClasses.add(PublicApiVisitor.class); 228 visitorClasses.add(BranchVisitor.class); 229 visitorClasses.add(StatementVisitor.class); 230 visitorClasses.add(ComplexityVisitor.class); 231 visitorClasses.add(LinesOfCodeVisitor.class); 232 if (conf.isAnalysePropertyAccessors()) { 233 visitorClasses.add(AccessorVisitor.class); 234 } 235 return visitorClasses; 236 } 237 }