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