Thorough work on inserting comments in the proper place for delombok; should now work fine with GWT native methods!

NON-NLS-1 is still theoretically problematic, but that'll be a fix for another day.

Also added ability to recognize that nothing has changed, which will copy the original file instead of reparsing it.
This commit is contained in:
Roel Spilker
2009-11-27 04:23:41 +01:00
parent e54c3f36e3
commit 391db3dcec
19 changed files with 169 additions and 35 deletions
+1 -1
View File
@@ -14,7 +14,7 @@
<classpathentry kind="lib" path="deps/lombok/org.eclipse.equinox.common_3.5.0.v20090520-1800.jar"/>
<classpathentry kind="lib" path="deps/junit-4.7.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/lombok.patcher"/>
<classpathentry kind="lib" path="deps/lombok/tools.jar"/>
<classpathentry kind="lib" path="deps/lombok/tools.jar" sourcepath="C:/javasource/javasources.zip"/>
<classpathentry kind="lib" path="deps/lombok/spi-0.2.4.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
+6 -1
View File
@@ -42,7 +42,7 @@ public class JavacTransformer {
this.handlers = HandlerLibrary.load(messager);
}
public void transform(Context context, Iterable<JCCompilationUnit> compilationUnits) {
public boolean transform(Context context, Iterable<JCCompilationUnit> compilationUnits) {
List<JavacAST> asts = new ArrayList<JavacAST>();
for (JCCompilationUnit unit : compilationUnits) asts.add(new JavacAST(messager, context, unit));
@@ -57,6 +57,11 @@ public class JavacTransformer {
for (JavacAST ast : asts) {
ast.traverse(new AnnotationVisitor());
}
for (JavacAST ast : asts) {
if (ast.isChanged()) return true;
}
return false;
}
private class AnnotationVisitor extends JavacASTAdapter {
@@ -21,6 +21,7 @@
*/
package lombok.javac.handlers;
import static lombok.javac.handlers.JavacHandlerUtil.markAnnotationAsProcessed;
import lombok.Cleanup;
import lombok.core.AnnotationValues;
import lombok.core.AST.Kind;
@@ -53,6 +54,7 @@ import com.sun.tools.javac.util.Name;
@ProviderFor(JavacAnnotationHandler.class)
public class HandleCleanup implements JavacAnnotationHandler<Cleanup> {
@Override public boolean handle(AnnotationValues<Cleanup> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, Cleanup.class);
String cleanupName = annotation.getInstance().value();
if (cleanupName.length() == 0) {
annotationNode.addError("cleanupName cannot be the empty string.");
@@ -59,6 +59,7 @@ import com.sun.tools.javac.util.List;
@ProviderFor(JavacAnnotationHandler.class)
public class HandleData implements JavacAnnotationHandler<Data> {
@Override public boolean handle(AnnotationValues<Data> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, Data.class);
JavacNode typeNode = annotationNode.up();
JCClassDecl typeDecl = null;
if (typeNode.get() instanceof JCClassDecl) typeDecl = (JCClassDecl)typeNode.get();
@@ -72,6 +72,7 @@ public class HandleEqualsAndHashCode implements JavacAnnotationHandler<EqualsAnd
}
@Override public boolean handle(AnnotationValues<EqualsAndHashCode> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, EqualsAndHashCode.class);
EqualsAndHashCode ann = annotation.getInstance();
List<String> excludes = List.from(ann.exclude());
List<String> includes = List.from(ann.of());
@@ -80,8 +80,10 @@ public class HandleGetter implements JavacAnnotationHandler<Getter> {
}
@Override public boolean handle(AnnotationValues<Getter> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, Getter.class);
JavacNode fieldNode = annotationNode.up();
AccessLevel level = annotation.getInstance().value();
if (level == AccessLevel.NONE) return true;
return createGetterForField(level, fieldNode, annotationNode, true);
@@ -82,8 +82,10 @@ public class HandleSetter implements JavacAnnotationHandler<Setter> {
}
@Override public boolean handle(AnnotationValues<Setter> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, Setter.class);
JavacNode fieldNode = annotationNode.up();
AccessLevel level = annotation.getInstance().value();
if (level == AccessLevel.NONE) return true;
return createSetterForField(level, fieldNode, annotationNode, true);
@@ -22,6 +22,7 @@
package lombok.javac.handlers;
import static lombok.javac.handlers.JavacHandlerUtil.chainDots;
import static lombok.javac.handlers.JavacHandlerUtil.markAnnotationAsProcessed;
import java.util.ArrayList;
import java.util.Collection;
@@ -49,6 +50,7 @@ import com.sun.tools.javac.util.List;
@ProviderFor(JavacAnnotationHandler.class)
public class HandleSneakyThrows implements JavacAnnotationHandler<SneakyThrows> {
@Override public boolean handle(AnnotationValues<SneakyThrows> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, SneakyThrows.class);
Collection<String> exceptionNames = annotation.getRawExpressions("value");
List<JCExpression> memberValuePairs = ast.getArguments();
@@ -51,10 +51,12 @@ public class HandleSynchronized implements JavacAnnotationHandler<Synchronized>
private static final String STATIC_LOCK_NAME = "$LOCK";
@Override public boolean handle(AnnotationValues<Synchronized> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, Synchronized.class);
JavacNode methodNode = annotationNode.up();
if (methodNode == null || methodNode.getKind() != Kind.METHOD || !(methodNode.get() instanceof JCMethodDecl)) {
annotationNode.addError("@Synchronized is legal only on methods.");
return true;
}
@@ -62,6 +64,7 @@ public class HandleSynchronized implements JavacAnnotationHandler<Synchronized>
if ((method.mods.flags & Flags.ABSTRACT) != 0) {
annotationNode.addError("@Synchronized is legal only on concrete methods.");
return true;
}
boolean isStatic = (method.mods.flags & Flags.STATIC) != 0;
@@ -68,6 +68,8 @@ public class HandleToString implements JavacAnnotationHandler<ToString> {
}
@Override public boolean handle(AnnotationValues<ToString> annotation, JCAnnotation ast, JavacNode annotationNode) {
markAnnotationAsProcessed(annotationNode, ToString.class);
ToString ann = annotation.getInstance();
List<String> excludes = List.from(ann.exclude());
List<String> includes = List.from(ann.of());
@@ -21,6 +21,7 @@
*/
package lombok.javac.handlers;
import java.lang.annotation.Annotation;
import java.util.regex.Pattern;
import lombok.AccessLevel;
@@ -34,7 +35,9 @@ import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCImport;
import com.sun.tools.javac.tree.JCTree.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
@@ -57,6 +60,54 @@ public class JavacHandlerUtil {
return TransformationsUtil.PRIMITIVE_TYPE_NAME_PATTERN.matcher(typeName).matches();
}
/**
* Removes the annotation from javac's AST, then removes it from lombok's AST,
* then removes any import statement that imports this exact annotation (not star imports).
*/
public static void markAnnotationAsProcessed(JavacNode annotation, Class<? extends Annotation> annotationType) {
JavacNode parentNode = annotation.directUp();
switch (parentNode.getKind()) {
case FIELD:
case ARGUMENT:
case LOCAL:
JCVariableDecl variable = (JCVariableDecl) parentNode.get();
variable.mods.annotations = filterList(variable.mods.annotations, annotation.get());
break;
case METHOD:
JCMethodDecl method = (JCMethodDecl) parentNode.get();
method.mods.annotations = filterList(method.mods.annotations, annotation.get());
break;
default:
throw new IllegalStateException("Don't know how to remove annotations from: " + parentNode.getKind());
}
parentNode.removeChild(annotation);
JCCompilationUnit unit = (JCCompilationUnit) annotation.top().get();
deleteImportFromCompilationUnit(unit, annotationType.getName());
}
private static void deleteImportFromCompilationUnit(JCCompilationUnit unit, String name) {
List<JCTree> newDefs = List.nil();
for (JCTree def : unit.defs) {
boolean delete = false;
if (def instanceof JCImport) {
JCImport imp0rt = (JCImport)def;
delete = (!imp0rt.staticImport && imp0rt.qualid.toString().equals(name));
}
if (!delete) newDefs = newDefs.append(def);
}
unit.defs = newDefs;
}
private static List<JCAnnotation> filterList(List<JCAnnotation> annotations, JCTree jcTree) {
List<JCAnnotation> newAnnotations = List.nil();
for (JCAnnotation ann : annotations) {
if (jcTree != ann) newAnnotations = newAnnotations.append(ann);
}
return newAnnotations;
}
/**
* Translates the given field into all possible getter names.
* Convenient wrapper around {@link TransformationsUtil#toAllGetterNames(CharSequence, boolean)}.
+11 -1
View File
@@ -23,11 +23,21 @@ package lombok.delombok;
public final class Comment {
final int pos;
final int prevEndPos;
final String content;
final int endPos;
final boolean newLine;
public Comment(int pos, String content) {
public Comment(int prevEndPos, int pos, int endPos, String content, boolean newLine) {
this.pos = pos;
this.prevEndPos = prevEndPos;
this.endPos = endPos;
this.content = content;
this.newLine = newLine;
}
public boolean isConnected() {
return !newLine && prevEndPos == pos;
}
public String summary() {
@@ -80,6 +80,20 @@ public class CommentCollectingScanner extends Scanner {
@Override
protected void processComment(CommentStyle style) {
comments.add(pos(), new String(getRawCharacters(pos(), endPos())));
int prevEndPos = prevEndPos();
int pos = pos();
boolean newLine = containsNewLine(prevEndPos, pos);
String content = new String(getRawCharacters(pos, endPos()));
comments.add(prevEndPos, pos, endPos(), content, newLine);
}
private boolean containsNewLine(int from, int to) {
for (char c : getRawCharacters(from, to)) {
if (c == '\n' || c == '\r') {
return true;
}
}
return false;
}
}
@@ -24,11 +24,13 @@ package lombok.delombok;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.Date;
import javax.annotation.processing.Messager;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.Element;
import javax.tools.JavaFileObject;
import javax.tools.Diagnostic.Kind;
import lombok.javac.JavacTransformer;
@@ -52,7 +54,7 @@ public class CommentPreservingParser {
this.encoding = encoding;
}
public ParseResult parseFile(String fileName) throws IOException {
public ParseResult parse(String fileName, boolean forceProcessing) throws IOException {
Context context = new Context();
Options.instance(context).put(OptionName.ENCODING, encoding);
@@ -74,9 +76,8 @@ public class CommentPreservingParser {
@SuppressWarnings("deprecation")
JCCompilationUnit cu = compiler.parse(fileName);
new JavacTransformer(messager).transform(context, Collections.singleton(cu));
return new ParseResult(comments.comments, cu);
boolean changed = new JavacTransformer(messager).transform(context, Collections.singleton(cu));
return new ParseResult(comments.comments, cu, forceProcessing || changed);
}
private static final Messager messager = new Messager() {
@@ -100,21 +101,33 @@ public class CommentPreservingParser {
static class Comments {
List<Comment> comments = List.nil();
void add(int pos, String content) {
comments = comments.append(new Comment(pos, content));
void add(int prevEndPos, int pos, int endPos, String content, boolean newLine) {
comments = comments.append(new Comment(prevEndPos, pos, endPos, content, newLine));
}
}
public static class ParseResult {
private final List<Comment> comments;
private final JCCompilationUnit compilationUnit;
private final boolean changed;
private ParseResult(List<Comment> comments, JCCompilationUnit compilationUnit) {
private ParseResult(List<Comment> comments, JCCompilationUnit compilationUnit, boolean changed) {
this.comments = comments;
this.compilationUnit = compilationUnit;
this.changed = changed;
}
public void print(Writer out) {
public void print(Writer out) throws IOException {
if (!changed) {
JavaFileObject sourceFile = compilationUnit.getSourceFile();
if (sourceFile != null) {
out.write(sourceFile.getCharContent(true).toString());
System.out.println(out.toString());
return;
}
}
out.write("// Generated by delombok at " + new Date() + "\n");
compilationUnit.accept(new PrettyCommentsPrinter(out, compilationUnit, comments));
}
}
@@ -179,7 +179,32 @@ public class PrettyCommentsPrinter extends JCTree.Visitor {
align();
}
}
private void consumeTrailingComments(int from) throws IOException {
boolean shouldIndent = !newLine;
Comment head = comments.head;
while (comments.nonEmpty() && head.prevEndPos == from) {
from = head.endPos;
if (!head.isConnected()) {
if (head.newLine) {
if (!newLine) {
println();
align();
}
}
else {
print(" ");
}
}
print(head.content);
comments = comments.tail;
head = comments.head;
}
if (newLine && shouldIndent) {
align();
}
}
/** The output stream on which trees are printed.
*/
Writer out;
@@ -277,15 +302,17 @@ public class PrettyCommentsPrinter extends JCTree.Visitor {
if (tree == null) print("/*missing*/");
else {
consumeComments(tree.pos);
tree.accept(this);
consumeComments(endPos(tree));
tree.accept(this);
int endPos = endPos(tree);
consumeTrailingComments(endPos);
consumeComments(endPos);
}
} catch (UncheckedIOException ex) {
IOException e = new IOException(ex.getMessage());
e.initCause(ex);
throw e;
} finally {
this.prec = prevPrec;
this.prec = prevPrec;
}
}
@@ -301,7 +328,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor {
public void printStat(JCTree tree) throws IOException {
printExpr(tree, TreeInfo.notExpression);
}
/** Derived visitor method: print list of expression trees, separated by given string.
* @param sep the separator string
*/
@@ -52,10 +52,9 @@ public class TestLombokFiles {
public void testSources() throws Exception {
File[] listFiles = BEFORE_FOLDER.listFiles();
for (File file : listFiles) {
ParseResult parseResult = parser.parseFile(file.toString());
ParseResult parseResult = parser.parse(file.toString(), true);
StringWriter writer = new StringWriter();
parseResult.print(writer);
System.out.println(writer);
compare(file.getName(), readAfter(file), writer.toString());
}
}
@@ -63,6 +62,9 @@ public class TestLombokFiles {
private void compare(String name, String expectedFile, String actualFile) {
String[] expectedLines = expectedFile.split("(\\r?\\n)");
String[] actualLines = actualFile.split("(\\r?\\n)");
if (actualLines[0].startsWith("// Generated by delombok at ")) {
actualLines[0] = "";
}
expectedLines = removeBlanks(expectedLines);
actualLines = removeBlanks(actualLines);
int size = Math.min(expectedLines.length, actualLines.length);
@@ -53,7 +53,7 @@ public class TestSourceFiles {
public void testSources() throws Exception {
File[] listFiles = BEFORE_FOLDER.listFiles();
for (File file : listFiles) {
ParseResult parseResult = parser.parseFile(file.toString());
ParseResult parseResult = parser.parse(file.toString(), true);
StringWriter writer = new StringWriter();
parseResult.print(writer);
compare(file.getName(), readAfter(file), writer.toString());
@@ -71,6 +71,9 @@ public class TestSourceFiles {
private void compare(String name, String expectedFile, String actualFile) {
String[] expectedLines = expectedFile.split("(\\r?\\n)");
String[] actualLines = actualFile.split("(\\r?\\n)");
if (actualLines[0].startsWith("// Generated by delombok at ")) {
actualLines[0] = "";
}
expectedLines = removeBlanks(expectedLines);
actualLines = removeBlanks(actualLines);
int size = Math.min(expectedLines.length, actualLines.length);
@@ -78,7 +81,7 @@ public class TestSourceFiles {
String expected = expectedLines[i];
String actual = actualLines[i];
if (!expected.equals(actual)) {
fail(String.format("Difference in line %s(%d):\n`%s`\n`%s`\n", name, i, expected, actual));
fail(String.format("Difference in line %s(%d):\nExpected `%s`\nGot `%s`\n", name, i, expected, actual));
}
}
if (expectedLines.length > actualLines.length) {
@@ -1,26 +1,20 @@
import lombok.Getter;
/* cmt */
/* cmt2 */
/* cmt3 */
/*bla */
public class CommentsInterspersed {
/** javadoc for field */
private int x;
/* bla2 */
@Getter()
private String test = "foo";
//$NON-NLS-1$
private String test = "foo"; //$NON-NLS-1$
/** Javadoc on method */
public native void gwtTest();
/*-{
public native void gwtTest(); /*-{
javascript;
}-*/
public CommentsInterspersed() {
}
public String getTest() {
return this.test;
return test;
}
}
//haha!
} //haha!
@@ -1,4 +1,4 @@
import lombok.Getter;
import /* cmt */ lombok./* cmt2 */Getter /* cmt3 */ ;
public /*bla */ class CommentsInterspersed {
/** javadoc for field */