diff options
20 files changed, 273 insertions, 173 deletions
diff --git a/src/core/lombok/core/HandlerPriority.java b/src/core/lombok/core/HandlerPriority.java new file mode 100644 index 00000000..174d02d4 --- /dev/null +++ b/src/core/lombok/core/HandlerPriority.java @@ -0,0 +1,18 @@ +package lombok.core; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface HandlerPriority { + int value(); + + /** + * This can be used to differentiate 2 handlers with the same value to be at a different handler priority anyway. + * <strong>DO NOT USE THIS</strong> unless someone has been crowding out the numbers and there's no room left. + */ + int subValue() default 0; +} diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java index 14102ba1..5f360f6f 100644 --- a/src/core/lombok/eclipse/HandlerLibrary.java +++ b/src/core/lombok/eclipse/HandlerLibrary.java @@ -28,17 +28,20 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.WeakHashMap; import lombok.Lombok; import lombok.core.AnnotationValues; -import lombok.core.PrintAST; +import lombok.core.AnnotationValues.AnnotationValueDecodeFail; +import lombok.core.HandlerPriority; import lombok.core.SpiLoadUtil; import lombok.core.TypeLibrary; import lombok.core.TypeResolver; -import lombok.core.AnnotationValues.AnnotationValueDecodeFail; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; @@ -59,13 +62,39 @@ public class HandlerLibrary { private TypeLibrary typeLibrary = new TypeLibrary(); + private static class VisitorContainer { + private final EclipseASTVisitor visitor; + private final long priority; + private final boolean deferUntilPostDiet; + + VisitorContainer(EclipseASTVisitor visitor) { + this.visitor = visitor; + this.deferUntilPostDiet = visitor.getClass().isAnnotationPresent(DeferUntilPostDiet.class); + HandlerPriority hp = visitor.getClass().getAnnotation(HandlerPriority.class); + this.priority = hp == null ? 0L : (((long)hp.value()) << 32) + hp.subValue(); + } + + public boolean deferUntilPostDiet() { + return deferUntilPostDiet; + } + + public long getPriority() { + return priority; + } + } + private static class AnnotationHandlerContainer<T extends Annotation> { - private EclipseAnnotationHandler<T> handler; - private Class<T> annotationClass; + private final EclipseAnnotationHandler<T> handler; + private final Class<T> annotationClass; + private final long priority; + private final boolean deferUntilPostDiet; AnnotationHandlerContainer(EclipseAnnotationHandler<T> handler, Class<T> annotationClass) { this.handler = handler; this.annotationClass = annotationClass; + this.deferUntilPostDiet = handler.getClass().isAnnotationPresent(DeferUntilPostDiet.class); + HandlerPriority hp = handler.getClass().getAnnotation(HandlerPriority.class); + this.priority = hp == null ? 0L : (((long)hp.value()) << 32) + hp.subValue(); } public void handle(org.eclipse.jdt.internal.compiler.ast.Annotation annotation, @@ -81,14 +110,18 @@ public class HandlerLibrary { } public boolean deferUntilPostDiet() { - return handler.getClass().isAnnotationPresent(DeferUntilPostDiet.class); + return deferUntilPostDiet; + } + + public long getPriority() { + return priority; } } private Map<String, AnnotationHandlerContainer<?>> annotationHandlers = new HashMap<String, AnnotationHandlerContainer<?>>(); - private Collection<EclipseASTVisitor> visitorHandlers = new ArrayList<EclipseASTVisitor>(); + private Collection<VisitorContainer> visitorHandlers = new ArrayList<VisitorContainer>(); /** * Creates a new HandlerLibrary. Errors will be reported to the Eclipse Error log. @@ -101,9 +134,24 @@ public class HandlerLibrary { loadAnnotationHandlers(lib); loadVisitorHandlers(lib); + lib.calculatePriorities(); + return lib; } + private SortedSet<Long> priorities; + + public SortedSet<Long> getPriorities() { + return priorities; + } + + private void calculatePriorities() { + SortedSet<Long> set = new TreeSet<Long>(); + for (AnnotationHandlerContainer<?> container : annotationHandlers.values()) set.add(container.getPriority()); + for (VisitorContainer container : visitorHandlers) set.add(container.getPriority()); + this.priorities = Collections.unmodifiableSortedSet(set); + } + /** Uses SPI Discovery to find implementations of {@link EclipseAnnotationHandler}. */ @SuppressWarnings({"rawtypes", "unchecked"}) private static void loadAnnotationHandlers(HandlerLibrary lib) { @@ -131,7 +179,7 @@ public class HandlerLibrary { private static void loadVisitorHandlers(HandlerLibrary lib) { try { for (EclipseASTVisitor visitor : SpiLoadUtil.findServices(EclipseASTVisitor.class, EclipseASTVisitor.class.getClassLoader())) { - lib.visitorHandlers.add(visitor); + lib.visitorHandlers.add(new VisitorContainer(visitor)); } } catch (Throwable t) { throw Lombok.sneakyThrow(t); @@ -170,7 +218,7 @@ public class HandlerLibrary { * @param annotationNode The Lombok AST Node representing the Annotation AST Node. * @param annotation 'node.get()' - convenience parameter. */ - public void handleAnnotation(CompilationUnitDeclaration ast, EclipseNode annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation, boolean skipPrintAst) { + public void handleAnnotation(CompilationUnitDeclaration ast, EclipseNode annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation, long priority) { String pkgName = annotationNode.getPackageDeclaration(); Collection<String> imports = annotationNode.getImportStatements(); @@ -179,11 +227,10 @@ public class HandlerLibrary { if (rawType == null) return; for (String fqn : resolver.findTypeMatches(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName()))) { - boolean isPrintAST = fqn.equals(PrintAST.class.getName()); - if (isPrintAST == skipPrintAst) continue; AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); - if (container == null) continue; + if (priority != container.getPriority()) continue; + if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) { if (needsHandling(annotation)) container.preHandle(annotation, annotationNode); continue; @@ -202,12 +249,16 @@ public class HandlerLibrary { /** * Will call all registered {@link EclipseASTVisitor} instances. */ - public void callASTVisitors(EclipseAST ast) { - for (EclipseASTVisitor visitor : visitorHandlers) try { - ast.traverse(visitor); - } catch (Throwable t) { - error((CompilationUnitDeclaration) ast.top().get(), - String.format("Lombok visitor handler %s failed", visitor.getClass()), t); + public void callASTVisitors(EclipseAST ast, long priority, boolean isCompleteParse) { + for (VisitorContainer container : visitorHandlers) { + if (!isCompleteParse && container.deferUntilPostDiet()) continue; + if (priority != container.getPriority()) continue; + try { + ast.traverse(container.visitor); + } catch (Throwable t) { + error((CompilationUnitDeclaration) ast.top().get(), + String.format("Lombok visitor handler %s failed", container.visitor.getClass()), t); + } } } } diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 89248be1..63783734 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -165,41 +165,42 @@ public class TransformEclipseAST { * then handles any PrintASTs. */ public void go() { - ast.traverse(new AnnotationVisitor(true)); - handlers.callASTVisitors(ast); - ast.traverse(new AnnotationVisitor(false)); + for (Long d : handlers.getPriorities()) { + ast.traverse(new AnnotationVisitor(d)); + handlers.callASTVisitors(ast, d, ast.isCompleteParse()); + } } private static class AnnotationVisitor extends EclipseASTAdapter { - private final boolean skipPrintAst; + private final long priority; - public AnnotationVisitor(boolean skipAllButPrintAST) { - this.skipPrintAst = skipAllButPrintAST; + public AnnotationVisitor(long priority) { + this.priority = priority; } @Override public void visitAnnotationOnField(FieldDeclaration field, EclipseNode annotationNode, Annotation annotation) { CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation, skipPrintAst); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnMethodArgument(Argument arg, AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) { CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation, skipPrintAst); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnLocal(LocalDeclaration local, EclipseNode annotationNode, Annotation annotation) { CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation, skipPrintAst); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnMethod(AbstractMethodDeclaration method, EclipseNode annotationNode, Annotation annotation) { CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation, skipPrintAst); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnType(TypeDeclaration type, EclipseNode annotationNode, Annotation annotation) { CompilationUnitDeclaration top = (CompilationUnitDeclaration) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation, skipPrintAst); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } } } diff --git a/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java b/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java index 30408118..3d30e8fe 100644 --- a/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java +++ b/src/core/lombok/eclipse/handlers/HandleExtensionMethod.java @@ -26,13 +26,17 @@ import java.util.List; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.mangosdk.spi.ProviderFor; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.experimental.ExtensionMethod; // This handler just does some additional error checking; the real work is done in the agent. +@ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(66560) // 2^16 + 2^10; we must run AFTER HandleVal which is at 2^16 public class HandleExtensionMethod extends EclipseAnnotationHandler<ExtensionMethod> { @Override public void handle(AnnotationValues<ExtensionMethod> annotation, Annotation ast, EclipseNode annotationNode) { TypeDeclaration typeDecl = null; diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index e1dd8460..2cdfdd4d 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -25,6 +25,7 @@ import static lombok.eclipse.handlers.EclipseHandlerUtil.*; import lombok.AccessLevel; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; import lombok.experimental.FieldDefaults; @@ -42,6 +43,7 @@ import org.mangosdk.spi.ProviderFor; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> { public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java index a9678f0c..da6fa2a2 100644 --- a/src/core/lombok/eclipse/handlers/HandlePrintAST.java +++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java @@ -30,6 +30,7 @@ import org.mangosdk.spi.ProviderFor; import lombok.Lombok; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.core.PrintAST; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseASTVisitor; @@ -41,6 +42,7 @@ import lombok.eclipse.EclipseNode; */ @ProviderFor(EclipseAnnotationHandler.class) @DeferUntilPostDiet +@HandlerPriority(536870912) // 2^29; this handler is customarily run at the very end. public class HandlePrintAST extends EclipseAnnotationHandler<PrintAST> { public void handle(AnnotationValues<PrintAST> annotation, Annotation ast, EclipseNode annotationNode) { PrintStream stream = System.out; diff --git a/src/core/lombok/eclipse/handlers/HandleVal.java b/src/core/lombok/eclipse/handlers/HandleVal.java index 56b3effd..e7849952 100644 --- a/src/core/lombok/eclipse/handlers/HandleVal.java +++ b/src/core/lombok/eclipse/handlers/HandleVal.java @@ -22,6 +22,7 @@ package lombok.eclipse.handlers; import lombok.val; +import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseASTAdapter; import lombok.eclipse.EclipseASTVisitor; @@ -38,6 +39,7 @@ import org.mangosdk.spi.ProviderFor; */ @ProviderFor(EclipseASTVisitor.class) @DeferUntilPostDiet +@HandlerPriority(65536) // 2^16; resolution needs to work, so if the RHS expression is i.e. a call to a generated getter, we have to run after that getter has been generated. public class HandleVal extends EclipseASTAdapter { @Override public void visitLocal(EclipseNode localNode, LocalDeclaration local) { if (!EclipseHandlerUtil.typeMatches(val.class, localNode, local.type)) return; diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index 9b3edabf..a1eb24ff 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -21,11 +21,14 @@ */ package lombok.eclipse.handlers; +import static lombok.eclipse.handlers.EclipseHandlerUtil.hasAnnotation; import lombok.AccessLevel; -import lombok.experimental.Value; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.NonFinal; +import lombok.experimental.Value; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -36,6 +39,7 @@ import org.mangosdk.spi.ProviderFor; * Handles the {@code lombok.Value} annotation for eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends EclipseAnnotationHandler<Value> { public void handle(AnnotationValues<Value> annotation, Annotation ast, EclipseNode annotationNode) { Value ann = annotation.getInstance(); @@ -52,6 +56,11 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { return; } + // Make class final. + if (!hasAnnotation(NonFinal.class, typeNode)) typeDecl.modifiers |= ClassFileConstants.AccFinal; + + new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); + //Careful: Generate the public static constructor (if there is one) LAST, so that any attempt to //'find callers' on the annotation node will find callers of the constructor, which is by far the //most useful of the many methods built by @Value. This trick won't work for the non-static constructor, @@ -62,7 +71,6 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { new HandleWither().generateWitherForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, ast); } } diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java index f8bd06b2..b3ebc2d8 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Value.java @@ -35,12 +35,14 @@ import java.lang.annotation.Target; * <li>Generates withers for all fields (except final fields that are initialized in the field declaration itself) * <li>Generates a constructor for each argument * <li>Adds {@code private} and {@code final} to each field. + * <li>Makes the class itself final. * </ul> * - * In other words, {@code @Value} is a shorthand for {@code @Getter @Wither @FieldDefaults(makeFinal=true,level=AccessLevel.PRIVATE) @EqualsAndHashCode @ToString @AllArgsConstructor}. + * In other words, {@code @Value} is a shorthand for:<br /> + * {@code final @Getter @Wither @FieldDefaults(makeFinal=true,level=AccessLevel.PRIVATE) @EqualsAndHashCode @ToString @AllArgsConstructor}. * <p> - * If any method to be generated already exists (in name - the return type or parameters are not relevant), then - * that method will not be generated by the Data annotation. + * If any method to be generated already exists (in name and parameter c ount - the return type or parameter types are not relevant), then + * that method will not be generated by the Value annotation. * <p> * The generated constructor will have 1 parameter for each field. The generated toString will print all fields, * while the generated hashCode and equals take into account all non-transient fields.<br> diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java index 9666c9d5..1ce083f3 100644 --- a/src/core/lombok/javac/HandlerLibrary.java +++ b/src/core/lombok/javac/HandlerLibrary.java @@ -25,14 +25,17 @@ import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.SortedSet; +import java.util.TreeSet; import java.util.WeakHashMap; import javax.annotation.processing.Messager; import javax.tools.Diagnostic; -import lombok.core.PrintAST; +import lombok.core.HandlerPriority; import lombok.core.SpiLoadUtil; import lombok.core.TypeLibrary; import lombok.core.TypeResolver; @@ -52,9 +55,8 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; public class HandlerLibrary { private final TypeLibrary typeLibrary = new TypeLibrary(); private final Map<String, AnnotationHandlerContainer<?>> annotationHandlers = new HashMap<String, AnnotationHandlerContainer<?>>(); - private final Collection<JavacASTVisitor> visitorHandlers = new ArrayList<JavacASTVisitor>(); + private final Collection<VisitorContainer> visitorHandlers = new ArrayList<VisitorContainer>(); private final Messager messager; - private int phase = 0; /** * Creates a new HandlerLibrary that will report any problems or errors to the provided messager. @@ -64,22 +66,53 @@ public class HandlerLibrary { this.messager = messager; } + private static class VisitorContainer { + private final JavacASTVisitor visitor; + private final long priority; + + VisitorContainer(JavacASTVisitor visitor) { + this.visitor = visitor; + HandlerPriority hp = visitor.getClass().getAnnotation(HandlerPriority.class); + this.priority = hp == null ? 0L : (((long)hp.value()) << 32) + hp.subValue(); + } + + public long getPriority() { + return priority; + } + } + private static class AnnotationHandlerContainer<T extends Annotation> { - private JavacAnnotationHandler<T> handler; - private Class<T> annotationClass; + private final JavacAnnotationHandler<T> handler; + private final Class<T> annotationClass; + private final long priority; AnnotationHandlerContainer(JavacAnnotationHandler<T> handler, Class<T> annotationClass) { this.handler = handler; this.annotationClass = annotationClass; - } - - public boolean isResolutionBased() { - return handler.getClass().isAnnotationPresent(ResolutionBased.class); + HandlerPriority hp = handler.getClass().getAnnotation(HandlerPriority.class); + this.priority = hp == null ? 0L : (((long)hp.value()) << 32) + hp.subValue(); } public void handle(final JavacNode node) { handler.handle(JavacHandlerUtil.createAnnotation(annotationClass, node), (JCAnnotation)node.get(), node); } + + public long getPriority() { + return priority; + } + } + + private SortedSet<Long> priorities; + + public SortedSet<Long> getPriorities() { + return priorities; + } + + private void calculatePriorities() { + SortedSet<Long> set = new TreeSet<Long>(); + for (AnnotationHandlerContainer<?> container : annotationHandlers.values()) set.add(container.getPriority()); + for (VisitorContainer container : visitorHandlers) set.add(container.getPriority()); + this.priorities = Collections.unmodifiableSortedSet(set); } /** @@ -97,6 +130,8 @@ public class HandlerLibrary { System.err.println("Lombok isn't running due to misconfigured SPI files: " + e); } + library.calculatePriorities(); + return library; } @@ -120,7 +155,7 @@ public class HandlerLibrary { private static void loadVisitorHandlers(HandlerLibrary lib) throws IOException { //No, that seemingly superfluous reference to JavacASTVisitor's classloader is not in fact superfluous! for (JavacASTVisitor visitor : SpiLoadUtil.findServices(JavacASTVisitor.class, JavacASTVisitor.class.getClassLoader())) { - lib.visitorHandlers.add(visitor); + lib.visitorHandlers.add(new VisitorContainer(visitor)); } } @@ -171,24 +206,15 @@ public class HandlerLibrary { * @param node The Lombok AST Node representing the Annotation AST Node. * @param annotation 'node.get()' - convenience parameter. */ - public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation) { + public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation, long priority) { TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); String rawType = annotation.annotationType.toString(); for (String fqn : resolver.findTypeMatches(node, typeLibrary, rawType)) { - boolean isPrintAST = fqn.equals(PrintAST.class.getName()); - if (isPrintAST && phase != 2) continue; - if (!isPrintAST && phase == 2) continue; AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); if (container == null) continue; try { - if (container.isResolutionBased() && phase == 1) { - if (checkAndSetHandled(annotation)) container.handle(node); - } - if (!container.isResolutionBased() && phase == 0) { - if (checkAndSetHandled(annotation)) container.handle(node); - } - if (container.annotationClass == PrintAST.class && phase == 2) { + if (container.getPriority() == priority) { if (checkAndSetHandled(annotation)) container.handle(node); } } catch (AnnotationValueDecodeFail fail) { @@ -204,29 +230,11 @@ public class HandlerLibrary { /** * Will call all registered {@link JavacASTVisitor} instances. */ - public void callASTVisitors(JavacAST ast) { - for (JavacASTVisitor visitor : visitorHandlers) try { - boolean isResolutionBased = visitor.getClass().isAnnotationPresent(ResolutionBased.class); - if (!isResolutionBased && phase == 0) ast.traverse(visitor); - if (isResolutionBased && phase == 1) ast.traverse(visitor); + public void callASTVisitors(JavacAST ast, long priority) { + for (VisitorContainer container : visitorHandlers) try { + if (container.getPriority() == priority) ast.traverse(container.visitor); } catch (Throwable t) { - javacError(String.format("Lombok visitor handler %s failed", visitor.getClass()), t); + javacError(String.format("Lombok visitor handler %s failed", container.visitor.getClass()), t); } } - - /** - * Lombok does not currently support triggering annotations in a specified order; the order is essentially - * random right now. As a temporary hack we've identified 3 important phases. - */ - public void setPreResolutionPhase() { - phase = 0; - } - - public void setPostResolutionPhase() { - phase = 1; - } - - public void setPrintASTPhase() { - phase = 2; - } } diff --git a/src/core/lombok/javac/JavacTransformer.java b/src/core/lombok/javac/JavacTransformer.java index 3afdee0a..9cac787c 100644 --- a/src/core/lombok/javac/JavacTransformer.java +++ b/src/core/lombok/javac/JavacTransformer.java @@ -22,10 +22,10 @@ package lombok.javac; import java.util.ArrayList; +import java.util.SortedSet; import javax.annotation.processing.Messager; - import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; @@ -43,7 +43,11 @@ public class JavacTransformer { this.handlers = HandlerLibrary.load(messager); } - public void transform(boolean postResolution, Context context, java.util.List<JCCompilationUnit> compilationUnitsRaw) { + public SortedSet<Long> getPriorities() { + return handlers.getPriorities(); + } + + public void transform(long priority, Context context, java.util.List<JCCompilationUnit> compilationUnitsRaw) { List<JCCompilationUnit> compilationUnits; if (compilationUnitsRaw instanceof List<?>) { compilationUnits = (List<JCCompilationUnit>)compilationUnitsRaw; @@ -58,54 +62,44 @@ public class JavacTransformer { for (JCCompilationUnit unit : compilationUnits) asts.add(new JavacAST(messager, context, unit)); - if (!postResolution) { - handlers.setPreResolutionPhase(); - for (JavacAST ast : asts) { - ast.traverse(new AnnotationVisitor()); - handlers.callASTVisitors(ast); - } - } - - if (postResolution) { - handlers.setPostResolutionPhase(); - for (JavacAST ast : asts) { - ast.traverse(new AnnotationVisitor()); - handlers.callASTVisitors(ast); - } - - handlers.setPrintASTPhase(); - for (JavacAST ast : asts) { - ast.traverse(new AnnotationVisitor()); - } + for (JavacAST ast : asts) { + ast.traverse(new AnnotationVisitor(priority)); + handlers.callASTVisitors(ast, priority); } for (JavacAST ast : asts) if (ast.isChanged()) LombokOptions.markChanged(context, (JCCompilationUnit) ast.top().get()); } private class AnnotationVisitor extends JavacASTAdapter { + private final long priority; + + AnnotationVisitor(long priority) { + this.priority = priority; + } + @Override public void visitAnnotationOnType(JCClassDecl type, JavacNode annotationNode, JCAnnotation annotation) { JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnField(JCVariableDecl field, JavacNode annotationNode, JCAnnotation annotation) { JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnMethod(JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation) { JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnMethodArgument(JCVariableDecl argument, JCMethodDecl method, JavacNode annotationNode, JCAnnotation annotation) { JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } @Override public void visitAnnotationOnLocal(JCVariableDecl local, JavacNode annotationNode, JCAnnotation annotation) { JCCompilationUnit top = (JCCompilationUnit) annotationNode.top().get(); - handlers.handleAnnotation(top, annotationNode, annotation); + handlers.handleAnnotation(top, annotationNode, annotation, priority); } } } diff --git a/src/core/lombok/javac/ResolutionBased.java b/src/core/lombok/javac/ResolutionBased.java deleted file mode 100644 index 556e3736..00000000 --- a/src/core/lombok/javac/ResolutionBased.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (C) 2012 The Project Lombok Authors. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package lombok.javac; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -/** - * Marker to indicate a handler is to be called after all the non-resolution based visitors. - * NB: Temporary solution - will be rewritten to a different style altogether in a future release. - */ -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -public @interface ResolutionBased { -} diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index d05a3bdf..190a369b 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 The Project Lombok Authors. + * Copyright (C) 2009-2012 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -30,8 +30,10 @@ import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; import java.util.IdentityHashMap; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.SortedSet; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Messager; @@ -80,6 +82,14 @@ public class Processor extends AbstractProcessor { placePostCompileAndDontMakeForceRoundDummiesHook(); transformer = new JavacTransformer(procEnv.getMessager()); trees = Trees.instance(procEnv); + SortedSet<Long> p = transformer.getPriorities(); + if (p.isEmpty()) { + this.priorityLevels = new long[] {0L}; + } else { + this.priorityLevels = new long[p.size()]; + int i = 0; + for (Long prio : p) this.priorityLevels[i++] = prio; + } } private void placePostCompileAndDontMakeForceRoundDummiesHook() { @@ -204,47 +214,72 @@ public class Processor extends AbstractProcessor { } } - private final IdentityHashMap<JCCompilationUnit, Void> rootsAtPhase0 = new IdentityHashMap<JCCompilationUnit, Void>(); - private final IdentityHashMap<JCCompilationUnit, Void> rootsAtPhase1 = new IdentityHashMap<JCCompilationUnit, Void>(); - private int dummyCount = 0; + private final IdentityHashMap<JCCompilationUnit, Long> roots = new IdentityHashMap<JCCompilationUnit, Long>(); + private long[] priorityLevels; /** {@inheritDoc} */ @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { if (roundEnv.processingOver()) return false; - if (!rootsAtPhase0.isEmpty()) { - ArrayList<JCCompilationUnit> cus = new ArrayList<JCCompilationUnit>(rootsAtPhase0.keySet()); - transformer.transform(true, processingEnv.getContext(), cus); - rootsAtPhase1.putAll(rootsAtPhase0); - rootsAtPhase0.clear(); - } + // We have: A sorted set of all priority levels: 'priorityLevels' + + // Step 1: Take all CUs which aren't already in the map. Give them the first priority level. for (Element element : roundEnv.getRootElements()) { JCCompilationUnit unit = toUnit(element); - if (unit != null) { - if (!rootsAtPhase1.containsKey(unit)) rootsAtPhase0.put(unit, null); + if (unit == null) continue; + if (roots.containsKey(unit)) continue; + roots.put(unit, priorityLevels[0]); + } + + // Step 2: For all CUs (in the map, not the roundEnv!), run them across all handlers at their current prio level. + + for (long prio : priorityLevels) { + List<JCCompilationUnit> cusForThisRound = new ArrayList<JCCompilationUnit>(); + for (Map.Entry<JCCompilationUnit, Long> entry : roots.entrySet()) { + Long prioOfCu = entry.getValue(); + if (prioOfCu == null || prioOfCu != prio) continue; + cusForThisRound.add(entry.getKey()); } + transformer.transform(prio, processingEnv.getContext(), cusForThisRound); } - if (!rootsAtPhase0.isEmpty()) { - ArrayList<JCCompilationUnit> cus = new ArrayList<JCCompilationUnit>(rootsAtPhase0.keySet()); - transformer.transform(false, processingEnv.getContext(), cus); - JavacFiler filer = (JavacFiler) processingEnv.getFiler(); - if (!filer.newFiles()) { - try { - JavaFileObject dummy = filer.createSourceFile("lombok.dummy.ForceNewRound" + (dummyCount++)); - Writer w = dummy.openWriter(); - w.close(); - } catch (Exception e) { - processingEnv.getMessager().printMessage(Kind.WARNING, - "Can't force a new processing round. Lombok features that require resolution won't work."); + // Step 3: Push up all CUs to the next level. Set level to null if there is no next level. + + boolean nextRoundNeeded = false; + for (int i = priorityLevels.length - 1; i >= 0; i--) { + Long curLevel = priorityLevels[i]; + Long nextLevel = (i == priorityLevels.length - 1) ? null : priorityLevels[i + 1]; + for (Map.Entry<JCCompilationUnit, Long> entry : roots.entrySet()) { + if (curLevel.equals(entry.getValue())) { + entry.setValue(nextLevel); + if (nextLevel != null) nextRoundNeeded = true; } } } + // Step 4: If ALL values are null, quit. Else, force new round. + + if (nextRoundNeeded) forceNewRound((JavacFiler) processingEnv.getFiler()); + return false; } + private int dummyCount = 0; + private void forceNewRound(JavacFiler filer) { + if (!filer.newFiles()) { + try { + JavaFileObject dummy = filer.createSourceFile("lombok.dummy.ForceNewRound" + (dummyCount++)); + Writer w = dummy.openWriter(); + w.close(); + } catch (Exception e) { + e.printStackTrace(); + processingEnv.getMessager().printMessage(Kind.WARNING, + "Can't force a new processing round. Lombok won't work."); + } + } + } + private JCCompilationUnit toUnit(Element element) { TreePath path = trees == null ? null : trees.getPath(element); if (path == null) return null; diff --git a/src/core/lombok/javac/handlers/HandleDelegate.java b/src/core/lombok/javac/handlers/HandleDelegate.java index 50a2f1bb..b693a2a3 100644 --- a/src/core/lombok/javac/handlers/HandleDelegate.java +++ b/src/core/lombok/javac/handlers/HandleDelegate.java @@ -43,11 +43,11 @@ import javax.lang.model.type.TypeMirror; import lombok.Delegate; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.javac.FindTypeVarScanner; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacResolution; -import lombok.javac.ResolutionBased; import lombok.javac.JavacResolution.TypeNotConvertibleException; import org.mangosdk.spi.ProviderFor; @@ -75,7 +75,7 @@ import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; @ProviderFor(JavacAnnotationHandler.class) -@ResolutionBased +@HandlerPriority(65536) //2^16; to make sure that we also delegate generated methods. public class HandleDelegate extends JavacAnnotationHandler<Delegate> { private static final List<String> METHODS_IN_OBJECT = Collections.unmodifiableList(Arrays.asList( "hashCode()", diff --git a/src/core/lombok/javac/handlers/HandleExtensionMethod.java b/src/core/lombok/javac/handlers/HandleExtensionMethod.java index 92d7c0e4..68df38ef 100644 --- a/src/core/lombok/javac/handlers/HandleExtensionMethod.java +++ b/src/core/lombok/javac/handlers/HandleExtensionMethod.java @@ -31,10 +31,10 @@ import java.util.List; import javax.lang.model.element.ElementKind; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.experimental.ExtensionMethod; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; -import lombok.javac.ResolutionBased; import org.mangosdk.spi.ProviderFor; @@ -60,7 +60,7 @@ import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; * Handles the {@link ExtensionMethod} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) -@ResolutionBased +@HandlerPriority(66560) // 2^16 + 2^10; we must run AFTER HandleVal which is at 2^16 public class HandleExtensionMethod extends JavacAnnotationHandler<ExtensionMethod> { @Override public void handle(final AnnotationValues<ExtensionMethod> annotation, final JCAnnotation source, final JavacNode annotationNode) { @@ -83,9 +83,6 @@ public class HandleExtensionMethod extends JavacAnnotationHandler<ExtensionMetho final List<Extension> extensions = getExtensions(annotationNode, extensionProviders); if (extensions.isEmpty()) return; - // call HandleVal explicitly to ensure val gets handled before @ExtensionMethdod gets handled. - // TODO maybe we should prioritize lombok handler - annotationNode.traverse(new HandleVal()); new ExtensionMethodReplaceVisitor(annotationNode, extensions, suppressBaseMethods).replace(); annotationNode.rebuild(); diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java index a2dfb7ce..c0829172 100644 --- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java @@ -25,6 +25,7 @@ import static lombok.javac.handlers.JavacHandlerUtil.*; import lombok.AccessLevel; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; import lombok.experimental.PackagePrivate; @@ -43,6 +44,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { public boolean generateFieldDefaultsForType(JavacNode typeNode, JavacNode errorNode, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index 4208f3c6..5ccc82d4 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -44,7 +44,6 @@ import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.Name; public class HandleLog { - private HandleLog() { throw new UnsupportedOperationException(); } diff --git a/src/core/lombok/javac/handlers/HandlePrintAST.java b/src/core/lombok/javac/handlers/HandlePrintAST.java index 27aa02ad..7b6d942c 100644 --- a/src/core/lombok/javac/handlers/HandlePrintAST.java +++ b/src/core/lombok/javac/handlers/HandlePrintAST.java @@ -31,6 +31,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; import lombok.Lombok; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; import lombok.core.PrintAST; import lombok.javac.JavacASTVisitor; import lombok.javac.JavacAnnotationHandler; @@ -40,6 +41,7 @@ import lombok.javac.JavacNode; * Handles the {@code lombok.core.PrintAST} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(536870912) // 2^29; this handler is customarily run at the very end. public class HandlePrintAST extends JavacAnnotationHandler<PrintAST> { @Override public void handle(AnnotationValues<PrintAST> annotation, JCAnnotation ast, JavacNode annotationNode) { PrintStream stream = System.out; diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 52d2ed13..0ab9d783 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -24,11 +24,11 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import lombok.val; +import lombok.core.HandlerPriority; import lombok.javac.JavacASTAdapter; import lombok.javac.JavacASTVisitor; import lombok.javac.JavacNode; import lombok.javac.JavacResolution; -import lombok.javac.ResolutionBased; import org.mangosdk.spi.ProviderFor; @@ -44,7 +44,7 @@ import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.util.List; @ProviderFor(JavacASTVisitor.class) -@ResolutionBased +@HandlerPriority(65536) // 2^16; resolution needs to work, so if the RHS expression is i.e. a call to a generated getter, we have to run after that getter has been generated. public class HandleVal extends JavacASTAdapter { @Override public void visitLocal(JavacNode localNode, JCVariableDecl local) { if (local.vartype == null || (!local.vartype.toString().equals("val") && !local.vartype.toString().equals("lombok.val"))) return; diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index fac017a8..b52bad7c 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -24,18 +24,23 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import lombok.AccessLevel; import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.experimental.NonFinal; import lombok.experimental.Value; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; +import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; /** * Handles the {@code lombok.Value} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends JavacAnnotationHandler<Value> { @Override public void handle(AnnotationValues<Value> annotation, JCAnnotation ast, JavacNode annotationNode) { deleteAnnotationIfNeccessary(annotationNode, Value.class); @@ -49,12 +54,16 @@ public class HandleValue extends JavacAnnotationHandler<Value> { String staticConstructorName = annotation.getInstance().staticConstructor(); + if (!hasAnnotationAndDeleteIfNeccessary(NonFinal.class, typeNode)) { + ((JCClassDecl) typeNode.get()).mods.flags |= Flags.FINAL; + } + new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); + // TODO move this to the end OR move it to the top in eclipse. new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleWither().generateWitherForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); } } |