blob: 94c08c4441dd2a2fe6fa4b123543a69218933905 [file] [log] [blame]
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.currysrc;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.text.edits.TextEdit;
import com.google.currysrc.api.Rules;
import com.google.currysrc.api.input.InputFileGenerator;
import com.google.currysrc.api.output.OutputSourceFileGenerator;
import com.google.currysrc.api.transform.TransformRule;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* The main execution API for users of currysrc.
*/
public final class Main {
private static final Charset JAVA_SOURCE_CHARSET = StandardCharsets.UTF_8;
private final boolean debug;
public Main(boolean debug) {
this.debug = debug;
}
public void execute(Rules rules) throws Exception {
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
InputFileGenerator inputFileGenerator = rules.getInputFileGenerator();
OutputSourceFileGenerator outputSourceFileGenerator = rules.getOutputSourceFileGenerator();
for (File file : inputFileGenerator.generate()) {
System.out.println("Processing: " + file);
String source = readSource(file);
CompilationUnitHandler compilationUnitHandler =
new CompilationUnitHandler(file, parser, source);
compilationUnitHandler.setDebug(debug);
List<TransformRule> transformRules = rules.getTransformRules(file);
if (!transformRules.isEmpty()) {
for (TransformRule transformRule : transformRules) {
compilationUnitHandler.apply(transformRule);
}
}
File outputFile =
outputSourceFileGenerator.generate(compilationUnitHandler.getCompilationUnit());
if (outputFile != null) {
writeSource(compilationUnitHandler.getDocument(), outputFile);
}
}
}
private static void writeSource(Document document, File outputFile) throws IOException {
File outputDir = outputFile.getParentFile();
if (outputDir.exists()) {
if (!outputDir.isDirectory()) {
throw new IOException(outputDir + " is not a directory");
}
}
if (!outputDir.exists()) {
if (!outputDir.mkdirs()) {
throw new IOException("Unable to create " + outputDir);
}
}
String source = document.get();
// TODO Look at guava for this
FileOutputStream fos = new FileOutputStream(outputFile);
Writer writer = new OutputStreamWriter(fos, JAVA_SOURCE_CHARSET);
try {
writer.write(source.toCharArray());
} finally {
writer.close();
}
}
private static String readSource(File file) throws IOException {
StringBuilder sb = new StringBuilder(2048);
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(new FileInputStream(file), JAVA_SOURCE_CHARSET))) {
char[] buffer = new char[1024];
int count;
while ((count = reader.read(buffer)) != -1) {
sb.append(buffer, 0, count);
}
}
return sb.toString();
}
private static class CompilationUnitHandler implements TransformRule.Context {
private final File file;
private final ASTParser parser;
private boolean debug;
private Document documentBefore;
private CompilationUnit compilationUnitBefore;
private Document documentRequested;
private TrackingASTRewrite rewriteRequested;
public CompilationUnitHandler(File file, ASTParser parser, String source) {
this.file = file;
this.parser = parser;
// Initialize source / AST state.
documentBefore = new Document(source);
compilationUnitBefore = parseDocument(file, parser, documentBefore);
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public void apply(TransformRule transformRule) throws BadLocationException {
if (documentRequested != null || rewriteRequested != null) {
throw new AssertionError("Handler state not reset properly");
}
if (transformRule.matches(compilationUnitBefore)) {
// Apply the rule.
transformRule.transform(this, compilationUnitBefore);
// Work out what happened, report/error as needed and reset the state.
CompilationUnit compilationUnitAfter;
Document documentAfter;
if (ruleUsedRewrite()) {
if (debug) {
System.out.println("AST Transform: " + transformRule + ", rewrite: " +
(rewriteRequested.isEmpty() ? "None" : rewriteRequested.toString()));
}
if (rewriteRequested.isEmpty()) {
if (transformRule.mustModify()) {
throw new RuntimeException("AST Transformer Rule: " + transformRule
+ " did not modify the compilation unit as it should");
}
documentAfter = documentBefore;
compilationUnitAfter = compilationUnitBefore;
} else {
Document documentToRewrite = new Document(documentBefore.get());
compilationUnitAfter = applyRewrite(file + " after " + transformRule, parser,
documentToRewrite, rewriteRequested);
documentAfter = documentToRewrite;
}
} else if (ruleUsedDocument()) {
String sourceBefore = documentBefore.get();
String sourceAfter = documentRequested.get();
if (debug) {
System.out.println(
"Document Transformer: " + transformRule + ", diff: " +
generateDiff(sourceBefore, sourceAfter));
}
if (sourceBefore.equals(sourceAfter)) {
if (transformRule.mustModify()) {
throw new RuntimeException("Document Transformer Rule: " + transformRule
+ " did not modify document as it should");
}
documentAfter = documentBefore;
compilationUnitAfter = compilationUnitBefore;
} else {
// Regenerate the AST from the modified document.
compilationUnitAfter = parseDocument(
file + " after document transformer " + transformRule, parser, documentRequested);
documentAfter = documentRequested;
}
} else {
// The rule didn't request anything.... should this be an error?
compilationUnitAfter = compilationUnitBefore;
documentAfter = documentBefore;
}
// Reset document / compilation state for the next round.
documentBefore = documentAfter;
compilationUnitBefore = compilationUnitAfter;
documentRequested = null;
rewriteRequested = null;
}
}
@Override public ASTRewrite rewrite() {
if (documentRequested != null) {
throw new IllegalStateException("document() already called.");
}
if (rewriteRequested != null) {
throw new IllegalStateException("rewrite() already called.");
}
rewriteRequested = createTrackingASTRewrite(compilationUnitBefore);
return rewriteRequested;
}
@Override public Document document() {
if (rewriteRequested != null) {
throw new IllegalStateException("rewrite() already called.");
}
if (documentRequested != null) {
throw new IllegalStateException("document() already called.");
}
documentRequested = new Document(documentBefore.get());
return documentRequested;
}
public CompilationUnit getCompilationUnit() {
return compilationUnitBefore;
}
public Document getDocument() {
return documentBefore;
}
private boolean ruleUsedRewrite() {
return rewriteRequested != null;
}
private boolean ruleUsedDocument() {
return documentRequested != null;
}
private static CompilationUnit applyRewrite(Object documentId, ASTParser parser,
Document document, ASTRewrite rewrite) throws BadLocationException {
TextEdit textEdit = rewrite.rewriteAST(document, null);
textEdit.apply(document, TextEdit.UPDATE_REGIONS);
// Reparse the document.
return parseDocument(documentId, parser, document);
}
private static CompilationUnit parseDocument(Object documentId, ASTParser parser,
Document document) {
parser.setSource(document.get().toCharArray());
configureParser(parser);
CompilationUnit cu = (CompilationUnit) parser.createAST(null /* progressMonitor */);
if (cu.getProblems().length > 0) {
System.err.println("Error parsing:" + documentId + ": " + Arrays.toString(cu.getProblems()));
throw new RuntimeException("Unable to parse document. Stopping.");
}
return cu;
}
private static void configureParser(ASTParser parser) {
Map<String, String> options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_1_7);
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_7);
options.put(JavaCore.COMPILER_DOC_COMMENT_SUPPORT, JavaCore.ENABLED);
parser.setCompilerOptions(options);
}
private static TrackingASTRewrite createTrackingASTRewrite(CompilationUnit cu) {
return new TrackingASTRewrite(cu.getAST());
}
private static String generateDiff(String before, String after) {
if (before.equals(after)) {
return "No diff";
}
// TODO Implement this
return "Diff. DIFF NOT IMPLEMENTED";
}
}
private static class TrackingASTRewrite extends ASTRewrite {
public TrackingASTRewrite(AST ast) {
super(ast);
}
public boolean isEmpty() {
return !getRewriteEventStore().getChangeRootIterator().hasNext();
}
}
}