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 }