blob: 1b544e83e2cb43a647ed4394c76c3b63637d2dac [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 com.google.currysrc.api.Rules;
import com.google.currysrc.api.input.InputFileGenerator;
import com.google.currysrc.api.output.OutputSourceFileGenerator;
import com.google.currysrc.api.process.Context;
import com.google.currysrc.api.process.Reporter;
import com.google.currysrc.api.process.Rule;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
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 java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
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 {
execute(rules, new OutputStreamWriter(System.out));
}
public void execute(Rules rules, Writer reportWriter) throws Exception {
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
InputFileGenerator inputFileGenerator = rules.getInputFileGenerator();
OutputSourceFileGenerator outputSourceFileGenerator = rules.getOutputSourceFileGenerator();
for (File inputFile : inputFileGenerator.generate()) {
System.out.println("Processing: " + inputFile);
String source = readSource(inputFile);
CompilationUnitHandler compilationUnitHandler =
new CompilationUnitHandler(inputFile, parser, source, new PrintWriter(reportWriter));
compilationUnitHandler.setDebug(debug);
List<Rule> ruleList = rules.getRuleList(inputFile);
if (!ruleList.isEmpty()) {
for (Rule rule : ruleList) {
compilationUnitHandler.apply(rule);
}
}
File outputFile = outputSourceFileGenerator.generate(
compilationUnitHandler.getCompilationUnit(), inputFile);
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 Context {
private final File file;
private final ASTParser parser;
private final Reporter reporter;
private boolean debug;
private Document documentBefore;
private CompilationUnit compilationUnitBefore;
private Document documentRequested;
private TrackingASTRewrite rewriteRequested;
public CompilationUnitHandler(File file, ASTParser parser, String source,
PrintWriter reportWriter) {
this.file = file;
this.parser = parser;
this.reporter = new ReporterImpl(reportWriter);
// Initialize source / AST state.
documentBefore = new Document(source);
compilationUnitBefore = parseDocument(file, parser, documentBefore);
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public void apply(Rule rule) throws BadLocationException {
if (documentRequested != null || rewriteRequested != null) {
throw new AssertionError("Handler state not reset properly");
}
if (rule.matches(compilationUnitBefore)) {
// Apply the rule.
rule.process(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 processor: " + rule + ", rewrite: " +
(rewriteRequested.isEmpty() ? "None" : rewriteRequested.toString()));
}
if (rewriteRequested.isEmpty()) {
if (rule.mustModify()) {
throw new RuntimeException("AST processor Rule: " + rule
+ " did not modify the compilation unit as it should");
}
documentAfter = documentBefore;
compilationUnitAfter = compilationUnitBefore;
} else {
Document documentToRewrite = new Document(documentBefore.get());
compilationUnitAfter = applyRewrite(file + " after " + rule, parser,
documentToRewrite, rewriteRequested);
documentAfter = documentToRewrite;
}
} else if (ruleUsedDocument()) {
String sourceBefore = documentBefore.get();
String sourceAfter = documentRequested.get();
if (debug) {
System.out.println(
"Document processor: " + rule + ", diff: " +
generateDiff(sourceBefore, sourceAfter));
}
if (sourceBefore.equals(sourceAfter)) {
if (rule.mustModify()) {
throw new RuntimeException("Document processor Rule: " + rule
+ " did not modify document as it should");
}
documentAfter = documentBefore;
compilationUnitAfter = compilationUnitBefore;
} else {
// Regenerate the AST from the modified document.
compilationUnitAfter = parseDocument(
file + " after document processor " + rule, 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;
}
@Override public Reporter reporter() {
return reporter;
}
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 class ReporterImpl implements Reporter {
private final PrintWriter reportWriter;
public ReporterImpl(PrintWriter reportWriter) {
this.reportWriter = reportWriter;
}
@Override public void info(String message) {
reportInternal(compilationUnitIdentifier(), message);
}
@Override
public void info(ASTNode node, String message) {
reportInternal(nodeIdentifier(node), message);
}
private void reportInternal(String locationIdentifier, String message) {
reportWriter
.append(locationIdentifier)
.append(": ")
.append(message)
.append('\n');
}
private String compilationUnitIdentifier() {
return file.getPath();
}
private String nodeIdentifier(ASTNode node) {
String approximateNodeLocation;
try {
approximateNodeLocation = "line approx. " +
documentBefore.getLineOfOffset(node.getStartPosition());
} catch (BadLocationException e) {
approximateNodeLocation = "unknown location";
}
return file.getPath() + "(" + approximateNodeLocation + ")";
}
}
}
private static class TrackingASTRewrite extends ASTRewrite {
public TrackingASTRewrite(AST ast) {
super(ast);
}
public boolean isEmpty() {
return !getRewriteEventStore().getChangeRootIterator().hasNext();
}
}
}