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