• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.currysrc;
17 
18 import com.google.currysrc.api.RuleSet;
19 import com.google.currysrc.api.input.InputFileGenerator;
20 import com.google.currysrc.api.output.OutputSourceFileGenerator;
21 import com.google.currysrc.api.process.Context;
22 import com.google.currysrc.api.process.Reporter;
23 import com.google.currysrc.api.process.Rule;
24 
25 import org.eclipse.jdt.core.JavaCore;
26 import org.eclipse.jdt.core.compiler.IProblem;
27 import org.eclipse.jdt.core.dom.AST;
28 import org.eclipse.jdt.core.dom.ASTNode;
29 import org.eclipse.jdt.core.dom.ASTParser;
30 import org.eclipse.jdt.core.dom.CompilationUnit;
31 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
32 import org.eclipse.jface.text.BadLocationException;
33 import org.eclipse.jface.text.Document;
34 import org.eclipse.text.edits.TextEdit;
35 
36 import java.io.BufferedReader;
37 import java.io.File;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.InputStreamReader;
42 import java.io.OutputStreamWriter;
43 import java.io.PrintWriter;
44 import java.io.Writer;
45 import java.nio.charset.Charset;
46 import java.nio.charset.StandardCharsets;
47 import java.util.Arrays;
48 import java.util.List;
49 import java.util.Map;
50 
51 /**
52  * The main execution API for users of currysrc.
53  */
54 public final class Main {
55 
56   private static final Charset JAVA_SOURCE_CHARSET = StandardCharsets.UTF_8;
57 
58   private final boolean debug;
59   private Map<String, String> jdtOptions;
60   private Writer reportWriter;
61 
Main(boolean debug)62   public Main(boolean debug) {
63     this.debug = debug;
64   }
65 
setJdtOptions(Map<String, String> jdtOptions)66   public Main setJdtOptions(Map<String, String> jdtOptions) {
67     this.jdtOptions = jdtOptions;
68     return this;
69   }
70 
setReportWriter(Writer reportWriter)71   public Main setReportWriter(Writer reportWriter) {
72     this.reportWriter = reportWriter;
73     return this;
74   }
75 
execute(RuleSet ruleSet)76   public void execute(RuleSet ruleSet) throws Exception {
77     if (reportWriter == null) {
78       reportWriter = new OutputStreamWriter(System.out);
79     }
80 
81     Map<String, String> compilerOptions = this.jdtOptions;
82     if (compilerOptions == null) {
83       compilerOptions = defaultJdtOptions();
84     }
85     ASTParser parser = ASTParser.newParser(AST.JLS8);
86     parser.setCompilerOptions(compilerOptions);
87     parser.setKind(ASTParser.K_COMPILATION_UNIT);
88 
89     InputFileGenerator inputFileGenerator = ruleSet.getInputFileGenerator();
90     OutputSourceFileGenerator outputSourceFileGenerator = ruleSet.getOutputSourceFileGenerator();
91     for (File inputFile : inputFileGenerator.generate()) {
92       System.out.println("Processing: " + inputFile);
93 
94       String source = readSource(inputFile);
95       CompilationUnitHandler compilationUnitHandler = new CompilationUnitHandler(
96           inputFile, parser, source, new PrintWriter(reportWriter), compilerOptions
97       );
98       compilationUnitHandler.setDebug(debug);
99 
100       List<Rule> ruleList = ruleSet.getRuleList(inputFile);
101       if (!ruleList.isEmpty()) {
102         for (Rule rule : ruleList) {
103           compilationUnitHandler.apply(rule);
104         }
105       }
106 
107       File outputFile = outputSourceFileGenerator.generate(
108           compilationUnitHandler.getCompilationUnit(), inputFile);
109       if (outputFile != null) {
110         writeSource(compilationUnitHandler.getDocument(), outputFile);
111       }
112     }
113   }
114 
writeSource(Document document, File outputFile)115   private static void writeSource(Document document, File outputFile) throws IOException {
116     File outputDir = outputFile.getParentFile();
117     if (outputDir.exists()) {
118       if (!outputDir.isDirectory()) {
119         throw new IOException(outputDir + " is not a directory");
120       }
121     }
122     if (!outputDir.exists()) {
123       if (!outputDir.mkdirs()) {
124         throw new IOException("Unable to create " + outputDir);
125       }
126     }
127     String source = document.get();
128 
129     // TODO Look at guava for this
130     FileOutputStream fos = new FileOutputStream(outputFile);
131     Writer writer = new OutputStreamWriter(fos, JAVA_SOURCE_CHARSET);
132     try {
133       writer.write(source.toCharArray());
134     } finally {
135       writer.close();
136     }
137   }
138 
readSource(File file)139   private static String readSource(File file) throws IOException {
140     StringBuilder sb = new StringBuilder(2048);
141     try (BufferedReader reader = new BufferedReader(
142         new InputStreamReader(new FileInputStream(file), JAVA_SOURCE_CHARSET))) {
143       char[] buffer = new char[1024];
144       int count;
145       while ((count = reader.read(buffer)) != -1) {
146         sb.append(buffer, 0, count);
147       }
148     }
149     return sb.toString();
150   }
151 
152   private static class CompilationUnitHandler implements Context {
153 
154     private final File file;
155     private final ASTParser parser;
156     private final Reporter reporter;
157     private final Map<String, String> jdtOptions;
158 
159     private boolean debug;
160 
161     private Document documentBefore;
162     private CompilationUnit compilationUnitBefore;
163 
164     private Document documentRequested;
165     private TrackingASTRewrite rewriteRequested;
166 
CompilationUnitHandler(File file, ASTParser parser, String source, PrintWriter reportWriter, Map<String, String> jdtOptions)167     public CompilationUnitHandler(File file, ASTParser parser, String source,
168         PrintWriter reportWriter, Map<String, String> jdtOptions) {
169       this.file = file;
170       this.parser = parser;
171       this.reporter = new ReporterImpl(reportWriter);
172       this.jdtOptions = jdtOptions;
173 
174       // Initialize source / AST state.
175       documentBefore = new Document(source);
176       compilationUnitBefore = parseDocument(file, parser, documentBefore, jdtOptions);
177     }
178 
setDebug(boolean debug)179     public void setDebug(boolean debug) {
180       this.debug = debug;
181     }
182 
apply(Rule rule)183     public void apply(Rule rule) throws BadLocationException {
184       if (documentRequested != null || rewriteRequested != null) {
185         throw new AssertionError("Handler state not reset properly");
186       }
187 
188       if (rule.matches(compilationUnitBefore)) {
189         // Apply the rule.
190         rule.process(this, compilationUnitBefore);
191 
192         // Work out what happened, report/error as needed and reset the state.
193         CompilationUnit compilationUnitAfter;
194         Document documentAfter;
195         if (ruleUsedRewrite()) {
196           if (debug) {
197             System.out.println("AST processor: " + rule + ", rewrite: " +
198                 (rewriteRequested.isEmpty() ? "None" : rewriteRequested.toString()));
199           }
200           if (rewriteRequested.isEmpty()) {
201             if (rule.mustModify()) {
202               throw new RuntimeException("AST processor Rule: " + rule
203                   + " did not modify the compilation unit as it should");
204             }
205             documentAfter = documentBefore;
206             compilationUnitAfter = compilationUnitBefore;
207           } else {
208             Document documentToRewrite = new Document(documentBefore.get());
209             compilationUnitAfter = applyRewrite(file + " after " + rule, parser,
210                 documentToRewrite, rewriteRequested, jdtOptions);
211             documentAfter = documentToRewrite;
212           }
213         } else if (ruleUsedDocument()) {
214           String sourceBefore = documentBefore.get();
215           String sourceAfter = documentRequested.get();
216           if (debug) {
217             System.out.println(
218                 "Document processor: " + rule + ", diff: " +
219                     generateDiff(sourceBefore, sourceAfter));
220           }
221           if (sourceBefore.equals(sourceAfter)) {
222             if (rule.mustModify()) {
223               throw new RuntimeException("Document processor Rule: " + rule
224                   + " did not modify document as it should");
225             }
226             documentAfter = documentBefore;
227             compilationUnitAfter = compilationUnitBefore;
228           } else {
229             // Regenerate the AST from the modified document.
230             compilationUnitAfter = parseDocument(
231                 file + " after document processor " + rule, parser, documentRequested, jdtOptions);
232             documentAfter = documentRequested;
233           }
234         } else {
235           // The rule didn't request anything.... should this be an error?
236           compilationUnitAfter = compilationUnitBefore;
237           documentAfter = documentBefore;
238         }
239 
240         // Reset document / compilation state for the next round.
241         documentBefore = documentAfter;
242         compilationUnitBefore = compilationUnitAfter;
243         documentRequested = null;
244         rewriteRequested = null;
245       }
246     }
247 
rewrite()248     @Override public ASTRewrite rewrite() {
249       if (documentRequested != null) {
250         throw new IllegalStateException("document() already called.");
251       }
252       if (rewriteRequested != null) {
253         throw new IllegalStateException("rewrite() already called.");
254       }
255       rewriteRequested = createTrackingASTRewrite(compilationUnitBefore);
256       return rewriteRequested;
257     }
258 
document()259     @Override public Document document() {
260       if (rewriteRequested != null) {
261         throw new IllegalStateException("rewrite() already called.");
262       }
263       if (documentRequested != null) {
264         throw new IllegalStateException("document() already called.");
265       }
266       documentRequested = new Document(documentBefore.get());
267       return documentRequested;
268     }
269 
reporter()270     @Override public Reporter reporter() {
271       return reporter;
272     }
273 
getCompilationUnit()274     public CompilationUnit getCompilationUnit() {
275       return compilationUnitBefore;
276     }
277 
getDocument()278     public Document getDocument() {
279       return documentBefore;
280     }
281 
ruleUsedRewrite()282     private boolean ruleUsedRewrite() {
283       return rewriteRequested != null;
284     }
285 
ruleUsedDocument()286     private boolean ruleUsedDocument() {
287       return documentRequested != null;
288     }
289 
applyRewrite(Object documentId, ASTParser parser, Document document, ASTRewrite rewrite, Map<String, String> options)290     private static CompilationUnit applyRewrite(Object documentId, ASTParser parser,
291             Document document, ASTRewrite rewrite, Map<String, String> options)
292             throws BadLocationException {
293       TextEdit textEdit = rewrite.rewriteAST(document, options);
294       textEdit.apply(document, TextEdit.UPDATE_REGIONS);
295       // Reparse the document.
296       return parseDocument(documentId, parser, document, options);
297     }
298 
parseDocument(Object documentId, ASTParser parser, Document document, Map<String, String> compilerOptions)299     private static CompilationUnit parseDocument(Object documentId, ASTParser parser,
300         Document document, Map<String, String> compilerOptions) {
301       parser.setSource(document.get().toCharArray());
302       parser.setCompilerOptions(compilerOptions);
303 
304       CompilationUnit cu = (CompilationUnit) parser.createAST(null /* progressMonitor */);
305       if (cu.getProblems().length > 0) {
306         for(IProblem problem : cu.getProblems()) {
307             System.err.println("Error parsing:" + documentId + ":" +
308                     problem.getSourceLineNumber() + ": " + problem.toString());
309         }
310         throw new RuntimeException("Unable to parse document. Stopping.");
311       }
312       return cu;
313     }
314 
createTrackingASTRewrite(CompilationUnit cu)315     private static TrackingASTRewrite createTrackingASTRewrite(CompilationUnit cu) {
316       return new TrackingASTRewrite(cu.getAST());
317     }
318 
generateDiff(String before, String after)319     private static String generateDiff(String before, String after) {
320       if (before.equals(after)) {
321         return "No diff";
322       }
323       // TODO Implement this
324       return "Diff. DIFF NOT IMPLEMENTED";
325     }
326 
327     private class ReporterImpl implements Reporter {
328 
329       private final PrintWriter reportWriter;
330 
ReporterImpl(PrintWriter reportWriter)331       public ReporterImpl(PrintWriter reportWriter) {
332         this.reportWriter = reportWriter;
333       }
334 
info(String message)335       @Override public void info(String message) {
336         reportInternal(compilationUnitIdentifier(), message);
337       }
338 
339       @Override
info(ASTNode node, String message)340       public void info(ASTNode node, String message) {
341         reportInternal(nodeIdentifier(node), message);
342       }
343 
reportInternal(String locationIdentifier, String message)344       private void reportInternal(String locationIdentifier, String message) {
345         reportWriter
346             .append(locationIdentifier)
347             .append(": ")
348             .append(message)
349             .append('\n');
350       }
351 
compilationUnitIdentifier()352       private String compilationUnitIdentifier() {
353         return file.getPath();
354       }
355 
nodeIdentifier(ASTNode node)356       private String nodeIdentifier(ASTNode node) {
357         String approximateNodeLocation;
358         try {
359           approximateNodeLocation = "line approx. " +
360               documentBefore.getLineOfOffset(node.getStartPosition());
361         } catch (BadLocationException e) {
362           approximateNodeLocation = "unknown location";
363         }
364         return file.getPath() + "(" + approximateNodeLocation + ")";
365       }
366     }
367   }
368 
369   private static class TrackingASTRewrite extends ASTRewrite {
370 
TrackingASTRewrite(AST ast)371     public TrackingASTRewrite(AST ast) {
372       super(ast);
373     }
374 
isEmpty()375     public boolean isEmpty() {
376       return !getRewriteEventStore().getChangeRootIterator().hasNext();
377     }
378   }
379 
defaultJdtOptions()380   private static Map<String, String> defaultJdtOptions() {
381     Map<String, String> options = JavaCore.getOptions();
382     options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_8);
383     options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
384     options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
385     return options;
386   }
387 }
388