diff options
author | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-07-22 23:23:46 +0200 |
---|---|---|
committer | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-07-22 23:23:46 +0200 |
commit | 45697b50816df79475a8bb69dc89ff68747fbfe6 (patch) | |
tree | 25cb023eec1f74baf5063cc5a58a5351ee43d6f0 /src/core | |
parent | 4c03e3d220900431085897878d4888bf530b31ec (diff) | |
parent | deed98be16e5099af52d951fc611f86a82a42858 (diff) | |
download | lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.tar.gz lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.tar.bz2 lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.zip |
Merge branch 'master' into jdk8. Also added some major fixes whilst merging.
Conflicts:
src/core/lombok/javac/handlers/JavacHandlerUtil.java
src/utils/lombok/javac/CommentCatcher.java
src/utils/lombok/javac/Javac.java
Diffstat (limited to 'src/core')
69 files changed, 2750 insertions, 545 deletions
diff --git a/src/core/lombok/Data.java b/src/core/lombok/Data.java index ee6f2fcb..bbc8d920 100644 --- a/src/core/lombok/Data.java +++ b/src/core/lombok/Data.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -39,7 +39,7 @@ import java.lang.annotation.Target; * @see RequiredArgsConstructor * @see ToString * @see EqualsAndHashCode - * @see lombok.experimental.Value + * @see lombok.Value */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/NonNull.java b/src/core/lombok/NonNull.java index 5f5d8ed2..96813170 100644 --- a/src/core/lombok/NonNull.java +++ b/src/core/lombok/NonNull.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -28,12 +28,14 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * Lombok is smart enough to translate any annotation named {@code @NonNull} in any casing and - * with any package name to the return type of generated getters and the parameter of generated setters and constructors, - * as well as generate the appropriate null checks in the setter and constructor. - * - * You can use this annotation for the purpose, though you can also use JSR305's annotation, findbugs's, pmd's, or IDEA's, or just - * about anyone elses. As long as it is named {@code @NonNull}. + * If put on a parameter, lombok will insert a null-check at the start of the method / constructor's body, throwing a + * {@code NullPointerException} with the parameter's name as message. If put on a field, any generated method assigning + * a value to this field will also produce these nullchecks. + * <p> + * Note that any annotation named {@code NonNull} with any casing and any package will result in nullchecks produced for + * generated methods (and the annotation will be copied to the getter return type and any parameters of generated methods), + * but <em>only</em> this annotation, if present on a parameter, will result in a null check inserted into your otherwise + * handwritten method. * * WARNING: If the java community ever does decide on supporting a single {@code @NonNull} annotation (for example via JSR305), then * this annotation will <strong>be deleted</strong> from the lombok package. If the need to update an import statement scares diff --git a/src/core/lombok/Value.java b/src/core/lombok/Value.java new file mode 100644 index 00000000..2cffe15b --- /dev/null +++ b/src/core/lombok/Value.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2012-2013 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; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Generates a lot of code which fits with a class that is a representation of an immutable entity. + * <p> + * Equivalent to {@code @Getter @FieldDefaults(makeFinal=true, level=AccessLevel.PRIVATE) @RequiredArgsConstructor @ToString @EqualsAndHashCode}. + * <p> + * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Value.html">the project lombok features page for @Value</a>. + * + * @see lombok.Getter + * @see lombok.experimental.FieldDefaults + * @see lombok.RequiredArgsConstructor + * @see lombok.ToString + * @see lombok.EqualsAndHashCode + * @see lombok.Data + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.SOURCE) +public @interface Value { + /** + * If you specify a static constructor name, then the generated constructor will be private, and + * instead a static factory method is created that other classes can use to create instances. + * We suggest the name: "of", like so: + * + * <pre> + * public @Data(staticConstructor = "of") class Point { final int x, y; } + * </pre> + * + * Default: No static constructor, instead the normal constructor is public. + */ + String staticConstructor() default ""; +} diff --git a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java index a3f18b40..751e691f 100644 --- a/src/core/lombok/bytecode/PreventNullAnalysisRemover.java +++ b/src/core/lombok/bytecode/PreventNullAnalysisRemover.java @@ -44,12 +44,12 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { byte[] fixedByteCode = fixJSRInlining(original); ClassReader reader = new ClassReader(fixedByteCode); - ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new FixedClassWriter(reader, 0); final AtomicBoolean changesMade = new AtomicBoolean(); - class PreventNullanalysisVisitor extends MethodVisitor { - PreventNullanalysisVisitor(MethodVisitor mv) { + class PreventNullAnalysisVisitor extends MethodVisitor { + PreventNullAnalysisVisitor(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @@ -61,6 +61,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { if (hit && !"(Ljava/lang/Object;)Ljava/lang/Object;".equals(desc)) hit = false; if (hit) { changesMade.set(true); + if (System.getProperty("lombok.debugAsmOnly", null) != null) super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470! } else { super.visitMethodInsn(opcode, owner, name, desc); } @@ -69,7 +70,7 @@ public class PreventNullAnalysisRemover implements PostCompilerTransformation { reader.accept(new ClassVisitor(Opcodes.ASM4, writer) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - return new PreventNullanalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions)); + return new PreventNullAnalysisVisitor(super.visitMethod(access, name, desc, signature, exceptions)); } }, 0); return changesMade.get() ? writer.toByteArray() : null; diff --git a/src/core/lombok/bytecode/SneakyThrowsRemover.java b/src/core/lombok/bytecode/SneakyThrowsRemover.java index 8143d860..914c313a 100644 --- a/src/core/lombok/bytecode/SneakyThrowsRemover.java +++ b/src/core/lombok/bytecode/SneakyThrowsRemover.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -32,19 +32,21 @@ import org.mangosdk.spi.ProviderFor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @ProviderFor(PostCompilerTransformation.class) public class SneakyThrowsRemover implements PostCompilerTransformation { - @Override public byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) { + @Override public byte[] applyTransformations(byte[] original, String fileName, final DiagnosticsReceiver diagnostics) { if (!new ClassFileMetaData(original).usesMethod("lombok/Lombok", "sneakyThrow")) return null; byte[] fixedByteCode = fixJSRInlining(original); ClassReader reader = new ClassReader(fixedByteCode); - ClassWriter writer = new FixedClassWriter(reader, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES); + ClassWriter writer = new ClassWriter(reader, 0); final AtomicBoolean changesMade = new AtomicBoolean(); @@ -53,6 +55,8 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { super(Opcodes.ASM4, mv); } + private boolean methodInsnQueued = false; + @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { if ( opcode == Opcodes.INVOKESTATIC && @@ -60,12 +64,117 @@ public class SneakyThrowsRemover implements PostCompilerTransformation { "lombok/Lombok".equals(owner) && "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;".equals(desc)) { - changesMade.set(true); - super.visitInsn(Opcodes.ATHROW); + if (System.getProperty("lombok.debugAsmOnly", null) != null) { + super.visitMethodInsn(opcode, owner, name, desc); // DEBUG for issue 470! + } else { + methodInsnQueued = true; + } } else { super.visitMethodInsn(opcode, owner, name, desc); } } + + private void handleQueue() { + if (!methodInsnQueued) return; + super.visitMethodInsn(Opcodes.INVOKESTATIC, "lombok/Lombok", "sneakyThrow", "(Ljava/lang/Throwable;)Ljava/lang/RuntimeException;"); + methodInsnQueued = false; + diagnostics.addWarning("Proper usage is: throw lombok.Lombok.sneakyThrow(someException);. You did not 'throw' it. Because of this, the call to sneakyThrow " + + "remains in your classfile and you will need lombok.jar on the classpath at runtime."); + } + + @Override public void visitInsn(int arg0) { + if (methodInsnQueued && arg0 == Opcodes.ATHROW) { + changesMade.set(true); + // As expected, the required ATHROW. We can now safely 'eat' the previous call. + methodInsnQueued = false; + } + handleQueue(); + super.visitInsn(arg0); + } + @Override public void visitFrame(int arg0, int arg1, Object[] arg2, int arg3, Object[] arg4) { + handleQueue(); + super.visitFrame(arg0, arg1, arg2, arg3, arg4); + } + + @Override public void visitIincInsn(int arg0, int arg1) { + handleQueue(); + super.visitIincInsn(arg0, arg1); + } + + @Override public void visitFieldInsn(int arg0, String arg1, String arg2, String arg3) { + handleQueue(); + super.visitFieldInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitIntInsn(int arg0, int arg1) { + handleQueue(); + super.visitIntInsn(arg0, arg1); + } + + @Override public void visitEnd() { + handleQueue(); + super.visitEnd(); + } + + @Override public void visitInvokeDynamicInsn(String arg0, String arg1, Handle arg2, Object... arg3) { + handleQueue(); + super.visitInvokeDynamicInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitLabel(Label arg0) { + handleQueue(); + super.visitLabel(arg0); + } + + @Override public void visitJumpInsn(int arg0, Label arg1) { + handleQueue(); + super.visitJumpInsn(arg0, arg1); + } + + @Override public void visitLdcInsn(Object arg0) { + handleQueue(); + super.visitLdcInsn(arg0); + } + + @Override public void visitLocalVariable(String arg0, String arg1, String arg2, Label arg3, Label arg4, int arg5) { + handleQueue(); + super.visitLocalVariable(arg0, arg1, arg2, arg3, arg4, arg5); + } + + @Override public void visitMaxs(int arg0, int arg1) { + handleQueue(); + super.visitMaxs(arg0, arg1); + } + + @Override public void visitLookupSwitchInsn(Label arg0, int[] arg1, Label[] arg2) { + handleQueue(); + super.visitLookupSwitchInsn(arg0, arg1, arg2); + } + + @Override public void visitMultiANewArrayInsn(String arg0, int arg1) { + handleQueue(); + super.visitMultiANewArrayInsn(arg0, arg1); + } + + @Override public void visitVarInsn(int arg0, int arg1) { + handleQueue(); + super.visitVarInsn(arg0, arg1); + } + + @Override public void visitTryCatchBlock(Label arg0, Label arg1, Label arg2, String arg3) { + handleQueue(); + super.visitTryCatchBlock(arg0, arg1, arg2, arg3); + } + + @Override public void visitTableSwitchInsn(int arg0, int arg1, Label arg2, Label... arg3) { + handleQueue(); + super.visitTableSwitchInsn(arg0, arg1, arg2, arg3); + } + + @Override public void visitTypeInsn(int arg0, String arg1) { + handleQueue(); + super.visitTypeInsn(arg0, arg1); + } } reader.accept(new ClassVisitor(Opcodes.ASM4, writer) { diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index 68f36412..6fed0252 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009 The Project Lombok Authors. + * Copyright (C) 2009-2013 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,7 +30,6 @@ import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; @@ -54,15 +53,15 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, private L top; private final String fileName; private final String packageDeclaration; - private final Collection<String> imports; - Map<N, Void> identityDetector = new IdentityHashMap<N, Void>(); + private final ImportList imports; + Map<N, N> identityDetector = new IdentityHashMap<N, N>(); private Map<N, L> nodeMap = new IdentityHashMap<N, L>(); private boolean changed = false; - protected AST(String fileName, String packageDeclaration, Collection<String> imports) { + protected AST(String fileName, String packageDeclaration, ImportList imports) { this.fileName = fileName == null ? "(unknown).java" : fileName; this.packageDeclaration = packageDeclaration; - this.imports = Collections.unmodifiableCollection(new ArrayList<String>(imports)); + this.imports = imports; } public void setChanged() { @@ -96,7 +95,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, * * Example: "java.util.IOException". */ - public final Collection<String> getImportStatements() { + public final ImportList getImportList() { return imports; } @@ -106,7 +105,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, */ protected L putInMap(L node) { nodeMap.put(node.get(), node); - identityDetector.put(node.get(), null); + identityDetector.put(node.get(), node.get()); return node; } @@ -118,7 +117,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, /** Clears the registry that avoids endless loops, and empties the node map. The existing node map * object is left untouched, and instead a new map is created. */ protected void clearState() { - identityDetector = new IdentityHashMap<N, Void>(); + identityDetector = new IdentityHashMap<N, N>(); nodeMap = new IdentityHashMap<N, L>(); } @@ -128,9 +127,7 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, * case you should do nothing lest the AST build process loops endlessly. */ protected boolean setAndGetAsHandled(N node) { - if (identityDetector.containsKey(node)) return true; - identityDetector.put(node, null); - return false; + return identityDetector.put(node, node) != null; } public String getFileName() { @@ -147,6 +144,24 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, return nodeMap.get(node); } + /** + * Returns the JLS spec version that the compiler uses to parse and compile this AST. + * For example, if -source 1.6 is on the command line, this will return {@code 6}. + */ + public int getSourceVersion() { + return 6; + } + + /** + * Returns the latest version of the java language specification supported by the host compiler. + * For example, if compiling with javac v1.7, this returns {@code 7}. + * + * NB: Even if -source (lower than maximum) is specified, this method still returns the maximum supported number. + */ + public int getLatestJavaSpecSupported() { + return 6; + } + @SuppressWarnings({"unchecked", "rawtypes"}) L replaceNewWithExistingOld(Map<N, L> oldNodes, L newNode) { L oldNode = oldNodes.get(newNode.get()); @@ -159,8 +174,8 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, oldChild.parent = targetNode; } - targetNode.children.clear(); - ((List)targetNode.children).addAll(children); + targetNode.children = LombokImmutableList.copyOf(children); + return targetNode; } @@ -217,14 +232,12 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, } } - for (Class<?> statementType : getStatementTypes()) { - if (statementType.isAssignableFrom(t)) { - f.setAccessible(true); - fields.add(new FieldAccess(f, dim)); - break; - } + if (shouldDrill(c, t, f.getName())) { + f.setAccessible(true); + fields.add(new FieldAccess(f, dim)); } } + getFields(c.getSuperclass(), fields); } @@ -241,6 +254,14 @@ public abstract class AST<A extends AST<A, L, N>, L extends LombokNode<A, L, N>, * though some platforms (such as Eclipse) group these under one common supertype. */ protected abstract Collection<Class<? extends N>> getStatementTypes(); + protected boolean shouldDrill(Class<?> parentType, Class<?> childType, String fieldName) { + for (Class<?> statementType : getStatementTypes()) { + if (statementType.isAssignableFrom(childType)) return true; + } + + return false; + } + /** * buildTree implementation that uses reflection to find all child nodes by way of inspecting * the fields. */ diff --git a/src/core/lombok/core/AnnotationProcessor.java b/src/core/lombok/core/AnnotationProcessor.java index 8c5f7fba..e9cf3891 100644 --- a/src/core/lombok/core/AnnotationProcessor.java +++ b/src/core/lombok/core/AnnotationProcessor.java @@ -164,12 +164,9 @@ public class AnnotationProcessor extends AbstractProcessor { } } - boolean handled = false; - for (ProcessorDescriptor proc : active) { - handled |= proc.process(annotations, roundEnv); - } + for (ProcessorDescriptor proc : active) proc.process(annotations, roundEnv); - return handled; + return false; } /** diff --git a/src/core/lombok/core/AnnotationValues.java b/src/core/lombok/core/AnnotationValues.java index df056dd4..d04797cb 100644 --- a/src/core/lombok/core/AnnotationValues.java +++ b/src/core/lombok/core/AnnotationValues.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -435,23 +435,16 @@ public class AnnotationValues<A extends Annotation> { } /* 2. Walk through non-star imports and search for a match. */ { - for (String im : ast == null ? Collections.<String>emptyList() : ast.getImportStatements()) { - if (im.endsWith(".*")) continue; - int idx = im.lastIndexOf('.'); - String simple = idx == -1 ? im : im.substring(idx+1); - if (simple.equals(prefix)) { - return im + typeName.substring(prefix.length()); - } + if (prefix.equals(typeName)) { + String fqn = ast.getImportList().getFullyQualifiedNameForSimpleName(typeName); + if (fqn != null) return fqn; } } /* 3. Walk through star imports and, if they start with "java.", use Class.forName based resolution. */ { - List<String> imports = ast == null ? Collections.<String>emptyList() : new ArrayList<String>(ast.getImportStatements()); - imports.add("java.lang.*"); - for (String im : imports) { - if (!im.endsWith(".*") || !im.startsWith("java.")) continue; + for (String potential : ast.getImportList().applyNameToStarImports("java", typeName)) { try { - Class<?> c = Class.forName(im.substring(0, im.length()-1) + typeName); + Class<?> c = Class.forName(potential); if (c != null) return c.getName(); } catch (Throwable t) { //Class.forName failed for whatever reason - it most likely does not exist, continue. diff --git a/src/core/lombok/core/ImportList.java b/src/core/lombok/core/ImportList.java new file mode 100644 index 00000000..95e266c4 --- /dev/null +++ b/src/core/lombok/core/ImportList.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2013 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.core; + +import java.util.Collection; + +public interface ImportList { + /** + * If there is an explicit import of the stated unqualified type name, return that. Otherwise, return null. + */ + String getFullyQualifiedNameForSimpleName(String unqualified); + + /** + * Returns true if the package name is explicitly star-imported, OR the packageName refers to this source file's own package name, OR packageName is 'java.lang'. + */ + boolean hasStarImport(String packageName); + + /** + * Takes all explicit non-static star imports whose first element is equal to {@code startsWith}, replaces the star with {@code unqualified}, and returns these. + */ + Collection<String> applyNameToStarImports(String startsWith, String unqualified); + + String applyUnqualifiedNameToPackage(String unqualified); +} diff --git a/src/core/lombok/core/LombokInternalAliasing.java b/src/core/lombok/core/LombokInternalAliasing.java new file mode 100644 index 00000000..4fd7b29d --- /dev/null +++ b/src/core/lombok/core/LombokInternalAliasing.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2013 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.core; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class LombokInternalAliasing { + public static final Map<String, String> IMPLIED_EXTRA_STAR_IMPORTS; + public static final Map<String, String> ALIASES; + + /** + * Provide a fully qualified name (FQN), and the canonical version of this is returned. + */ + public static String processAliases(String in) { + if (in == null) return null; + for (Map.Entry<String, String> e : ALIASES.entrySet()) { + if (in.equals(e.getKey())) return e.getValue(); + } + return in; + } + + static { + Map<String, String> m = new HashMap<String, String>(); + m.put("lombok.experimental", "lombok"); + IMPLIED_EXTRA_STAR_IMPORTS = Collections.unmodifiableMap(m); + + m = new HashMap<String, String>(); + m.put("lombok.experimental.Value", "lombok.Value"); + ALIASES = Collections.unmodifiableMap(m); + } +} diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index eac59806..07c62151 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -42,7 +42,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, protected final A ast; protected final Kind kind; protected final N node; - protected final List<L> children; + protected LombokImmutableList<L> children; protected L parent; /** structurally significant are those nodes that can be annotated in java 1.6 or are method-like toplevels, @@ -62,7 +62,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, this.ast = ast; this.kind = kind; this.node = node; - this.children = children == null ? new ArrayList<L>() : children; + this.children = children != null ? LombokImmutableList.copyOf(children) : LombokImmutableList.<L>of(); for (L child : this.children) { child.parent = (L) this; if (!child.isStructurallySignificant) @@ -91,12 +91,12 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, } /** - * Convenient shortcut to the owning ast object's {@code getImportStatements} method. + * Convenient shortcut to the owning ast object's {@code getImportList} method. * - * @see AST#getImportStatements() + * @see AST#getImportList() */ - public Collection<String> getImportStatements() { - return ast.getImportStatements(); + public ImportList getImportList() { + return ast.getImportList(); } /** @@ -120,35 +120,6 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, return node; } - /** - * Replaces the AST node represented by this node object with the provided node. This node must - * have a parent, obviously, for this to work. - * - * Also affects the underlying (Eclipse/javac) AST. - */ - @SuppressWarnings({"rawtypes", "unchecked"}) - public L replaceWith(N newN, Kind newNodeKind) { - ast.setChanged(); - L newNode = ast.buildTree(newN, newNodeKind); - newNode.parent = parent; - for (int i = 0; i < parent.children.size(); i++) { - if (parent.children.get(i) == this) ((List)parent.children).set(i, newNode); - } - - parent.replaceChildNode(get(), newN); - return newNode; - } - - /** - * Replaces the stated node with a new one. The old node must be a direct child of this node. - * - * Also affects the underlying (Eclipse/javac) AST. - */ - public void replaceChildNode(N oldN, N newN) { - ast.setChanged(); - ast.replaceStatementInNode(get(), oldN, newN); - } - public Kind getKind() { return kind; } @@ -204,12 +175,27 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, /** * Returns all children nodes. + */ + public LombokImmutableList<L> down() { + return children; + } + + /** + * Convenient shortcut to the owning ast object's getLatestJavaSpecSupported method. + * + * @see AST#getLatestJavaSpecSupported() + */ + public int getLatestJavaSpecSupported() { + return ast.getLatestJavaSpecSupported(); + } + + /** + * Convenient shortcut to the owning ast object's getSourceVersion method. * - * A copy is created, so changing the list has no effect. Also, while iterating through this list, - * you may add, remove, or replace children without causing {@code ConcurrentModificationException}s. + * @see AST#getSourceVersion() */ - public Collection<L> down() { - return new ArrayList<L>(children); + public int getSourceVersion() { + return ast.getSourceVersion(); } /** @@ -235,13 +221,13 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, * * Does not change the underlying (javac/Eclipse) AST, only the wrapped view. */ - @SuppressWarnings({"rawtypes", "unchecked"}) + @SuppressWarnings({"unchecked"}) public L add(N newChild, Kind newChildKind) { ast.setChanged(); L n = ast.buildTree(newChild, newChildKind); if (n == null) return null; n.parent = (L) this; - ((List)children).add(n); + children = children.append(n); return n; } @@ -267,7 +253,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, for (LombokNode child : children) child.gatherAndRemoveChildren(map); ast.identityDetector.remove(get()); map.put(get(), (L) this); - children.clear(); + children = LombokImmutableList.of(); ast.getNodeMap().remove(get()); } @@ -278,7 +264,7 @@ public abstract class LombokNode<A extends AST<A, L, N>, L extends LombokNode<A, */ public void removeChild(L child) { ast.setChanged(); - children.remove(child); + children = children.removeElement(child); } /** diff --git a/src/core/lombok/core/PostCompiler.java b/src/core/lombok/core/PostCompiler.java index 1e40cca2..4a099862 100644 --- a/src/core/lombok/core/PostCompiler.java +++ b/src/core/lombok/core/PostCompiler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -33,6 +33,7 @@ public final class PostCompiler { private static List<PostCompilerTransformation> transformations; public static byte[] applyTransformations(byte[] original, String fileName, DiagnosticsReceiver diagnostics) { + if (System.getProperty("lombok.disablePostCompiler", null) != null) return original; init(diagnostics); byte[] previous = original; for (PostCompilerTransformation transformation : transformations) { @@ -59,6 +60,7 @@ public final class PostCompiler { } public static OutputStream wrapOutputStream(final OutputStream originalStream, final String fileName, final DiagnosticsReceiver diagnostics) throws IOException { + if (System.getProperty("lombok.disablePostCompiler", null) != null) return originalStream; return new ByteArrayOutputStream() { @Override public void close() throws IOException { // no need to call super diff --git a/src/core/lombok/core/TransformationsUtil.java b/src/core/lombok/core/TransformationsUtil.java index 921c27d6..8959ad7a 100644 --- a/src/core/lombok/core/TransformationsUtil.java +++ b/src/core/lombok/core/TransformationsUtil.java @@ -22,13 +22,25 @@ package lombok.core; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import lombok.ToString; +import lombok.Value; import lombok.experimental.Accessors; +import lombok.experimental.FieldDefaults; +import lombok.experimental.Wither; /** * Container for static utility methods useful for some of the standard lombok transformations, regardless of @@ -39,6 +51,13 @@ public class TransformationsUtil { //Prevent instantiation } + @SuppressWarnings({"all", "unchecked", "deprecation"}) + public static final List<Class<? extends java.lang.annotation.Annotation>> INVALID_ON_BUILDERS = Collections.unmodifiableList( + Arrays.<Class<? extends java.lang.annotation.Annotation>>asList( + Getter.class, Setter.class, Wither.class, ToString.class, EqualsAndHashCode.class, + RequiredArgsConstructor.class, AllArgsConstructor.class, NoArgsConstructor.class, + Data.class, Value.class, lombok.experimental.Value.class, FieldDefaults.class)); + /** * Given the name of a field, return the 'base name' of that field. For example, {@code fFoobar} becomes {@code foobar} if {@code f} is in the prefix list. * For prefixes that end in a letter character, the next character must be a non-lowercase character (i.e. {@code hashCode} is not {@code ashCode} even if @@ -159,12 +178,12 @@ public class TransformationsUtil { if (fieldName.length() == 0) return null; - Accessors ac = accessors.getInstance(); - fieldName = removePrefix(fieldName, ac.prefix()); + Accessors ac = accessors == null ? null : accessors.getInstance(); + fieldName = removePrefix(fieldName, ac == null ? new String[0] : ac.prefix()); if (fieldName == null) return null; String fName = fieldName.toString(); - if (adhereToFluent && ac.fluent()) return fName; + if (adhereToFluent && ac != null && ac.fluent()) return fName; if (isBoolean && fName.startsWith("is") && fieldName.length() > 2 && !Character.isLowerCase(fieldName.charAt(2))) { // The field is for example named 'isRunning'. diff --git a/src/core/lombok/core/TypeLibrary.java b/src/core/lombok/core/TypeLibrary.java index 0e3f7bf4..c0e9dc43 100644 --- a/src/core/lombok/core/TypeLibrary.java +++ b/src/core/lombok/core/TypeLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -21,12 +21,7 @@ */ package lombok.core; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.List; import java.util.Map; /** @@ -40,24 +35,23 @@ import java.util.Map; * <ul><li>foo.Spork</li><li>Spork</li><li>foo.*</li></ul> */ public class TypeLibrary { - private final Map<String, List<String>> keyToFqnMap; - private final String singletonValue; - private final List<String> singletonKeys; + private final Map<String, String> unqualifiedToQualifiedMap; + private final String unqualified, qualified; public TypeLibrary() { - keyToFqnMap = new HashMap<String, List<String>>(); - singletonKeys = null; - singletonValue = null; + unqualifiedToQualifiedMap = new HashMap<String, String>(); + unqualified = null; + qualified = null; } private TypeLibrary(String fqnSingleton) { - keyToFqnMap = null; - singletonValue = fqnSingleton; + unqualifiedToQualifiedMap = null; + qualified = fqnSingleton; int idx = fqnSingleton.lastIndexOf('.'); if (idx == -1) { - singletonKeys = Collections.singletonList(fqnSingleton); + unqualified = fqnSingleton; } else { - singletonKeys = Arrays.asList(fqnSingleton, fqnSingleton.substring(idx + 1), fqnSingleton.substring(0, idx) + ".*"); + unqualified = fqnSingleton.substring(idx + 1); } } @@ -71,42 +65,34 @@ public class TypeLibrary { * @param fullyQualifiedTypeName the FQN type name, such as 'java.lang.String'. */ public void addType(String fullyQualifiedTypeName) { - if (keyToFqnMap == null) throw new IllegalStateException("SingleType library"); + fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); int idx = fullyQualifiedTypeName.lastIndexOf('.'); if (idx == -1) throw new IllegalArgumentException( "Only fully qualified types are allowed (and stuff in the default package is not palatable to us either!)"); + String unqualified = fullyQualifiedTypeName.substring(idx + 1); + if (unqualifiedToQualifiedMap == null) throw new IllegalStateException("SingleType library"); - fullyQualifiedTypeName = fullyQualifiedTypeName.replace("$", "."); - final String simpleName = fullyQualifiedTypeName.substring(idx +1); - final String packageName = fullyQualifiedTypeName.substring(0, idx); - - if (keyToFqnMap.put(fullyQualifiedTypeName, Collections.singletonList(fullyQualifiedTypeName)) != null) return; - - addToMap(simpleName, fullyQualifiedTypeName); - addToMap(packageName + ".*", fullyQualifiedTypeName); - } - - private TypeLibrary addToMap(String keyName, String fullyQualifiedTypeName) { - List<String> list = keyToFqnMap.get(keyName); - if (list == null) { - list = new ArrayList<String>(); - keyToFqnMap.put(keyName, list); + unqualifiedToQualifiedMap.put(unqualified, fullyQualifiedTypeName); + unqualifiedToQualifiedMap.put(fullyQualifiedTypeName, fullyQualifiedTypeName); + for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) { + if (fullyQualifiedTypeName.equals(e.getValue())) unqualifiedToQualifiedMap.put(e.getKey(), fullyQualifiedTypeName); } - - list.add(fullyQualifiedTypeName); - return this; } /** - * Returns all items in the type library that may be a match to the provided type. + * Translates an unqualified name such as 'String' to 'java.lang.String', _if_ you added 'java.lang.String' to the library via the {@code addType} method. + * Also returns the input if it is equal to a fully qualified name added to this type library. * - * @param typeReference something like 'String', 'java.lang.String', or 'java.lang.*'. - * @return A list of Fully Qualified Names for all types in the library that fit the reference. + * Returns null if it does not match any type in this type library. */ - public Collection<String> findCompatible(String typeReference) { - if (singletonKeys != null) return singletonKeys.contains(typeReference) ? Collections.singletonList(singletonValue) : Collections.<String>emptyList(); - - List<String> result = keyToFqnMap.get(typeReference); - return result == null ? Collections.<String>emptyList() : Collections.unmodifiableList(result); + public String toQualified(String typeReference) { + if (unqualifiedToQualifiedMap == null) { + if (typeReference.equals(unqualified) || typeReference.equals(qualified)) return qualified; + for (Map.Entry<String, String> e : LombokInternalAliasing.ALIASES.entrySet()) { + if (e.getKey().equals(typeReference)) return e.getValue(); + } + return null; + } + return unqualifiedToQualifiedMap.get(typeReference); } } diff --git a/src/core/lombok/core/TypeResolver.java b/src/core/lombok/core/TypeResolver.java index 27f0bfc1..287a085f 100644 --- a/src/core/lombok/core/TypeResolver.java +++ b/src/core/lombok/core/TypeResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -21,11 +21,6 @@ */ package lombok.core; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; - import lombok.core.AST.Kind; /** @@ -35,60 +30,47 @@ import lombok.core.AST.Kind; * and this importer also can't find inner types from superclasses/interfaces. */ public class TypeResolver { - private Collection<String> imports; + private ImportList imports; /** * Creates a new TypeResolver that can be used to resolve types in a source file with the given package and import statements. */ - public TypeResolver(String packageString, Collection<String> importStrings) { - this.imports = makeImportList(packageString, importStrings); - } - - private static Collection<String> makeImportList(String packageString, Collection<String> importStrings) { - Set<String> imports = new HashSet<String>(); - if (packageString != null) imports.add(packageString + ".*"); - imports.addAll(importStrings == null ? Collections.<String>emptySet() : importStrings); - imports.add("java.lang.*"); - return imports; + public TypeResolver(ImportList importList) { + this.imports = importList; } public boolean typeMatches(LombokNode<?, ?, ?> context, String fqn, String typeRef) { - return !findTypeMatches(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef).isEmpty(); + return typeRefToFullyQualifiedName(context, TypeLibrary.createLibraryForSingleType(fqn), typeRef) != null; } - /** - * Finds type matches for the stated type reference. The provided context is scanned for local type names - * that shadow type names listed in import statements. If such a shadowing occurs, no matches are returned - * for any shadowed types, as you would expect. - */ - public Collection<String> findTypeMatches(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) { + public String typeRefToFullyQualifiedName(LombokNode<?, ?, ?> context, TypeLibrary library, String typeRef) { + typeRef = LombokInternalAliasing.processAliases(typeRef); // When asking if 'Foo' could possibly be referring to 'bar.Baz', the answer is obviously no. - Collection<String> potentialMatches = library.findCompatible(typeRef); - if (potentialMatches.isEmpty()) return Collections.emptyList(); + String qualified = library.toQualified(typeRef); + if (qualified == null) return null; - // If input type appears to be fully qualified, we found a winner. - int idx = typeRef.indexOf('.'); - if (idx > -1) return potentialMatches; + // When asking if 'lombok.Getter' could possibly be referring to 'lombok.Getter', the answer is obviously yes. + if (typeRef.equals(qualified)) return typeRef; - // If there's an import statement that explicitly imports a 'Getter' that isn't any of our potentials, return no matches, - // because if you want to know if 'Foo' could refer to 'bar.Foo' when 'baz.Foo' is explicitly imported, the answer is no. - if (nameConflictInImportList(typeRef, potentialMatches)) return Collections.emptyList(); + // When asking if 'Getter' could possibly be referring to 'lombok.Getter' if 'import lombok.Getter;' is in the source file, the answer is yes. + String fromExplicitImport = imports.getFullyQualifiedNameForSimpleName(typeRef); + if (fromExplicitImport != null) { + // ... and if 'import foobar.Getter;' is in the source file, the answer is no. + return fromExplicitImport.equals(qualified) ? qualified : null; + } - // Check if any of our potentials are even imported in the first place. If not: no matches. - // Note that (ourPackage.*) is added to the imports. - potentialMatches = eliminateImpossibleMatches(potentialMatches, library); - if (potentialMatches.isEmpty()) return Collections.emptyList(); + // When asking if 'Getter' could possibly be referring to 'lombok.Getter' and 'import lombok.*; / package lombok;' isn't in the source file. the answer is no. + String pkgName = qualified.substring(0, qualified.length() - typeRef.length() - 1); + if (!imports.hasStarImport(pkgName)) return null; - // Now the hard part - inner classes or method local classes in our own scope. - // For method locals, this refers to any statements that are 'above' the type reference with the same name. - // For inners, this refers to siblings of us or any parent node that are type declarations. + // Now the hard part: Given that there is a star import, 'Getter' most likely refers to 'lombok.Getter', but type shadowing may occur in which case it doesn't. LombokNode<?, ?, ?> n = context; mainLoop: while (n != null) { if (n.getKind() == Kind.TYPE && typeRef.equals(n.getName())) { // Our own class or one of our outer classes is named 'typeRef' so that's what 'typeRef' is referring to, not one of our type library classes. - return Collections.emptyList(); + return null; } if (n.getKind() == Kind.STATEMENT || n.getKind() == Kind.LOCAL) { @@ -99,7 +81,7 @@ public class TypeResolver { for (LombokNode<?, ?, ?> child : newN.down()) { // We found a method local with the same name above our code. That's the one 'typeRef' is referring to, not // anything in the type library we're trying to find, so, no matches. - if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return Collections.emptyList(); + if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null; if (child == n) break; } } @@ -110,41 +92,14 @@ public class TypeResolver { if (n.getKind() == Kind.TYPE || n.getKind() == Kind.COMPILATION_UNIT) { for (LombokNode<?, ?, ?> child : n.down()) { // Inner class that's visible to us has 'typeRef' as name, so that's the one being referred to, not one of our type library classes. - if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return Collections.emptyList(); + if (child.getKind() == Kind.TYPE && typeRef.equals(child.getName())) return null; } } n = n.directUp(); } - // No class in this source file is a match, therefore the potential matches found via the import statements must be it. Return those. - return potentialMatches; - } - - private Collection<String> eliminateImpossibleMatches(Collection<String> potentialMatches, TypeLibrary library) { - Set<String> results = new HashSet<String>(); - - for (String importedType : imports) { - Collection<String> reduced = new HashSet<String>(library.findCompatible(importedType)); - reduced.retainAll(potentialMatches); - results.addAll(reduced); - } - - return results; - } - - private boolean nameConflictInImportList(String simpleName, Collection<String> potentialMatches) { - for (String importedType : imports) { - if (!toSimpleName(importedType).equals(simpleName)) continue; - if (potentialMatches.contains(importedType)) continue; - return true; - } - return false; - } - - private static String toSimpleName(String typeName) { - int idx = typeName.lastIndexOf('.'); - return idx == -1 ? typeName : typeName.substring(idx+1); + return qualified; } } diff --git a/src/core/lombok/core/Version.java b/src/core/lombok/core/Version.java index b9239978..bd83c1f8 100644 --- a/src/core/lombok/core/Version.java +++ b/src/core/lombok/core/Version.java @@ -28,8 +28,8 @@ public class Version { // ** CAREFUL ** - this class must always compile with 0 dependencies (it must not refer to any other sources or libraries). // Note: In 'X.Y.Z', if Z is odd, its a snapshot build built from the repository, so many different 0.10.3 versions can exist, for example. // Official builds always end in an even number. (Since 0.10.2). - private static final String VERSION = "0.11.7"; - private static final String RELEASE_NAME = "Dashing Kakapo"; + private static final String VERSION = "0.12.1"; + private static final String RELEASE_NAME = "Angry Butterfy"; private Version() { //Prevent instantiation diff --git a/src/core/lombok/core/handlers/HandlerUtil.java b/src/core/lombok/core/handlers/HandlerUtil.java new file mode 100644 index 00000000..3d386054 --- /dev/null +++ b/src/core/lombok/core/handlers/HandlerUtil.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 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.core.handlers; + +import lombok.core.JavaIdentifiers; +import lombok.core.LombokNode; + +public class HandlerUtil { + private HandlerUtil() {} + + /** Checks if the given name is a valid identifier. + * + * If it is, this returns {@code true} and does nothing else. + * If it isn't, this returns {@code false} and adds an error message to the supplied node. + */ + public static boolean checkName(String nameSpec, String identifier, LombokNode<?, ?, ?> errorNode) { + if (identifier.isEmpty()) { + errorNode.addError(nameSpec + " cannot be the empty string."); + return false; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(identifier)) { + errorNode.addError(nameSpec + " must be a valid java identifier."); + return false; + } + + return true; + } +} diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 8ab42140..370b40fc 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -24,12 +24,14 @@ package lombok.eclipse; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import org.eclipse.jdt.internal.compiler.CompilationResult; import java.util.Collection; import java.util.Collections; import java.util.List; import lombok.Lombok; import lombok.core.AST; +import lombok.core.LombokImmutableList; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; @@ -55,7 +57,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * @param ast The compilation unit, which serves as the top level node in the tree to be built. */ public EclipseAST(CompilationUnitDeclaration ast) { - super(toFileName(ast), packageDeclaration(ast), imports(ast)); + super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast)); this.compilationUnitDeclaration = ast; setTop(buildCompilationUnit(ast)); this.completeParse = isComplete(ast); @@ -67,16 +69,18 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName()); } - private static Collection<String> imports(CompilationUnitDeclaration cud) { - List<String> imports = new ArrayList<String>(); - if (cud.imports == null) return imports; - for (ImportReference imp : cud.imports) { - if (imp == null) continue; - String qualifiedName = Eclipse.toQualifiedName(imp.getImportName()); - if ((imp.bits & ASTNode.OnDemand) != 0) qualifiedName += ".*"; - imports.add(qualifiedName); - } - return imports; + @Override public int getSourceVersion() { + long sl = compilationUnitDeclaration.problemReporter.options.sourceLevel; + long cl = compilationUnitDeclaration.problemReporter.options.complianceLevel; + sl >>= 16; + cl >>= 16; + if (sl == 0) sl = cl; + if (cl == 0) cl = sl; + return Math.min((int)(sl - 44), (int)(cl - 44)); + } + + @Override public int getLatestJavaSpecSupported() { + return Eclipse.getEcjCompilerVersion(); } /** @@ -88,8 +92,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) { - for (EclipseNode child : node.down()) { - child.traverse(visitor); + LombokImmutableList<EclipseNode> children = node.down(); + int len = children.size(); + for (int i = 0; i < len; i++) { + children.get(i).traverse(visitor); } } @@ -118,7 +124,8 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } void addToCompilationResult() { - addProblemToCompilationResult((CompilationUnitDeclaration) top().get(), + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, isWarning, message, sourceStart, sourceEnd); } } @@ -142,11 +149,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; try { - EcjReflectionCheck.addProblemToCompilationResult.invoke(null, ast, isWarning, message, sourceStart, sourceEnd); + EcjReflectionCheck.addProblemToCompilationResult.invoke(null, fileNameArray, result, isWarning, message, sourceStart, sourceEnd); } catch (NoClassDefFoundError e) { //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly //do anything useful here. @@ -163,7 +169,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { //do anything useful here. } } - + private final CompilationUnitDeclaration compilationUnitDeclaration; private boolean completeParse; @@ -353,7 +359,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } private static class EcjReflectionCheck { - private static final String CUD_TYPE = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration"; + private static final String COMPILATIONRESULT_TYPE = "org.eclipse.jdt.internal.compiler.CompilationResult"; public static Method addProblemToCompilationResult; public static final Throwable problem; @@ -362,7 +368,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { Throwable problem_ = null; Method m = null; try { - m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", Class.forName(CUD_TYPE), boolean.class, String.class, int.class, int.class); + m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", char[].class, Class.forName(COMPILATIONRESULT_TYPE), boolean.class, String.class, int.class, int.class); } catch (Throwable t) { // That's problematic, but as long as no local classes are used we don't actually need it. // Better fail on local classes than crash altogether. diff --git a/src/core/lombok/eclipse/EclipseAnnotationHandler.java b/src/core/lombok/eclipse/EclipseAnnotationHandler.java index 84304339..ca9965f7 100644 --- a/src/core/lombok/eclipse/EclipseAnnotationHandler.java +++ b/src/core/lombok/eclipse/EclipseAnnotationHandler.java @@ -29,7 +29,7 @@ import lombok.core.SpiLoadUtil; * * You MUST replace 'T' with a specific annotation type, such as: * - * {@code public class HandleGetter implements EclipseAnnotationHandler<Getter>} + * {@code public class HandleGetter extends EclipseAnnotationHandler<Getter>} * * Because this generics parameter is inspected to figure out which class you're interested in. * diff --git a/src/core/lombok/eclipse/EclipseAstProblemView.java b/src/core/lombok/eclipse/EclipseAstProblemView.java index a2d5b833..c1179666 100644 --- a/src/core/lombok/eclipse/EclipseAstProblemView.java +++ b/src/core/lombok/eclipse/EclipseAstProblemView.java @@ -3,7 +3,6 @@ package lombok.eclipse; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.util.Util; @@ -13,14 +12,12 @@ public class EclipseAstProblemView { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; - char[] fileNameArray = ast.getFileName(); + if (result == null) return; if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray(); int lineNumber = 0; int columnNumber = 1; - CompilationResult result = ast.compilationResult; int[] lineEnds = null; lineNumber = sourceStart >= 0 ? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1) @@ -33,7 +30,7 @@ public class EclipseAstProblemView { fileNameArray, message, 0, new String[0], isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error, sourceStart, sourceEnd, lineNumber, columnNumber); - ast.compilationResult.record(ecProblem, null); + result.record(ecProblem, null); } private static class LombokProblem extends DefaultProblem { diff --git a/src/core/lombok/eclipse/EclipseImportList.java b/src/core/lombok/eclipse/EclipseImportList.java new file mode 100644 index 00000000..69246b3c --- /dev/null +++ b/src/core/lombok/eclipse/EclipseImportList.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 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.eclipse; + +import static lombok.eclipse.Eclipse.toQualifiedName; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; + +public class EclipseImportList implements ImportList { + private ImportReference[] imports; + private ImportReference pkg; + + public EclipseImportList(CompilationUnitDeclaration cud) { + this.pkg = cud.currentPackage; + this.imports = cud.imports; + } + + @Override public String getFullyQualifiedNameForSimpleName(String unqualified) { + if (imports != null) { + outer: + for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) != 0) continue; + char[][] tokens = imp.tokens; + char[] token = tokens.length == 0 ? new char[0] : tokens[tokens.length - 1]; + int len = token.length; + if (len != unqualified.length()) continue; + for (int i = 0; i < len; i++) if (token[i] != unqualified.charAt(i)) continue outer; + return LombokInternalAliasing.processAliases(toQualifiedName(tokens)); + } + } + return null; + } + + @Override public boolean hasStarImport(String packageName) { + for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } + if (isEqual(packageName, pkg)) return true; + if ("java.lang".equals(packageName)) return true; + if (imports != null) for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) == 0) continue; + if (imp.isStatic()) continue; + if (isEqual(packageName, imp)) return true; + } + return false; + } + + private static boolean isEqual(String packageName, ImportReference pkgOrStarImport) { + if (pkgOrStarImport == null || pkgOrStarImport.tokens == null || pkgOrStarImport.tokens.length == 0) return packageName.isEmpty(); + int pos = 0; + int len = packageName.length(); + for (int i = 0; i < pkgOrStarImport.tokens.length; i++) { + if (i != 0) { + if (pos >= len) return false; + if (packageName.charAt(pos++) != '.') return false; + } + for (int j = 0; j < pkgOrStarImport.tokens[i].length; j++) { + if (pos >= len) return false; + if (packageName.charAt(pos++) != pkgOrStarImport.tokens[i][j]) return false; + } + } + return true; + } + + @Override public Collection<String> applyNameToStarImports(String startsWith, String name) { + List<String> out = Collections.emptyList(); + + if (pkg != null && pkg.tokens != null && pkg.tokens.length != 0) { + char[] first = pkg.tokens[0]; + int len = first.length; + boolean match = true; + if (startsWith.length() == len) { + for (int i = 0; match && i < len; i++) { + if (startsWith.charAt(i) != first[i]) match = false; + } + if (match) out.add(toQualifiedName(pkg.tokens) + "." + name); + } + } + + if (imports != null) { + outer: + for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) == 0) continue; + if (imp.isStatic()) continue; + if (imp.tokens == null || imp.tokens.length == 0) continue; + char[] firstToken = imp.tokens[0]; + if (firstToken.length != startsWith.length()) continue; + for (int i = 0; i < firstToken.length; i++) if (startsWith.charAt(i) != firstToken[i]) continue outer; + String fqn = toQualifiedName(imp.tokens) + "." + name; + if (out.isEmpty()) out = Collections.singletonList(fqn); + else if (out.size() == 1) { + out = new ArrayList<String>(out); + out.add(fqn); + } else { + out.add(fqn); + } + } + } + return out; + } + + @Override public String applyUnqualifiedNameToPackage(String unqualified) { + if (pkg == null || pkg.tokens == null || pkg.tokens.length == 0) return unqualified; + return toQualifiedName(pkg.tokens) + "." + unqualified; + } +} diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java index 56744793..242e923c 100644 --- a/src/core/lombok/eclipse/HandlerLibrary.java +++ b/src/core/lombok/eclipse/HandlerLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -218,30 +218,27 @@ public class HandlerLibrary { * @param annotation 'node.get()' - convenience parameter. */ 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(); - - TypeResolver resolver = new TypeResolver(pkgName, imports); + TypeResolver resolver = new TypeResolver(annotationNode.getImportList()); TypeReference rawType = annotation.type; if (rawType == null) return; - for (String fqn : resolver.findTypeMatches(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName()))) { - 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; - } - - try { - if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode); - } catch (AnnotationValueDecodeFail fail) { - fail.owner.setError(fail.getMessage(), fail.idx); - } catch (Throwable t) { - error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); - } + String fqn = resolver.typeRefToFullyQualifiedName(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName())); + if (fqn == null) return; + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + if (container == null) return; + if (priority != container.getPriority()) return; + + if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) { + if (needsHandling(annotation)) container.preHandle(annotation, annotationNode); + return; + } + + try { + if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode); + } catch (AnnotationValueDecodeFail fail) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch (Throwable t) { + error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); } } diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 47e620f6..11caf5c2 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -144,7 +144,7 @@ public class TransformEclipseAST { try { String message = "Lombok can't parse this source: " + t.toString(); - EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0); + EclipseAST.addProblemToCompilationResult(ast.getFileName(), ast.compilationResult, false, message, 0, 0); t.printStackTrace(); } catch (Throwable t2) { try { diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 78780522..9bd634f7 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -22,6 +22,7 @@ package lombok.eclipse.handlers; import static lombok.eclipse.Eclipse.*; +import static lombok.core.TransformationsUtil.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -61,6 +62,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.CastExpression; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; @@ -142,7 +144,7 @@ public class EclipseHandlerUtil { } catch (NoClassDefFoundError e) { //standalone ecj does not jave Platform, ILog, IStatus, and friends. new TerminalLogger().error(message, bundleName, error); } - if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0); + if (cud != null) EclipseAST.addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, false, message + " - See error log.", 0, 0); } /** @@ -290,11 +292,34 @@ public class EclipseHandlerUtil { if (!lastPartA.equals(lastPartB)) return false; String typeName = toQualifiedName(typeRef.getTypeName()); - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); } + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) { + List<String> disallowed = null; + for (EclipseNode child : typeNode.down()) { + for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + if (disallowed == null) disallowed = new ArrayList<String>(); + disallowed.add(annType.getSimpleName()); + } + } + } + + int size = disallowed == null ? 0 : disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + public static Annotation copyAnnotation(Annotation annotation, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; @@ -358,6 +383,20 @@ public class EclipseHandlerUtil { return out; } + public static TypeReference namePlusTypeParamsToTypeReference(char[] typeName, TypeParameter[] params, long p) { + if (params != null && params.length > 0) { + TypeReference[] refs = new TypeReference[params.length]; + int idx = 0; + for (TypeParameter param : params) { + TypeReference typeRef = new SingleTypeReference(param.name, p); + refs[idx++] = typeRef; + } + return new ParameterizedSingleTypeReference(typeName, refs, 0, p); + } + + return new SingleTypeReference(typeName, p); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -830,15 +869,20 @@ public class EclipseHandlerUtil { private static final Object MARKER = new Object(); static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) { - if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return; - generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + if (isBoolean(returnType)) { + generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + } + } + + public static boolean isBoolean(TypeReference typeReference) { + return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0; } private static GetterMethod findGetter(EclipseNode field) { FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get(); boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration); TypeReference fieldType = fieldDeclaration.type; - boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0); + boolean isBoolean = forceBool || isBoolean(fieldType); EclipseNode typeNode = field.up(); for (String potentialGetterName : toAllGetterNames(field, isBoolean)) { @@ -1207,15 +1251,15 @@ public class EclipseHandlerUtil { * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. * The field carries the @{@link SuppressWarnings}("all") annotation. */ - public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { field.annotations = createSuppressWarningsAll(field, field.annotations); - injectField(type, field); + return injectField(type, field); } /** * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectField(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectField(EclipseNode type, FieldDeclaration field) { TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.fields == null) { @@ -1242,7 +1286,7 @@ public class EclipseHandlerUtil { } } - type.add(field, Kind.FIELD); + return type.add(field, Kind.FIELD); } private static boolean isEnumConstant(final FieldDeclaration field) { @@ -1252,7 +1296,7 @@ public class EclipseHandlerUtil { /** * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) { + public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) { method.annotations = createSuppressWarningsAll(method, method.annotations); TypeDeclaration parent = (TypeDeclaration) type.get(); @@ -1285,7 +1329,29 @@ public class EclipseHandlerUtil { parent.methods = newArray; } - type.add(method, Kind.METHOD); + return type.add(method, Kind.METHOD); + } + + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + */ + public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) { + type.annotations = createSuppressWarningsAll(type, type.annotations); + TypeDeclaration parent = (TypeDeclaration) typeNode.get(); + + if (parent.memberTypes == null) { + parent.memberTypes = new TypeDeclaration[] { type }; + } else { + TypeDeclaration[] newArray = new TypeDeclaration[parent.memberTypes.length + 1]; + System.arraycopy(parent.memberTypes, 0, newArray, 0, parent.memberTypes.length); + newArray[parent.memberTypes.length] = type; + parent.memberTypes = newArray; + } + + return typeNode.add(type, Kind.TYPE); } private static final char[] ALL = "all".toCharArray(); @@ -1334,7 +1400,11 @@ public class EclipseHandlerUtil { EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL); equalExpression.sourceStart = pS; equalExpression.statementEnd = equalExpression.sourceEnd = pE; setGeneratedBy(equalExpression, source); - IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0); + Block throwBlock = new Block(0); + throwBlock.statements = new Statement[] {throwStatement}; + throwBlock.sourceStart = pS; throwBlock.sourceEnd = pE; + setGeneratedBy(throwBlock, source); + IfStatement ifStatement = new IfStatement(equalExpression, throwBlock, 0, 0); setGeneratedBy(ifStatement, source); return ifStatement; } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java new file mode 100644 index 00000000..70110a9c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2013 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.eclipse.handlers; + +import static lombok.eclipse.Eclipse.*; +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeParameter; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.mangosdk.spi.ProviderFor; + +import lombok.AccessLevel; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; +import lombok.experimental.Builder; +import lombok.experimental.NonFinal; + +@ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. +public class HandleBuilder extends EclipseAnnotationHandler<Builder> { + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { + long p = (long) ast.sourceStart << 32 | ast.sourceEnd; + + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) builderMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } + + EclipseNode parent = annotationNode.up(); + + List<TypeReference> typesOfParameters = new ArrayList<TypeReference>(); + List<char[]> namesOfParameters = new ArrayList<char[]>(); + TypeReference returnType; + TypeParameter[] typeParams; + TypeReference[] thrownExceptions; + char[] nameOfStaticBuilderMethod; + EclipseNode tdParent; + + AbstractMethodDeclaration fillParametersFrom = null; + + if (parent.get() instanceof TypeDeclaration) { + tdParent = parent; + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + + List<EclipseNode> fields = new ArrayList<EclipseNode>(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.type); + fields.add(fieldNode); + } + + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.<Annotation>emptyList(), ast); + + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = null; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder"; + } else if (parent.get() instanceof ConstructorDeclaration) { + ConstructorDeclaration cd = (ConstructorDeclaration) parent.get(); + if (cd.typeParameters != null && cd.typeParameters.length > 0) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + + tdParent = parent.up(); + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + fillParametersFrom = cd; + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = cd.thrownExceptions; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder"; + } else if (parent.get() instanceof MethodDeclaration) { + MethodDeclaration md = (MethodDeclaration) parent.get(); + tdParent = parent.up(); + if (!md.isStatic()) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + fillParametersFrom = md; + returnType = copyType(md.returnType, ast); + typeParams = md.typeParameters; + thrownExceptions = md.thrownExceptions; + nameOfStaticBuilderMethod = md.selector; + if (builderClassName.isEmpty()) { + char[] token; + if (md.returnType instanceof QualifiedTypeReference) { + char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens; + token = tokens[tokens.length - 1]; + } else if (md.returnType instanceof SingleTypeReference) { + token = ((SingleTypeReference) md.returnType).token; + if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) { + for (TypeParameter tp : typeParams) { + if (Arrays.equals(tp.name, token)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + } + } else { + annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem."); + return; + } + + if (Character.isLowerCase(token[0])) { + char[] newToken = new char[token.length]; + System.arraycopy(token, 1, newToken, 1, token.length - 1); + newToken[0] = Character.toTitleCase(token[0]); + token = newToken; + } + + builderClassName = new String(token) + "Builder"; + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + + if (fillParametersFrom != null) { + if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { + namesOfParameters.add(a.name); + typesOfParameters.add(a.type); + } + } + + EclipseNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } + List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>(); + for (EclipseNode fieldNode : fieldNodes) { + MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain()); + if (newMethod != null) newMethods.add(newMethod); + } + + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + ConstructorDeclaration cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), true, ast, Collections.<Annotation>emptyList()); + if (cd != null) injectMethod(builderType, cd); + } + + for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + } + + private MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.typeParameters = copyTypeParams(typeParams, source); + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); + return out; + } + + private MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<char[]> fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + + out.modifiers = ClassFileConstants.AccPublic; + TypeDeclaration typeDecl = (TypeDeclaration) type.get(); + out.selector = name.toCharArray(); + out.thrownExceptions = copyTypes(thrownExceptions, source); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = returnType; + + List<Expression> assigns = new ArrayList<Expression>(); + for (char[] fieldName : fieldNames) { + SingleNameReference nameRef = new SingleNameReference(fieldName, p); + assigns.add(nameRef); + } + + Statement statement; + + if (staticName == null) { + AllocationExpression allocationStatement = new AllocationExpression(); + allocationStatement.type = copyType(out.returnType, source); + allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p); + } else { + MessageSend invoke = new MessageSend(); + invoke.selector = staticName; + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p); + TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters; + if (tps != null) { + TypeReference[] trs = new TypeReference[tps.length]; + for (int i = 0; i < trs.length; i++) { + trs[i] = new SingleTypeReference(tps[i].name, p); + } + invoke.typeArguments = trs; + } + invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + if (returnType instanceof SingleTypeReference && Arrays.equals(TypeBinding.VOID.simpleName, ((SingleTypeReference) returnType).token)) { + statement = invoke; + } else { + statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + } + } + + out.statements = new Statement[] { statement }; + + out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); + return out; + } + + private List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) { + int len = namesOfParameters.size(); + TypeDeclaration td = (TypeDeclaration) builderType.get(); + FieldDeclaration[] existing = td.fields; + if (existing == null) existing = new FieldDeclaration[0]; + + List<EclipseNode> out = new ArrayList<EclipseNode>(); + + top: + for (int i = len - 1; i >= 0; i--) { + char[] name = namesOfParameters.get(i); + for (FieldDeclaration exists : existing) { + if (Arrays.equals(exists.name, name)) { + out.add(builderType.getNodeFor(exists)); + continue top; + } + } + TypeReference fieldReference = copyType(typesOfParameters.get(i), source); + FieldDeclaration newField = new FieldDeclaration(name, 0, 0); + newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + newField.modifiers = ClassFileConstants.AccPrivate; + newField.type = fieldReference; + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + + return out; + } + + private static final AbstractMethodDeclaration[] EMPTY = {}; + + private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) { + TypeDeclaration td = (TypeDeclaration) builderType.get(); + AbstractMethodDeclaration[] existing = td.methods; + if (existing == null) existing = EMPTY; + int len = existing.length; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + char[] name = fd.name; + + for (int i = 0; i < len; i++) { + if (!(existing[i] instanceof MethodDeclaration)) continue; + char[] existingName = existing[i].selector; + if (Arrays.equals(name, existingName)) return null; + } + + boolean isBoolean = isBoolean(fd.type); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + + return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, + source, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + } + + private EclipseNode findInnerClass(EclipseNode parent, String name) { + char[] c = name.toCharArray(); + for (EclipseNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + TypeDeclaration td = (TypeDeclaration) child.get(); + if (Arrays.equals(td.name, c)) return child; + } + return null; + } + + private EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + TypeDeclaration parent = (TypeDeclaration) tdParent.get(); + TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); + builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + builder.typeParameters = copyTypeParams(typeParams, source); + builder.name = builderClassName.toCharArray(); + builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return injectType(tdParent, builder); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 8ccad77f..1ae680d9 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -39,6 +39,7 @@ import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Builder; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; @@ -53,18 +54,14 @@ import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; -import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; -import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; -import org.eclipse.jdt.internal.compiler.ast.TypeParameter; -import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.mangosdk.spi.ProviderFor; @@ -82,7 +79,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, false, false, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, false, onConstructor, ast); } } @@ -100,7 +97,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -117,7 +114,7 @@ public class HandleConstructor { return fields; } - private static List<EclipseNode> findAllFields(EclipseNode typeNode) { + static List<EclipseNode> findAllFields(EclipseNode typeNode) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -146,7 +143,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -164,25 +161,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { + public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { + public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child)); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -235,7 +241,7 @@ public class HandleConstructor { return new Annotation[] { ann }; } - private ConstructorDeclaration createConstructor( + static ConstructorDeclaration createConstructor( AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean suppressConstructorProperties, ASTNode source, List<Annotation> onConstructor) { @@ -307,7 +313,7 @@ public class HandleConstructor { return constructor; } - private boolean isLocalType(EclipseNode type) { + private static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); @@ -321,18 +327,9 @@ public class HandleConstructor { MethodDeclaration constructor = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); - constructor.modifiers = toEclipseModifier(level) | Modifier.STATIC; + constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; TypeDeclaration typeDecl = (TypeDeclaration) type.get(); - if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) { - TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length]; - int idx = 0; - for (TypeParameter param : typeDecl.typeParameters) { - TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); - setGeneratedBy(typeRef, source); - refs[idx++] = typeRef; - } - constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); - } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); + constructor.returnType = EclipseHandlerUtil.namePlusTypeParamsToTypeReference(typeDecl.name, typeDecl.typeParameters, p); constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 3a43bd3f..aa309489 100644 --- a/src/core/lombok/eclipse/handlers/HandleData.java +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -28,6 +28,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -64,6 +65,6 @@ public class HandleData extends EclipseAnnotationHandler<Data> { new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 0c82b74c..6990e609 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -204,18 +204,27 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0; - boolean needsCanEqual = !isDirectDescendantOfObject || !isFinal; - java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); errorNode.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + errorNode.addWarning(msg); } return; case NOT_EXISTS: diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 0d21fc08..d6d839cc 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -43,7 +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. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks 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) { @@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 7f788c5d..787f6f6c 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -49,6 +49,8 @@ import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.CastExpression; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; @@ -67,6 +69,7 @@ import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.mangosdk.spi.ProviderFor; /** @@ -184,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String getterName = toGetterName(fieldNode, isBoolean); if (getterName == null) { @@ -286,7 +289,6 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } private static final char[][] AR = fromQualifiedName("java.util.concurrent.atomic.AtomicReference"); - private static final TypeReference[][] AR_PARAMS = new TypeReference[5][]; private static final java.util.Map<String, char[][]> TYPE_MAP; static { @@ -305,41 +307,54 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { private static char[] valueName = "value".toCharArray(); private static char[] actualValueName = "actualValue".toCharArray(); + private static final int PARENTHESIZED = (1 << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK; + private Statement[] createLazyGetterBody(ASTNode source, EclipseNode fieldNode) { /* - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + final RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - TypeReference componentType = copyType(field.type, source); + TypeReference rawComponentType = copyType(field.type, source); + TypeReference boxedComponentType = null; + boolean isPrimitive = false; if (field.type instanceof SingleTypeReference && !(field.type instanceof ArrayTypeReference)) { char[][] newType = TYPE_MAP.get(new String(((SingleTypeReference)field.type).token)); if (newType != null) { - componentType = new QualifiedTypeReference(newType, poss(source, 3)); + boxedComponentType = new QualifiedTypeReference(newType, poss(source, 3)); + isPrimitive = true; } } + if (boxedComponentType == null) boxedComponentType = copyType(field.type, source); + boxedComponentType.sourceStart = pS; boxedComponentType.sourceEnd = boxedComponentType.statementEnd = pE; Statement[] statements = new Statement[3]; - /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); */ { + /* java.lang.Object value = this.fieldName.get(); */ { LocalDeclaration valueDecl = new LocalDeclaration(valueName, pS, pE); - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - valueDecl.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + valueDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); valueDecl.type.sourceStart = pS; valueDecl.type.sourceEnd = valueDecl.type.statementEnd = pE; MessageSend getter = new MessageSend(); @@ -356,8 +371,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + final ValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } @@ -383,28 +402,37 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { EqualExpression innerCond = new EqualExpression( new SingleNameReference(valueName, p), new NullLiteral(pS, pE), BinaryExpression.EQUAL_EQUAL); + innerCond.sourceStart = pS; innerCond.sourceEnd = innerCond.statementEnd = pE; Block innerThen = new Block(0); innerThen.statements = new Statement[3]; - /* final ValueType actualValue = new ValueType(); */ { + /* final ValueType actualValue = INITIALIZER_EXPRESSION */ { LocalDeclaration actualValueDecl = new LocalDeclaration(actualValueName, pS, pE); - actualValueDecl.type = copyType(field.type, source); + actualValueDecl.type = rawComponentType; actualValueDecl.type.sourceStart = pS; actualValueDecl.type.sourceEnd = actualValueDecl.type.statementEnd = pE; actualValueDecl.initialization = field.initialization; actualValueDecl.modifiers = ClassFileConstants.AccFinal; innerThen.statements[0] = actualValueDecl; } - /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); */ { - AllocationExpression create = new AllocationExpression(); - create.sourceStart = pS; create.sourceEnd = create.statementEnd = pE; - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - create.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); - create.type.sourceStart = pS; create.type.sourceEnd = create.type.statementEnd = pE; - create.arguments = new Expression[] {new SingleNameReference(actualValueName, p)}; - Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), create, pE); - innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; - - innerThen.statements[1] = innerAssign; + /* [IF PRIMITIVE] value = actualValue; */ { + if (isPrimitive) { + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), new SingleNameReference(actualValueName, p), pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + EqualExpression avIsNull = new EqualExpression( + new SingleNameReference(actualValueName, p), new NullLiteral(pS, pE), + BinaryExpression.EQUAL_EQUAL); + avIsNull.sourceStart = pS; avIsNull.sourceEnd = avIsNull.statementEnd = pE; + Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + ConditionalExpression ternary = new ConditionalExpression(avIsNull, fieldRef, new SingleNameReference(actualValueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), ternary, pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } } /* this.fieldName.set(value); */ { @@ -428,26 +456,34 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { statements[1] = ifStatement; } - /* return value.get(); */ { - MessageSend getter = new MessageSend(); - getter.sourceStart = pS; getter.sourceEnd = getter.statementEnd = pE; - getter.selector = new char[] {'g', 'e', 't'}; - getter.receiver = new SingleNameReference(valueName, p); - - statements[2] = new ReturnStatement(getter, pS, pE); + /* [IF PRIMITIVE] return (BoxedValueType)value; */ { + if (isPrimitive) { + CastExpression cast = makeCastExpression(new SingleNameReference(valueName, p), boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } + } + /* [ELSE] return (BoxedValueType)(value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + EqualExpression vIsThisFieldName = new EqualExpression( + new SingleNameReference(valueName, p), createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), + BinaryExpression.EQUAL_EQUAL); + vIsThisFieldName.sourceStart = pS; vIsThisFieldName.sourceEnd = vIsThisFieldName.statementEnd = pE; + ConditionalExpression ternary = new ConditionalExpression(vIsThisFieldName, new NullLiteral(pS, pE), new SingleNameReference(valueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + ternary.bits |= PARENTHESIZED; + CastExpression cast = makeCastExpression(ternary, boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } } - // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { - - LocalDeclaration first = (LocalDeclaration) statements[0]; - TypeReference innerType = copyType(first.type, source); - - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(innerType, source)}; + /* private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> fieldName = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); */ { + TypeReference innerType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); + TypeReference[][] typeParams = new TypeReference[5][]; + typeParams[4] = new TypeReference[] {innerType}; TypeReference type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + // Some magic here type.sourceStart = -1; type.sourceEnd = -2; diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index bffe2d62..2e7b4475 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -178,6 +178,16 @@ public class HandleLog { } /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleLog4j2Log extends EclipseAnnotationHandler<lombok.extern.log4j.Log4j2> { + @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, Annotation source, EclipseNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, source, annotationNode); + } + } + + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for Eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) @@ -224,6 +234,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J("org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger", "@Log4j"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", "getLogger", "@Log4j2"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J("org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger", "@Slf4j"), diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 9b46b704..3bfcc51c 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String setterName = toSetterName(fieldNode, isBoolean); boolean shouldReturnThis = shouldReturnThis(fieldNode); @@ -192,7 +192,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { injectMethod(fieldNode.up(), method); } - private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java index b7c8a5d8..aa78ca3b 100644 --- a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java @@ -40,6 +40,8 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -147,7 +149,21 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> { return; } - if (method.statements == null) return; + if (method.statements == null || method.statements.length == 0) { + boolean hasConstructorCall = false; + if (method instanceof ConstructorDeclaration) { + ExplicitConstructorCall constructorCall = ((ConstructorDeclaration) method).constructorCall; + hasConstructorCall = constructorCall != null && !constructorCall.isImplicitSuper() && !constructorCall.isImplicitThis(); + } + + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } + + return; + } Statement[] contents = method.statements; @@ -160,9 +176,9 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> { } private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source, AbstractMethodDeclaration method) { - int methodStart = method.bodyStart; - int methodEnd = method.bodyEnd; - long methodPosEnd = methodEnd << 32 | (methodEnd & 0xFFFFFFFFL); + int methodStart = method.bodyStart; + int methodEnd = method.bodyEnd; + long methodPosEnd = ((long) methodEnd) << 32 | (methodEnd & 0xFFFFFFFFL); TryStatement tryStatement = new TryStatement(); setGeneratedBy(tryStatement, source); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index 75d4acef..1193af31 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } } - private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, + static MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); @@ -209,21 +209,25 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, fieldAccess); + TypeReference fieldType = getFieldType(field, fieldAccess); Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); + // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. + boolean fieldBaseTypeIsPrimitive = BUILT_IN_TYPES.contains(new String(fieldType.getLastToken())); + boolean fieldIsPrimitive = fieldType.dimensions() == 0 && fieldBaseTypeIsPrimitive; + boolean fieldIsPrimitiveArray = fieldType.dimensions() == 1 && fieldBaseTypeIsPrimitive; + boolean fieldIsObjectArray = fieldType.dimensions() > 0 && !fieldIsPrimitiveArray; + @SuppressWarnings("unused") + boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; + Expression ex; - if (fType.dimensions() > 0) { + if (fieldIsPrimitiveArray || fieldIsObjectArray) { MessageSend arrayToString = new MessageSend(); arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE; arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); arrayToString.arguments = new Expression[] { fieldAccessor }; setGeneratedBy(arrayToString.arguments[0], source); - if (fType.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(fType.getLastToken()))) { - arrayToString.selector = "deepToString".toCharArray(); - } else { - arrayToString.selector = "toString".toCharArray(); - } + arrayToString.selector = (fieldIsObjectArray ? "deepToString" : "toString").toCharArray(); ex = arrayToString; } else { ex = fieldAccessor; @@ -278,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { return method; } - private String getTypeName(EclipseNode type) { + private static String getTypeName(EclipseNode type) { String typeName = getSingleTypeName(type); EclipseNode upType = type.up(); while (upType.getKind() == Kind.TYPE) { @@ -288,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { return typeName; } - private String getSingleTypeName(EclipseNode type) { + private static String getSingleTypeName(EclipseNode type) { TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); char[] rawTypeName = typeDeclaration.name; return rawTypeName == null ? "" : new String(rawTypeName); @@ -297,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( "byte", "short", "int", "long", "char", "boolean", "double", "float"))); - private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { + private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; NameReference ref; diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index b69b1669..0607137b 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -30,8 +30,9 @@ import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -78,6 +79,6 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 9d74cbd1..27fbc635 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String witherName = toWitherName(fieldNode, isBoolean); if (witherName == null) { diff --git a/src/core/lombok/eclipse/handlers/NonNullHandler.java b/src/core/lombok/eclipse/handlers/NonNullHandler.java new file mode 100644 index 00000000..5c58069c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/NonNullHandler.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 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.eclipse.handlers; + +import java.util.Arrays; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.mangosdk.spi.ProviderFor; + +import lombok.NonNull; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.DeferUntilPostDiet; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +@DeferUntilPostDiet +@ProviderFor(EclipseAnnotationHandler.class) +public class NonNullHandler extends EclipseAnnotationHandler<NonNull> { + @Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + Argument arg; + AbstractMethodDeclaration declaration; + + try { + arg = (Argument) annotationNode.up().get(); + declaration = (AbstractMethodDeclaration) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + Statement nullCheck = generateNullCheck(arg, ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + if (declaration.statements == null) { + declaration.statements = new Statement[] {nullCheck}; + } else { + char[] expectedName = arg.name; + for (Statement stat : declaration.statements) { + char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (Arrays.equals(expectedName, varNameOfNullCheck)) return; + } + + Statement[] newStatements = new Statement[declaration.statements.length + 1]; + int skipOver = 0; + for (Statement stat : declaration.statements) { + if (isGenerated(stat)) skipOver++; + else break; + } + System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver); + System.arraycopy(declaration.statements, skipOver, newStatements, skipOver + 1, declaration.statements.length - skipOver); + newStatements[skipOver] = nullCheck; + declaration.statements = newStatements; + } + annotationNode.up().up().rebuild(); + } + + private char[] returnVarNameIfNullCheck(Statement stat) { + if (!(stat instanceof IfStatement)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + Statement then = ((IfStatement) stat).thenStatement; + if (then instanceof Block) { + Statement[] blockStatements = ((Block) then).statements; + if (blockStatements == null || blockStatements.length == 0) return null; + then = blockStatements[0]; + } + + if (!(then instanceof ThrowStatement)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + Expression cond = ((IfStatement) stat).condition; + if (!(cond instanceof EqualExpression)) return null; + EqualExpression bin = (EqualExpression) cond; + int operatorId = ((bin.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT); + if (operatorId != OperatorIds.EQUAL_EQUAL) return null; + if (!(bin.left instanceof SingleNameReference)) return null; + if (!(bin.right instanceof NullLiteral)) return null; + return ((SingleNameReference) bin.left).token; + } + } +} diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java new file mode 100644 index 00000000..1300e7d3 --- /dev/null +++ b/src/core/lombok/experimental/Builder.java @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2013 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.experimental; + +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.*; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * The builder annotation creates a so-called 'builder' aspect to the class that is annotated or the class + * that contains a member which is annotated with {@code @Builder}. + * <p> + * If a member is annotated, it must be either a constructor or a static method. If a class is annotated, + * then a private constructor is generated with all fields as arguments + * (as if {@code @AllArgsConstructor(AccessLevel.PRIVATE)} is present + * on the class), and it is as if this constructor has been annotated with {@code @Builder} instead. + * <p> + * The effect of {@code @Builder} is that an inner class is generated named <code><strong>T</strong>Builder</code>, + * with a private constructor. Instances of <code><strong>T</strong>Builder</code> are made with the static + * method named {@code builder()} which is also generated for you in the class itself (not in the builder class). + * <p> + * The <code><strong>T</strong>Builder</code> class contains 1 method for each parameter of the annotated + * constructor / static method (each field, when annotating a class), which returns the builder itself. + * The builder also has a <code>build()</code> method which returns a completed instance of the original type, + * created by passing all parameters as set via the various other methods in the builder to the constructor + * or static method that was annotated with {@code @Builder}. The return type of this method will be the same + * as the relevant class, unless a static method has been annotated, in which case it'll be equal to the + * return type of that method. + * <p> + * Complete documentation is found at <a href="http://projectlombok.org/features/experimental/Builder.html">the project lombok features page for @Builder</a>. + * <p> + * <p> + * Before: + * + * <pre> + * @Builder + * class Example { + * private int foo; + * private final String bar; + * } + * </pre> + * + * After: + * + * <pre> + * class Example<T> { + * private T foo; + * private final String bar; + * + * private Example(T foo, String bar) { + * this.foo = foo; + * this.bar = bar; + * } + * + * public static <T> ExampleBuilder<T> builder() { + * return new ExampleBuilder<T>(); + * } + * + * public static class ExampleBuilder<T> { + * private T foo; + * private String bar; + * + * private ExampleBuilder() {} + * + * public ExampleBuilder foo(T foo) { + * this.foo = foo; + * return this; + * } + * + * public ExampleBuilder bar(String bar) { + * this.bar = bar; + * return this; + * } + * + * @java.lang.Override public String toString() { + * return "ExampleBuilder(foo = " + foo + ", bar = " + bar + ")"; + * } + * + * public Example build() { + * return new Example(foo, bar); + * } + * } + * } + * </pre> + */ +@Target({TYPE, METHOD, CONSTRUCTOR}) +@Retention(SOURCE) +public @interface Builder { + /** Name of the static method that creates a new builder instance. Default: {@code builder}. */ + String builderMethodName() default "builder"; + + /** Name of the instance method in the builder class that creates an instance of your {@code @Builder}-annotated class. */ + String buildMethodName() default "build"; + + /** Name of the builder class. + * Default for {@code @Builder} on types and constructors: {@code (TypeName)Builder}. + * Default for {@code @Builder} on static methods: {@code (ReturnTypeName)Builder}. + */ + String builderClassName() default ""; + + /** + * Normally the builder's 'set' methods are fluent, meaning, they have the same name as the field. Set this + * to {@code false} to name the setter method for field {@code someField}: {@code setSomeField}. + * <p> + * <strong>Default: true</strong> + */ + boolean fluent() default true; + + /** + * Normally the builder's 'set' methods are chaining, meaning, they return the builder so that you can chain + * calls to set methods. Set this to {@code false} to have these 'set' methods return {@code void} instead. + * <p> + * <strong>Default: true</strong> + */ + boolean chain() default true; +} diff --git a/src/core/lombok/experimental/Value.java b/src/core/lombok/experimental/Value.java index 048066df..b7700bb5 100644 --- a/src/core/lombok/experimental/Value.java +++ b/src/core/lombok/experimental/Value.java @@ -39,9 +39,11 @@ import java.lang.annotation.Target; * @see lombok.ToString * @see lombok.EqualsAndHashCode * @see lombok.Data + * @deprecated {@link lombok.Value} has been promoted to the main package, so use that one instead. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) +@Deprecated public @interface Value { /** * If you specify a static constructor name, then the generated constructor will be private, and diff --git a/src/core/lombok/extern/apachecommons/CommonsLog.java b/src/core/lombok/extern/apachecommons/CommonsLog.java index f178ae05..024e3744 100644 --- a/src/core/lombok/extern/apachecommons/CommonsLog.java +++ b/src/core/lombok/extern/apachecommons/CommonsLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -52,6 +52,7 @@ import java.lang.annotation.Target; * @see org.apache.commons.logging.LogFactory#getLog(java.lang.Class) org.apache.commons.logging.LogFactory.getLog(Class target) * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j */ diff --git a/src/core/lombok/extern/java/Log.java b/src/core/lombok/extern/java/Log.java index 90c62956..7ae4e07b 100644 --- a/src/core/lombok/extern/java/Log.java +++ b/src/core/lombok/extern/java/Log.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see java.util.logging.Logger#getLogger(java.lang.String) java.util.logging.Logger.getLogger(String name) * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j * @see lombok.extern.slf4j.XSlf4j @XSlf4j */ diff --git a/src/core/lombok/extern/log4j/Log4j.java b/src/core/lombok/extern/log4j/Log4j.java index 9cfc5839..29e1b27c 100644 --- a/src/core/lombok/extern/log4j/Log4j.java +++ b/src/core/lombok/extern/log4j/Log4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -50,6 +50,7 @@ import java.lang.annotation.Target; * * @see org.apache.log4j.Logger org.apache.log4j.Logger * @see org.apache.log4j.Logger#getLogger(java.lang.Class) org.apache.log4j.Logger.getLogger(Class target) + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.slf4j.Slf4j @Slf4j diff --git a/src/core/lombok/extern/log4j/Log4j2.java b/src/core/lombok/extern/log4j/Log4j2.java new file mode 100644 index 00000000..2a0f09e1 --- /dev/null +++ b/src/core/lombok/extern/log4j/Log4j2.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 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.extern.log4j; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Causes lombok to generate a logger field. + * <p> + * Complete documentation is found at <a href="http://projectlombok.org/features/Log.html">the project lombok features page for lombok log annotations</a>. + * <p> + * Example: + * <pre> + * @Log4j2 + * public class LogExample { + * } + * </pre> + * + * will generate: + * + * <pre> + * public class LogExample { + * private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.Logger.getLogger(LogExample.class); + * } + * </pre> + * + * This annotation is valid for classes and enumerations.<br /> + * + * @see org.apache.logging.log4j.Logger org.apache.logging.log4j.Logger + * @see org.apache.logging.log4j.LogManager#getLogger(java.lang.Class) org.apache.logging.log4j.LogManager.getLogger(Class target) + * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.apachecommons.CommonsLog @CommonsLog + * @see lombok.extern.java.Log @Log + * @see lombok.extern.slf4j.Slf4j @Slf4j + * @see lombok.extern.slf4j.XSlf4j @XSlf4j + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +public @interface Log4j2 { +}
\ No newline at end of file diff --git a/src/core/lombok/extern/slf4j/Slf4j.java b/src/core/lombok/extern/slf4j/Slf4j.java index 14dbcba6..45942971 100644 --- a/src/core/lombok/extern/slf4j/Slf4j.java +++ b/src/core/lombok/extern/slf4j/Slf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 */ @Retention(RetentionPolicy.SOURCE) @Target(ElementType.TYPE) diff --git a/src/core/lombok/extern/slf4j/XSlf4j.java b/src/core/lombok/extern/slf4j/XSlf4j.java index bdf8a62c..599c68ab 100644 --- a/src/core/lombok/extern/slf4j/XSlf4j.java +++ b/src/core/lombok/extern/slf4j/XSlf4j.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Project Lombok Authors. + * Copyright (C) 2012-2013 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 @@ -51,6 +51,7 @@ import java.lang.annotation.Target; * @see lombok.extern.apachecommons.CommonsLog @CommonsLog * @see lombok.extern.java.Log @Log * @see lombok.extern.log4j.Log4j @Log4j + * @see lombok.extern.log4j.Log4j2 @Log4j2 * @see lombok.extern.slf4j.Slf4j @Slf4j */ @Retention(RetentionPolicy.SOURCE) diff --git a/src/core/lombok/javac/FindTypeVarScanner.java b/src/core/lombok/javac/FindTypeVarScanner.java index b1b8e525..7c7d9d50 100644 --- a/src/core/lombok/javac/FindTypeVarScanner.java +++ b/src/core/lombok/javac/FindTypeVarScanner.java @@ -88,7 +88,7 @@ public class FindTypeVarScanner extends AbstractTypeVisitor6<Void, Void> { @Override public Void visitTypeVariable(TypeVariable t, Void p) { Name name = null; try { - name = ((Type)t).tsym.name; + name = ((Type) t).tsym.name; } catch (NullPointerException e) {} if (name != null) typeVariables.add(name.toString()); subVisit(t.getLowerBound()); diff --git a/src/core/lombok/javac/HandlerLibrary.java b/src/core/lombok/javac/HandlerLibrary.java index 2be84355..4306b5f2 100644 --- a/src/core/lombok/javac/HandlerLibrary.java +++ b/src/core/lombok/javac/HandlerLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -231,23 +231,23 @@ public class HandlerLibrary { * @param annotation 'node.get()' - convenience parameter. */ public void handleAnnotation(JCCompilationUnit unit, JavacNode node, JCAnnotation annotation, long priority) { - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); String rawType = annotation.annotationType.toString(); - for (String fqn : resolver.findTypeMatches(node, typeLibrary, rawType)) { - AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); - if (container == null) continue; - - try { - if (container.getPriority() == priority) { - if (checkAndSetHandled(annotation)) container.handle(node); - } - } catch (AnnotationValueDecodeFail fail) { - fail.owner.setError(fail.getMessage(), fail.idx); - } catch (Throwable t) { - String sourceName = "(unknown).java"; - if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName(); - javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t); + String fqn = resolver.typeRefToFullyQualifiedName(node, typeLibrary, rawType); + if (fqn == null) return; + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + if (container == null) return; + + try { + if (container.getPriority() == priority) { + if (checkAndSetHandled(annotation)) container.handle(node); } + } catch (AnnotationValueDecodeFail fail) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch (Throwable t) { + String sourceName = "(unknown).java"; + if (unit != null && unit.sourcefile != null) sourceName = unit.sourcefile.getName(); + javacError(String.format("Lombok annotation handler %s failed on " + sourceName, container.handler.getClass()), t); } } diff --git a/src/core/lombok/javac/Javac6BasedLombokOptions.java b/src/core/lombok/javac/Javac6BasedLombokOptions.java index 4bb2bdc3..871e41c4 100644 --- a/src/core/lombok/javac/Javac6BasedLombokOptions.java +++ b/src/core/lombok/javac/Javac6BasedLombokOptions.java @@ -26,7 +26,6 @@ import com.sun.tools.javac.util.Context; import com.sun.tools.javac.util.Options; public class Javac6BasedLombokOptions extends LombokOptions { - public static Javac6BasedLombokOptions replaceWithDelombokOptions(Context context) { Options options = Options.instance(context); context.put(optionsKey, (Options)null); @@ -38,7 +37,7 @@ public class Javac6BasedLombokOptions extends LombokOptions { private Javac6BasedLombokOptions(Context context) { super(context); } - + @Override public void putJavacOption(String optionName, String value) { put(OptionName.valueOf(optionName), value); } diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index 71c17538..36c51210 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.java @@ -24,6 +24,7 @@ package lombok.javac; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import javax.annotation.processing.Messager; @@ -34,10 +35,12 @@ import javax.tools.JavaFileObject; import lombok.core.AST; import com.sun.tools.javac.code.Symtab; +import com.sun.tools.javac.code.Source; import com.sun.tools.javac.model.JavacElements; import com.sun.tools.javac.model.JavacTypes; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCCatch; +import com.sun.tools.javac.tree.JCTree.JCTry; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -46,7 +49,6 @@ import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; -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; @@ -78,7 +80,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { * @param top The compilation unit, which serves as the top level node in the tree to be built. */ public JavacAST(Messager messager, Context context, JCCompilationUnit top) { - super(sourceName(top), packageDeclaration(top), imports(top)); + super(sourceName(top), packageDeclaration(top), new JavacImportList(top)); setTop(buildCompilationUnit(top)); this.context = context; this.messager = messager; @@ -98,16 +100,6 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return (cu.pid instanceof JCFieldAccess || cu.pid instanceof JCIdent) ? cu.pid.toString() : null; } - private static Collection<String> imports(JCCompilationUnit cu) { - List<String> imports = new ArrayList<String>(); - for (JCTree def : cu.defs) { - if (def instanceof JCImport) { - imports.add(((JCImport)def).qualid.toString()); - } - } - return imports; - } - public Context getContext() { return context; } @@ -121,9 +113,20 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { } void traverseChildren(JavacASTVisitor visitor, JavacNode node) { - for (JavacNode child : new ArrayList<JavacNode>(node.down())) { - child.traverse(visitor); - } + for (JavacNode child : node.down()) child.traverse(visitor); + } + + @Override public int getSourceVersion() { + try { + String nm = Source.instance(context).name(); + int underscoreIdx = nm.indexOf('_'); + if (underscoreIdx > -1) return Integer.parseInt(nm.substring(underscoreIdx + 1)); + } catch (Exception ignore) {} + return 6; + } + + @Override public int getLatestJavaSpecSupported() { + return Javac.getJavaCompilerVersion(); } /** @return A Name object generated for the proper name table belonging to this AST. */ @@ -223,6 +226,46 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { return putInMap(new JavacNode(this, local, childNodes, kind)); } + private static boolean JCTRY_RESOURCES_FIELD_INITIALIZED; + private static Field JCTRY_RESOURCES_FIELD; + + @SuppressWarnings("unchecked") + private static List<JCTree> getResourcesForTryNode(JCTry tryNode) { + if (!JCTRY_RESOURCES_FIELD_INITIALIZED) { + try { + JCTRY_RESOURCES_FIELD = JCTry.class.getField("resources"); + } catch (NoSuchFieldException ignore) { + // Java 1.6 or lower won't have this at all. + } catch (Exception ignore) { + // Shouldn't happen. Best thing we can do is just carry on and break on try/catch. + } + JCTRY_RESOURCES_FIELD_INITIALIZED = true; + } + + if (JCTRY_RESOURCES_FIELD == null) return Collections.emptyList(); + Object rv = null; + try { + rv = JCTRY_RESOURCES_FIELD.get(tryNode); + } catch (Exception ignore) {} + + if (rv instanceof List) return (List<JCTree>) rv; + return Collections.emptyList(); + } + + private JavacNode buildTry(JCTry tryNode) { + if (setAndGetAsHandled(tryNode)) return null; + List<JavacNode> childNodes = new ArrayList<JavacNode>(); + for (JCTree varDecl : getResourcesForTryNode(tryNode)) { + if (varDecl instanceof JCVariableDecl) { + addIfNotNull(childNodes, buildLocalVar((JCVariableDecl) varDecl, Kind.LOCAL)); + } + } + addIfNotNull(childNodes, buildStatement(tryNode.body)); + for (JCCatch jcc : tryNode.catchers) addIfNotNull(childNodes, buildTree(jcc, Kind.STATEMENT)); + addIfNotNull(childNodes, buildStatement(tryNode.finalizer)); + return putInMap(new JavacNode(this, tryNode, childNodes, Kind.STATEMENT)); + } + private JavacNode buildInitializer(JCBlock initializer) { if (setAndGetAsHandled(initializer)) return null; List<JavacNode> childNodes = new ArrayList<JavacNode>(); @@ -264,6 +307,7 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { if (statement instanceof JCAnnotation) return null; if (statement instanceof JCClassDecl) return buildType((JCClassDecl)statement); if (statement instanceof JCVariableDecl) return buildLocalVar((JCVariableDecl)statement, Kind.LOCAL); + if (statement instanceof JCTry) return buildTry((JCTry) statement); if (setAndGetAsHandled(statement)) return null; @@ -271,9 +315,18 @@ public class JavacAST extends AST<JavacAST, JavacNode, JCTree> { } private JavacNode drill(JCTree statement) { - List<JavacNode> childNodes = new ArrayList<JavacNode>(); - for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa)); - return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT)); + try { + List<JavacNode> childNodes = new ArrayList<JavacNode>(); + for (FieldAccess fa : fieldsOf(statement.getClass())) childNodes.addAll(buildWithField(JavacNode.class, statement, fa)); + return putInMap(new JavacNode(this, statement, childNodes, Kind.STATEMENT)); + } catch (OutOfMemoryError oome) { + String msg = oome.getMessage(); + if (msg == null) msg = "(no original message)"; + OutOfMemoryError newError = new OutOfMemoryError(getFileName() + "@pos" + statement.getPreferredPosition() + ": " + msg); + // We could try to set the stack trace of the new exception to the same one as the old exception, but this costs memory, + // and we're already in an extremely fragile situation in regards to remaining heap space, so let's not do that. + throw newError; + } } /** For javac, both JCExpression and JCStatement are considered as valid children types. */ diff --git a/src/core/lombok/javac/JavacAnnotationHandler.java b/src/core/lombok/javac/JavacAnnotationHandler.java index 169e2026..a86aa6c6 100644 --- a/src/core/lombok/javac/JavacAnnotationHandler.java +++ b/src/core/lombok/javac/JavacAnnotationHandler.java @@ -33,7 +33,7 @@ import com.sun.tools.javac.tree.JCTree.JCAnnotation; * * You MUST replace 'T' with a specific annotation type, such as: * - * {@code public class HandleGetter implements JavacAnnotationHandler<Getter>} + * {@code public class HandleGetter extends JavacAnnotationHandler<Getter>} * * Because this generics parameter is inspected to figure out which class you're interested in. * diff --git a/src/core/lombok/javac/JavacImportList.java b/src/core/lombok/javac/JavacImportList.java new file mode 100644 index 00000000..d5d7460a --- /dev/null +++ b/src/core/lombok/javac/JavacImportList.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2013 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.util.ArrayList; +import java.util.Collection; +import java.util.Map; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCompilationUnit; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCImport; +import com.sun.tools.javac.util.List; + +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; + +public class JavacImportList implements ImportList { + private final JCExpression pkg; + private final List<JCTree> defs; + + public JavacImportList(JCCompilationUnit cud) { + this.pkg = cud.pid; + this.defs = cud.defs; + } + + @Override public String getFullyQualifiedNameForSimpleName(String unqualified) { + for (JCTree def : defs) { + if (!(def instanceof JCImport)) continue; + JCTree qual = ((JCImport) def).qualid; + if (!(qual instanceof JCFieldAccess)) continue; + String simpleName = ((JCFieldAccess) qual).name.toString(); + if (simpleName.equals(unqualified)) { + return LombokInternalAliasing.processAliases(qual.toString()); + } + } + + return null; + } + + @Override public boolean hasStarImport(String packageName) { + for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } + if (pkg != null && pkg.toString().equals(packageName)) return true; + if ("java.lang".equals(packageName)) return true; + + for (JCTree def : defs) { + if (!(def instanceof JCImport)) continue; + if (((JCImport) def).staticImport) continue; + JCTree qual = ((JCImport) def).qualid; + if (!(qual instanceof JCFieldAccess)) continue; + String simpleName = ((JCFieldAccess) qual).name.toString(); + if (!"*".equals(simpleName)) continue; + if (packageName.equals(((JCFieldAccess) qual).selected.toString())) return true; + } + + return false; + } + + @Override public Collection<String> applyNameToStarImports(String startsWith, String name) { + ArrayList<String> out = new ArrayList<String>(); + + if (pkg != null && topLevelName(pkg).equals(startsWith)) out.add(pkg.toString() + "." + name); + + for (JCTree def : defs) { + if (!(def instanceof JCImport)) continue; + if (((JCImport) def).staticImport) continue; + JCTree qual = ((JCImport) def).qualid; + if (!(qual instanceof JCFieldAccess)) continue; + String simpleName = ((JCFieldAccess) qual).name.toString(); + if (!"*".equals(simpleName)) continue; + + String topLevelName = topLevelName(qual); + if (topLevelName.equals(startsWith)) { + out.add(((JCFieldAccess) qual).selected.toString() + "." + name); + } + } + + return out; + } + + private String topLevelName(JCTree tree) { + while (tree instanceof JCFieldAccess) tree = ((JCFieldAccess) tree).selected; + return tree.toString(); + } + + @Override public String applyUnqualifiedNameToPackage(String unqualified) { + if (pkg == null) return unqualified; + return pkg.toString() + "." + unqualified; + } +} diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index e5d8ed38..82ce0cb8 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -433,8 +433,8 @@ public class JavacResolution { if (symbol.name.length() == 0) { // Anonymous inner class if (type instanceof ClassType) { - List<Type> ifaces = ((ClassType)type).interfaces_field; - Type supertype = ((ClassType)type).supertype_field; + List<Type> ifaces = ((ClassType) type).interfaces_field; + Type supertype = ((ClassType) type).supertype_field; if (ifaces != null && ifaces.length() == 1) { return typeToJCTree(ifaces.get(0), ast, allowCompound, allowVoid); } diff --git a/src/core/lombok/javac/LombokOptions.java b/src/core/lombok/javac/LombokOptions.java index 66e42fe7..f1567e9d 100644 --- a/src/core/lombok/javac/LombokOptions.java +++ b/src/core/lombok/javac/LombokOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -49,6 +49,6 @@ public abstract class LombokOptions extends Options { protected LombokOptions(Context context) { super(context); } - + public abstract void putJavacOption(String optionName, String value); } diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index 96150b06..110acaad 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -62,10 +62,6 @@ import com.sun.tools.javac.util.Context; /** * This Annotation Processor is the standard injection mechanism for lombok-enabling the javac compiler. * - * Due to lots of changes in the core javac code, as well as lombok's heavy usage of non-public API, this - * code only works for the javac v1.6 compiler; it definitely won't work for javac v1.5, and it probably - * won't work for javac v1.7 without modifications. - * * To actually enable lombok in a javac compilation run, this class should be in the classpath when * running javac; that's the only requirement. */ diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java new file mode 100644 index 00000000..e60819da --- /dev/null +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -0,0 +1,330 @@ +/* + * Copyright (C) 2013 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.handlers; + +import java.util.ArrayList; +import java.util.Collections; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCClassDecl; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCFieldAccess; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeApply; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +import lombok.AccessLevel; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; +import lombok.experimental.Builder; +import lombok.experimental.NonFinal; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; +import static lombok.javac.Javac.*; +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +@ProviderFor(JavacAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. +public class HandleBuilder extends JavacAnnotationHandler<Builder> { + @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) buildMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } + + deleteAnnotationIfNeccessary(annotationNode, Builder.class); + deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + + JavacNode parent = annotationNode.up(); + + java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>(); + java.util.List<Name> namesOfParameters = new ArrayList<Name>(); + JCExpression returnType; + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrownExceptions = List.nil(); + Name nameOfStaticBuilderMethod; + JavacNode tdParent; + + JCMethodDecl fillParametersFrom = parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null; + + if (parent.get() instanceof JCClassDecl) { + tdParent = parent; + JCClassDecl td = (JCClassDecl) tdParent.get(); + ListBuffer<JavacNode> allFields = ListBuffer.lb(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); + for (JavacNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + JCVariableDecl fd = (JCVariableDecl) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.vartype); + allFields.append(fieldNode); + } + + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, true, annotationNode); + + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + thrownExceptions = List.nil(); + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; + } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) { + if (!fillParametersFrom.typarams.isEmpty()) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + tdParent = parent.up(); + JCClassDecl td = (JCClassDecl) tdParent.get(); + returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); + typeParams = td.typarams; + thrownExceptions = fillParametersFrom.thrown; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; + } else if (fillParametersFrom != null) { + tdParent = parent.up(); + JCClassDecl td = (JCClassDecl) tdParent.get(); + if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + returnType = fillParametersFrom.restype; + typeParams = fillParametersFrom.typarams; + thrownExceptions = fillParametersFrom.thrown; + nameOfStaticBuilderMethod = fillParametersFrom.name; + if (builderClassName.isEmpty()) { + if (returnType instanceof JCTypeApply) { + returnType = ((JCTypeApply) returnType).clazz; + } + if (returnType instanceof JCFieldAccess) { + builderClassName = ((JCFieldAccess) returnType).name.toString() + "Builder"; + } else if (returnType instanceof JCIdent) { + Name n = ((JCIdent) returnType).name; + + for (JCTypeParameter tp : typeParams) { + if (tp.name.equals(n)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + builderClassName = n.toString() + "Builder"; + } else if (returnType instanceof JCPrimitiveTypeTree) { + builderClassName = returnType.toString() + "Builder"; + if (Character.isLowerCase(builderClassName.charAt(0))) { + builderClassName = Character.toTitleCase(builderClassName.charAt(0)) + builderClassName.substring(1); + } + + } else { + // This shouldn't happen. + System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass()); + builderClassName = td.name.toString() + "Builder"; + } + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + + if (fillParametersFrom != null) { + for (JCVariableDecl param : fillParametersFrom.params) { + namesOfParameters.add(param.name); + typesOfParameters.add(param.vartype); + } + } + + JavacNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } + java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>(); + for (JavacNode fieldNode : fieldNodes) { + JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain()); + if (newMethod != null) newMethods.add(newMethod); + } + + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.<JCAnnotation>nil(), builderType, List.<JavacNode>nil(), true, ast); + if (cd != null) injectMethod(builderType, cd); + } + + for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); + if (md != null) injectMethod(tdParent, md); + } + } + + private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) { + TreeMaker maker = type.getTreeMaker(); + + JCExpression call; + JCStatement statement; + + ListBuffer<JCExpression> args = ListBuffer.lb(); + for (Name n : fieldNames) { + args.append(maker.Ident(n)); + } + + if (staticName == null) { + call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null); + statement = maker.Return(call); + } else { + ListBuffer<JCExpression> typeParams = ListBuffer.lb(); + for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { + typeParams.append(maker.Ident(tp.name)); + } + + JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); + call = maker.Apply(typeParams.toList(), fn, args.toList()); + if (returnType instanceof JCPrimitiveTypeTree && compareCTC(getTypeTag((JCPrimitiveTypeTree) returnType), CTC_VOID)) { + statement = maker.Exec(call); + } else { + statement = maker.Return(call); + } + } + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); + } + + private JCMethodDecl generateBuilderMethod(String builderMethodName, String builderClassName, JavacNode type, List<JCTypeParameter> typeParams) { + TreeMaker maker = type.getTreeMaker(); + + ListBuffer<JCExpression> typeArgs = ListBuffer.lb(); + for (JCTypeParameter typeParam : typeParams) { + typeArgs.append(maker.Ident(typeParam.name)); + } + + JCExpression call = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), List.<JCExpression>nil(), null); + JCStatement statement = maker.Return(call); + + JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + } + + private java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) { + int len = namesOfParameters.size(); + java.util.List<JavacNode> existing = new ArrayList<JavacNode>(); + for (JavacNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } + + java.util.List<JavacNode>out = new ArrayList<JavacNode>(); + + top: + for (int i = len - 1; i >= 0; i--) { + Name name = namesOfParameters.get(i); + for (JavacNode exists : existing) { + Name n = ((JCVariableDecl) exists.get()).name; + if (n.equals(name)) { + out.add(exists); + continue top; + } + } + TreeMaker maker = builderType.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source), null); + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + return out; + } + + + private JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JCTree source, boolean fluent, boolean chain) { + Name fieldName = ((JCVariableDecl) fieldNode.get()).name; + + for (JavacNode child : builderType.down()) { + if (child.getKind() != Kind.METHOD) continue; + Name existingName = ((JCMethodDecl) child.get()).name; + if (existingName.equals(fieldName)) return null; + } + + boolean isBoolean = isBoolean(fieldNode); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + + TreeMaker maker = builderType.getTreeMaker(); + return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + } + + private JavacNode findInnerClass(JavacNode parent, String name) { + for (JavacNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + JCClassDecl td = (JCClassDecl) child.get(); + if (td.name.contentEquals(name)) return child; + } + return null; + } + + private JavacNode makeBuilderClass(JavacNode tdParent, String builderClassName, List<JCTypeParameter> typeParams, JCAnnotation ast) { + TreeMaker maker = tdParent.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC | Flags.STATIC); + JCClassDecl builder = ClassDef(maker, mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); + return injectType(tdParent, builder); + } +} diff --git a/src/core/lombok/javac/handlers/HandleCleanup.java b/src/core/lombok/javac/handlers/HandleCleanup.java index c75256a5..790d8964 100644 --- a/src/core/lombok/javac/handlers/HandleCleanup.java +++ b/src/core/lombok/javac/handlers/HandleCleanup.java @@ -32,7 +32,6 @@ import lombok.javac.JavacNode; import org.mangosdk.spi.ProviderFor; -import com.sun.tools.javac.code.TypeTags; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCAssign; diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index bb883ca4..ecd982e9 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -29,6 +29,7 @@ import lombok.RequiredArgsConstructor; import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.core.AST.Kind; +import lombok.experimental.Builder; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; @@ -66,7 +67,7 @@ public class HandleConstructor { String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; List<JavacNode> fields = List.nil(); - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, false, false, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, fields, staticName, SkipIfConstructorExists.NO, false, annotationNode); } } @@ -84,7 +85,7 @@ public class HandleConstructor { @SuppressWarnings("deprecation") boolean suppressConstructorProperties = ann.suppressConstructorProperties(); if (level == AccessLevel.NONE) return; - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } @@ -119,11 +120,11 @@ public class HandleConstructor { @SuppressWarnings("deprecation") boolean suppressConstructorProperties = ann.suppressConstructorProperties(); if (level == AccessLevel.NONE) return; - new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, false, suppressConstructorProperties, annotationNode); + new HandleConstructor().generateConstructor(typeNode, level, onConstructor, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, annotationNode); } } - private static List<JavacNode> findAllFields(JavacNode typeNode) { + static List<JavacNode> findAllFields(JavacNode typeNode) { ListBuffer<JavacNode> fields = ListBuffer.lb(); for (JavacNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -154,25 +155,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) { + public void generateRequiredArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, source); } - public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, JavacNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateAllArgsConstructor(JavacNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, JavacNode source) { generateConstructor(typeNode, level, List.<JCAnnotation>nil(), findAllFields(typeNode), staticName, skipIfConstructorExists, false, source); } - public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) { + public void generateConstructor(JavacNode typeNode, AccessLevel level, List<JCAnnotation> onConstructor, List<JavacNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, JavacNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (JavacNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -207,7 +217,7 @@ public class HandleConstructor { mods.annotations = mods.annotations.append(annotation); } - private JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean suppressConstructorProperties, JCTree source) { + static JCMethodDecl createConstructor(AccessLevel level, List<JCAnnotation> onConstructor, JavacNode typeNode, List<JavacNode> fields, boolean suppressConstructorProperties, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); boolean isEnum = (((JCClassDecl) typeNode.get()).mods.flags & Flags.ENUM) != 0; @@ -243,7 +253,7 @@ public class HandleConstructor { null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), maker.Block(0L, nullChecks.appendList(assigns).toList()), null), source); } - private boolean isLocalType(JavacNode type) { + private static boolean isLocalType(JavacNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); diff --git a/src/core/lombok/javac/handlers/HandleData.java b/src/core/lombok/javac/handlers/HandleData.java index 62183a15..858fb543 100644 --- a/src/core/lombok/javac/handlers/HandleData.java +++ b/src/core/lombok/javac/handlers/HandleData.java @@ -27,6 +27,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; @@ -50,7 +51,7 @@ public class HandleData extends JavacAnnotationHandler<Data> { String staticConstructorName = annotation.getInstance().staticConstructor(); // TODO move this to the end OR move it to the top in eclipse. - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, true, annotationNode); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); diff --git a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java index 39edb143..741e7e21 100644 --- a/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/javac/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -25,6 +25,7 @@ import static lombok.javac.Javac.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import lombok.EqualsAndHashCode; @@ -178,17 +179,26 @@ public class HandleEqualsAndHashCode extends JavacAnnotationHandler<EqualsAndHas boolean isFinal = (((JCClassDecl)typeNode.get()).mods.flags & Flags.FINAL) != 0; boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; - java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); source.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + source.addWarning(msg); } return; case NOT_EXISTS: diff --git a/src/core/lombok/javac/handlers/HandleFieldDefaults.java b/src/core/lombok/javac/handlers/HandleFieldDefaults.java index d32446c3..038f3e3f 100644 --- a/src/core/lombok/javac/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/javac/handlers/HandleFieldDefaults.java @@ -44,7 +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. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks 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) { @@ -108,6 +108,14 @@ public class HandleFieldDefaults extends JavacAnnotationHandler<FieldDefaults> { return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/javac/handlers/HandleGetter.java b/src/core/lombok/javac/handlers/HandleGetter.java index 2fa2a755..c5ec6f60 100644 --- a/src/core/lombok/javac/handlers/HandleGetter.java +++ b/src/core/lombok/javac/handlers/HandleGetter.java @@ -53,11 +53,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCIf; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; -import com.sun.tools.javac.tree.JCTree.JCNewClass; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; import com.sun.tools.javac.tree.JCTree.JCStatement; import com.sun.tools.javac.tree.JCTree.JCSynchronized; -import com.sun.tools.javac.tree.JCTree.JCTypeApply; import com.sun.tools.javac.tree.JCTree.JCTypeParameter; import com.sun.tools.javac.tree.JCTree.JCVariableDecl; import com.sun.tools.javac.tree.TreeMaker; @@ -256,6 +254,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { if (toClearOfMarkers != null) recursiveSetGeneratedBy(toClearOfMarkers, null); decl.mods.annotations = decl.mods.annotations.appendList(delegates); + copyJavadoc(field, decl, CopyJavadoc.GETTER); return decl; } @@ -287,6 +286,7 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { } private static final String AR = "java.util.concurrent.atomic.AtomicReference"; + private static final String JLO = "java.lang.Object"; private static final List<JCExpression> NIL_EXPRESSION = List.nil(); private static final java.util.Map<Object, String> TYPE_MAP; @@ -305,37 +305,50 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { private List<JCStatement> createLazyGetterBody(TreeMaker maker, JavacNode fieldNode, JCTree source) { /* - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); - if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + if (value == null) { + final RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ ListBuffer<JCStatement> statements = ListBuffer.lb(); JCVariableDecl field = (JCVariableDecl) fieldNode.get(); JCExpression copyOfRawFieldType = copyType(maker, field); + JCExpression copyOfBoxedFieldType = null; field.type = null; + boolean isPrimitive = false; if (field.vartype instanceof JCPrimitiveTypeTree) { String boxed = TYPE_MAP.get(((JCPrimitiveTypeTree)field.vartype).typetag); if (boxed != null) { + isPrimitive = true; field.vartype = chainDotsString(fieldNode, boxed); + copyOfBoxedFieldType = chainDotsString(fieldNode, boxed); } } + if (copyOfBoxedFieldType == null) copyOfBoxedFieldType = copyType(maker, field); Name valueName = fieldNode.toName("value"); Name actualValueName = fieldNode.toName("actualValue"); - /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get();*/ { - JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))); + /* java.lang.Object value = this.fieldName.get();*/ { + JCExpression valueVarType = chainDotsString(fieldNode, JLO); statements.append(maker.VarDef(maker.Modifiers(0), valueName, valueVarType, callGet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)))); } @@ -350,15 +363,23 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { /* if (value == null) { */ { ListBuffer<JCStatement> innerIfStatements = ListBuffer.lb(); - /* ValueType actualValue = new ValueType(); */ { + /* final RawValueType actualValue = INITIALIZER_EXPRESSION; */ { innerIfStatements.append(maker.VarDef(maker.Modifiers(Flags.FINAL), actualValueName, copyOfRawFieldType, field.init)); } - /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue);*/ { - JCTypeApply valueVarType = maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))); - JCNewClass newInstance = maker.NewClass(null, NIL_EXPRESSION, valueVarType, List.<JCExpression>of(maker.Ident(actualValueName)), null); - - JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), newInstance)); - innerIfStatements.append(statement); + /* [IF primitive] value = actualValue; */ { + if (isPrimitive) { + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), maker.Ident(actualValueName))); + innerIfStatements.append(statement); + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + JCExpression actualValueIsNull = Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(actualValueName), Javac.makeLiteral(maker, CTC_BOT, null)); + JCExpression thisDotFieldName = createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD); + JCExpression ternary = maker.Conditional(actualValueIsNull, thisDotFieldName, maker.Ident(actualValueName)); + JCStatement statement = maker.Exec(maker.Assign(maker.Ident(valueName), ternary)); + innerIfStatements.append(statement); + } } /* this.fieldName.set(value); */ { JCStatement statement = callSet(fieldNode, createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD), maker.Ident(valueName)); @@ -377,15 +398,25 @@ public class HandleGetter extends JavacAnnotationHandler<Getter> { JCIf ifStatement = maker.If(isNull, maker.Block(0, List.<JCStatement>of(synchronizedStatement)), null); statements.append(ifStatement); } - /* return value.get(); */ - statements.append(maker.Return(callGet(fieldNode, maker.Ident(valueName)))); + /* [IF PRIMITIVE] return (BoxedValueType) value; */ { + if (isPrimitive) { + statements.append(maker.Return(maker.TypeCast(copyOfBoxedFieldType, maker.Ident(valueName)))); + } + } + /* [ELSE] return (BoxedValueType) (value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + JCExpression valueEqualsSelf = Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(valueName), createFieldAccessor(maker, fieldNode, FieldAccess.ALWAYS_FIELD)); + JCExpression ternary = maker.Conditional(valueEqualsSelf, Javac.makeLiteral(maker, CTC_BOT, null), maker.Ident(valueName)); + JCExpression typeCast = maker.TypeCast(copyOfBoxedFieldType, maker.Parens(ternary)); + statements.append(maker.Return(typeCast)); + } + } // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { + /* private final java.util.concurrent.atomic.AtomicReference<Object> fieldName = new java.util.concurrent.atomic.AtomicReference<Object>(); */ { field.vartype = recursiveSetGeneratedBy( - maker.TypeApply(chainDotsString(fieldNode, AR), List.<JCExpression>of(maker.TypeApply(chainDotsString(fieldNode, AR), List.of(copyType(maker, field))))), - source); + maker.TypeApply(chainDotsString(fieldNode, AR), List.<JCExpression>of(chainDotsString(fieldNode, JLO))), source); field.init = recursiveSetGeneratedBy(maker.NewClass(null, NIL_EXPRESSION, copyType(maker, field), NIL_EXPRESSION, null), source); } diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index 62a55c44..35a32be5 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 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 @@ -82,7 +82,7 @@ public class HandleLog { private static boolean createField(LoggingFramework framework, JavacNode typeNode, JCFieldAccess loggingType, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); - // private static final <loggerType> log = <factoryMethod>(<parameter>); + // private static final <loggerType> log = <factoryMethod>(<parameter>); JCExpression loggerType = chainDotsString(typeNode, framework.getLoggerTypeName()); JCExpression factoryMethod = chainDotsString(typeNode, framework.getLoggerFactoryMethodName()); @@ -128,6 +128,16 @@ public class HandleLog { } /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for javac. + */ + @ProviderFor(JavacAnnotationHandler.class) + public static class HandleLog4j2Log extends JavacAnnotationHandler<lombok.extern.log4j.Log4j2> { + @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, JCAnnotation ast, JavacNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, annotationNode); + } + } + + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for javac. */ @ProviderFor(JavacAnnotationHandler.class) @@ -163,6 +173,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J(lombok.extern.log4j.Log4j.class, "org.apache.log4j.Logger", "org.apache.log4j.Logger.getLogger"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2(lombok.extern.log4j.Log4j2.class, "org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager.getLogger"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J(lombok.extern.slf4j.Slf4j.class, "org.slf4j.Logger", "org.slf4j.LoggerFactory.getLogger"), diff --git a/src/core/lombok/javac/handlers/HandleSetter.java b/src/core/lombok/javac/handlers/HandleSetter.java index 2136024e..c3ee10a3 100644 --- a/src/core/lombok/javac/handlers/HandleSetter.java +++ b/src/core/lombok/javac/handlers/HandleSetter.java @@ -26,10 +26,6 @@ import static lombok.javac.handlers.JavacHandlerUtil.*; import java.util.Collection; -import javax.lang.model.type.NoType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeVisitor; - import lombok.AccessLevel; import lombok.Setter; import lombok.core.AST.Kind; @@ -43,7 +39,6 @@ import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; -import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCAssign; @@ -195,9 +190,13 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { injectMethod(fieldNode.up(), createdSetter); } - private JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { String setterName = toSetterName(field); boolean returnThis = shouldReturnThis(field); + return createSetter(access, field, treeMaker, setterName, returnThis, source, onMethod, onParam); + } + + static JCMethodDecl createSetter(long access, JavacNode field, TreeMaker treeMaker, String setterName, boolean shouldReturnThis, JCTree source, List<JCAnnotation> onMethod, List<JCAnnotation> onParam) { if (setterName == null) return null; JCVariableDecl fieldDecl = (JCVariableDecl) field.get(); @@ -223,17 +222,17 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { } JCExpression methodType = null; - if (returnThis) { + if (shouldReturnThis) { methodType = cloneSelfType(field); } if (methodType == null) { //WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6. - methodType = treeMaker.Type(new JCNoType(CTC_VOID)); - returnThis = false; + methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID)); + shouldReturnThis = false; } - if (returnThis) { + if (shouldReturnThis) { JCReturn returnStatement = treeMaker.Return(treeMaker.Ident(field.toName("this"))); statements.append(returnStatement); } @@ -249,26 +248,9 @@ public class HandleSetter extends JavacAnnotationHandler<Setter> { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.<JCExpression>nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, methodType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); - } - - private static class JCNoType extends Type implements NoType { - public JCNoType(Object tag) { - //FIX - super(1, null); - } - - @Override - public TypeKind getKind() { - if (Javac.compareCTC(tag, CTC_VOID)) return TypeKind.VOID; - if (Javac.compareCTC(tag, CTC_NONE)) return TypeKind.NONE; - throw new AssertionError("Unexpected tag: " + tag); - } - - @Override - public <R, P> R accept(TypeVisitor<R, P> v, P p) { - return v.visitNoType(this, p); - } + copyJavadoc(field, decl, CopyJavadoc.SETTER); + return decl; } } diff --git a/src/core/lombok/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java index a5bd74e7..c818f630 100644 --- a/src/core/lombok/javac/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/javac/handlers/HandleSneakyThrows.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2011 The Project Lombok Authors. + * Copyright (C) 2009-2013 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 @@ -36,8 +36,6 @@ import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; import com.sun.tools.javac.tree.JCTree; -import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; -import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.TreeMaker; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCBlock; @@ -84,13 +82,20 @@ public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> { return; } - if (method.body == null) return; - if (method.body.stats.isEmpty()) return; + if (method.body == null || method.body.stats.isEmpty()) { + generateEmptyBlockWarning(methodNode, annotation, false); + return; + } final JCStatement constructorCall = method.body.stats.get(0); final boolean isConstructorCall = isConstructorCall(constructorCall); List<JCStatement> contents = isConstructorCall ? method.body.stats.tail : method.body.stats; + if (contents == null || contents.isEmpty()) { + generateEmptyBlockWarning(methodNode, annotation, true); + return; + } + for (String exception : exceptions) { contents = List.of(buildTryCatchBlock(methodNode, contents, exception, annotation.get())); } @@ -99,14 +104,14 @@ public class HandleSneakyThrows extends JavacAnnotationHandler<SneakyThrows> { methodNode.rebuild(); } - private boolean isConstructorCall(final JCStatement supect) { - if (!(supect instanceof JCExpressionStatement)) return false; - final JCExpression supectExpression = ((JCExpressionStatement) supect).expr; - if (!(supectExpression instanceof JCMethodInvocation)) return false; - final String methodName = ((JCMethodInvocation) supectExpression).meth.toString(); - return "super".equals(methodName) || "this".equals(methodName); + private void generateEmptyBlockWarning(JavacNode methodNode, JavacNode annotation, boolean hasConstructorCall) { + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } } - + private JCStatement buildTryCatchBlock(JavacNode node, List<JCStatement> contents, String exception, JCTree source) { TreeMaker maker = node.getTreeMaker(); diff --git a/src/core/lombok/javac/handlers/HandleToString.java b/src/core/lombok/javac/handlers/HandleToString.java index c37fe149..8297a4db 100644 --- a/src/core/lombok/javac/handlers/HandleToString.java +++ b/src/core/lombok/javac/handlers/HandleToString.java @@ -24,6 +24,8 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; +import java.util.Collection; + import lombok.ToString; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; @@ -165,7 +167,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { } } - private JCMethodDecl createToString(JavacNode typeNode, List<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { + static JCMethodDecl createToString(JavacNode typeNode, Collection<JavacNode> fields, boolean includeFieldNames, boolean callSuper, FieldAccess fieldAccess, JCTree source) { TreeMaker maker = typeNode.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(chainDots(typeNode, "java", "lang", "Override"), List.<JCExpression>nil()); @@ -199,18 +201,22 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { } for (JavacNode fieldNode : fields) { - JCVariableDecl field = (JCVariableDecl) fieldNode.get(); JCExpression expr; JCExpression fieldAccessor = createFieldAccessor(maker, fieldNode, fieldAccess); - if (getFieldType(fieldNode, fieldAccess) instanceof JCArrayTypeTree) { - boolean multiDim = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCArrayTypeTree; - boolean primitiveArray = ((JCArrayTypeTree)field.vartype).elemtype instanceof JCPrimitiveTypeTree; - boolean useDeepTS = multiDim || !primitiveArray; - - JCExpression hcMethod = chainDots(typeNode, "java", "util", "Arrays", useDeepTS ? "deepToString" : "toString"); - expr = maker.Apply(List.<JCExpression>nil(), hcMethod, List.<JCExpression>of(fieldAccessor)); + JCExpression fieldType = getFieldType(fieldNode, fieldAccess); + + // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. + boolean fieldIsPrimitive = fieldType instanceof JCPrimitiveTypeTree; + boolean fieldIsPrimitiveArray = fieldType instanceof JCArrayTypeTree && ((JCArrayTypeTree) fieldType).elemtype instanceof JCPrimitiveTypeTree; + boolean fieldIsObjectArray = !fieldIsPrimitiveArray && fieldType instanceof JCArrayTypeTree; + @SuppressWarnings("unused") + boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; + + if (fieldIsPrimitiveArray || fieldIsObjectArray) { + JCExpression tsMethod = chainDots(typeNode, "java", "util", "Arrays", fieldIsObjectArray ? "deepToString" : "toString"); + expr = maker.Apply(List.<JCExpression>nil(), tsMethod, List.<JCExpression>of(fieldAccessor)); } else expr = fieldAccessor; if (first) { @@ -238,7 +244,7 @@ public class HandleToString extends JavacAnnotationHandler<ToString> { List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null), source); } - private String getTypeName(JavacNode typeNode) { + private static String getTypeName(JavacNode typeNode) { String typeName = ((JCClassDecl) typeNode.get()).name.toString(); JavacNode upType = typeNode.up(); while (upType.getKind() == Kind.TYPE) { diff --git a/src/core/lombok/javac/handlers/HandleValue.java b/src/core/lombok/javac/handlers/HandleValue.java index f5b10bc1..15fb4781 100644 --- a/src/core/lombok/javac/handlers/HandleValue.java +++ b/src/core/lombok/javac/handlers/HandleValue.java @@ -22,13 +22,17 @@ package lombok.javac.handlers; import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.lang.annotation.Annotation; + import lombok.AccessLevel; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; import org.mangosdk.spi.ProviderFor; @@ -44,7 +48,9 @@ import com.sun.tools.javac.tree.JCTree.JCModifiers; @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); + @SuppressWarnings("deprecation") + Class<? extends Annotation> oldExperimentalValue = lombok.experimental.Value.class; + deleteAnnotationIfNeccessary(annotationNode, Value.class, oldExperimentalValue); JavacNode typeNode = annotationNode.up(); boolean notAClass = !isClass(typeNode); @@ -65,7 +71,7 @@ public class HandleValue extends JavacAnnotationHandler<Value> { 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 HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, staticConstructorName, SkipIfConstructorExists.YES, annotationNode); new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index 62ed63f1..514e27dd 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -34,6 +34,7 @@ import lombok.experimental.Wither; import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; +import lombok.javac.handlers.JavacHandlerUtil.CopyJavadoc; import lombok.javac.handlers.JavacHandlerUtil.FieldAccess; import org.mangosdk.spi.ProviderFor; @@ -265,7 +266,9 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { if (isFieldDeprecated(field)) { annsOnMethod = annsOnMethod.prepend(treeMaker.Annotation(chainDots(field, "java", "lang", "Deprecated"), List.<JCExpression>nil())); } - return recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, + JCMethodDecl decl = recursiveSetGeneratedBy(treeMaker.MethodDef(treeMaker.Modifiers(access, annsOnMethod), methodName, returnType, methodGenericParams, parameters, throwsClauses, methodBody, annotationMethodDefaultValue), source); + copyJavadoc(field, decl, CopyJavadoc.WITHER); + return decl; } } diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 178b82a2..211f5d36 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -21,6 +21,7 @@ */ package lombok.javac.handlers; +import static lombok.core.TransformationsUtil.INVALID_ON_BUILDERS; import static lombok.javac.Javac.*; import java.lang.annotation.Annotation; @@ -31,6 +32,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.WeakHashMap; +import java.util.regex.Matcher; import java.util.regex.Pattern; import lombok.AccessLevel; @@ -51,9 +53,11 @@ import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree; import com.sun.tools.javac.tree.JCTree.JCAssign; +import com.sun.tools.javac.tree.JCTree.JCBlock; 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.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; @@ -122,6 +126,7 @@ public class JavacHandlerUtil { } public static <T extends JCTree> T recursiveSetGeneratedBy(T node, JCTree source) { + if (node == null) return null; setGeneratedBy(node, source); node.accept(new MarkingScanner(source)); @@ -186,7 +191,7 @@ public class JavacHandlerUtil { public static boolean typeMatches(Class<?> type, JavacNode node, JCTree typeNode) { String typeName = typeNode.toString(); - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); } @@ -278,7 +283,22 @@ public class JavacHandlerUtil { * then removes any import statement that imports this exact annotation (not star imports). * Only does this if the DeleteLombokAnnotations class is in the context. */ + @SuppressWarnings("unchecked") public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType) { + deleteAnnotationIfNeccessary0(annotation, annotationType); + } + + /** + * Removes the annotation from javac's AST (it remains in lombok's AST), + * then removes any import statement that imports this exact annotation (not star imports). + * Only does this if the DeleteLombokAnnotations class is in the context. + */ + @SuppressWarnings("unchecked") + public static void deleteAnnotationIfNeccessary(JavacNode annotation, Class<? extends Annotation> annotationType1, Class<? extends Annotation> annotationType2) { + deleteAnnotationIfNeccessary0(annotation, annotationType1, annotationType2); + } + + private static void deleteAnnotationIfNeccessary0(JavacNode annotation, Class<? extends Annotation>... annotationTypes) { if (inNetbeansEditor(annotation)) return; if (!annotation.shouldDeleteLombokAnnotations()) return; JavacNode parentNode = annotation.directUp(); @@ -306,7 +326,10 @@ public class JavacHandlerUtil { return; } - deleteImportFromCompilationUnit(annotation, annotationType.getName()); + parentNode.getAst().setChanged(); + for (Class<?> annotationType : annotationTypes) { + deleteImportFromCompilationUnit(annotation, annotationType.getName()); + } } public static void deleteImportFromCompilationUnit(JavacNode node, String name) { @@ -425,8 +448,12 @@ public class JavacHandlerUtil { } } - private static boolean isBoolean(JavacNode field) { + public static boolean isBoolean(JavacNode field) { JCExpression varType = ((JCVariableDecl) field.get()).vartype; + return isBoolean(varType); + } + + public static boolean isBoolean(JCExpression varType) { return varType != null && varType.toString().equals("boolean"); } @@ -543,6 +570,23 @@ public class JavacHandlerUtil { return MemberExistsResult.NOT_EXISTS; } + public static boolean isConstructorCall(final JCStatement statement) { + if (!(statement instanceof JCExpressionStatement)) return false; + JCExpression expr = ((JCExpressionStatement) statement).expr; + if (!(expr instanceof JCMethodInvocation)) return false; + JCExpression invocation = ((JCMethodInvocation) expr).meth; + String name; + if (invocation instanceof JCFieldAccess) { + name = ((JCFieldAccess) invocation).name.toString(); + } else if (invocation instanceof JCIdent) { + name = ((JCIdent) invocation).name.toString(); + } else { + name = ""; + } + + return "super".equals(name) || "this".equals(name); + } + /** * Turns an {@code AccessLevel} instance into the flag bit used by javac. */ @@ -710,11 +754,11 @@ public class JavacHandlerUtil { * * Also takes care of updating the JavacAST. */ - public static void injectField(JavacNode typeNode, JCVariableDecl field) { - injectField(typeNode, field, false); + public static JavacNode injectField(JavacNode typeNode, JCVariableDecl field) { + return injectField(typeNode, field, false); } - private static void injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { + private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { JCClassDecl type = (JCClassDecl) typeNode.get(); if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field)); @@ -740,7 +784,7 @@ public class JavacHandlerUtil { insertAfter.tail = fieldEntry; } - typeNode.add(field, Kind.FIELD); + return typeNode.add(field, Kind.FIELD); } private static boolean isEnumConstant(final JCVariableDecl field) { @@ -781,6 +825,20 @@ public class JavacHandlerUtil { typeNode.add(method, Kind.METHOD); } + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + * @return + */ + public static JavacNode injectType(final JavacNode typeNode, final JCClassDecl type) { + JCClassDecl typeDecl = (JCClassDecl) typeNode.get(); + addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type)); + typeDecl.defs = typeDecl.defs.append(type); + return typeNode.add(type, Kind.TYPE); + } + private static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source) { TreeMaker maker = node.getTreeMaker(); JCExpression suppressWarningsType = chainDots(node, "java", "lang", "SuppressWarnings"); @@ -890,7 +948,8 @@ public class JavacHandlerUtil { JCExpression npe = chainDots(variable, "java", "lang", "NullPointerException"); JCTree exception = maker.NewClass(null, List.<JCExpression>nil(), npe, List.<JCExpression>of(maker.Literal(fieldName.toString())), null); JCStatement throwStatement = maker.Throw(exception); - return maker.If(Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(fieldName), Javac.makeLiteral(maker, CTC_BOT, null)), throwStatement, null); + JCBlock throwBlock = maker.Block(0, List.of(throwStatement)); + return maker.If(Javac.makeBinary(maker, CTC_EQUAL, maker.Ident(fieldName), Javac.makeLiteral(maker, CTC_BOT, null)), throwBlock, null); } /** @@ -991,6 +1050,49 @@ public class JavacHandlerUtil { return result.toList(); } + public static List<JCTypeParameter> copyTypeParams(TreeMaker maker, List<JCTypeParameter> params) { + if (params == null || params.isEmpty()) return params; + ListBuffer<JCTypeParameter> out = ListBuffer.lb(); + for (JCTypeParameter tp : params) out.append(maker.TypeParameter(tp.name, tp.bounds)); + return out.toList(); + } + + public static JCExpression namePlusTypeParamsToTypeReference(TreeMaker maker, Name typeName, List<JCTypeParameter> params) { + ListBuffer<JCExpression> typeArgs = ListBuffer.lb(); + + if (!params.isEmpty()) { + for (JCTypeParameter param : params) { + typeArgs.append(maker.Ident(param.name)); + } + + return maker.TypeApply(maker.Ident(typeName), typeArgs.toList()); + } + + return maker.Ident(typeName); + } + + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(JavacNode typeNode, JavacNode errorNode) { + List<String> disallowed = List.nil(); + for (JavacNode child : typeNode.down()) { + for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + disallowed = disallowed.append(annType.getSimpleName()); + } + } + } + + int size = disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.head + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + static List<JCAnnotation> copyAnnotations(List<? extends JCExpression> in) { ListBuffer<JCAnnotation> out = ListBuffer.lb(); for (JCExpression expr : in) { @@ -1090,4 +1192,103 @@ public class JavacHandlerUtil { // This is somewhat unsafe, but it's better than outright throwing an exception here. Returning null will just cause an exception down the pipeline. return (JCExpression) in; } + + private static final Pattern SECTION_FINDER = Pattern.compile("^\\s*\\**\\s*[-*][-*]+\\s*([GS]ETTER|WITHER)\\s*[-*][-*]+\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + + private static String stripLinesWithTagFromJavadoc(String javadoc, String regexpFragment) { + Pattern p = Pattern.compile("^\\s*\\**\\s*" + regexpFragment + "\\s*\\**\\s*$", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE); + Matcher m = p.matcher(javadoc); + return m.replaceAll(""); + } + + private static String[] splitJavadocOnSectionIfPresent(String javadoc, String sectionName) { + Matcher m = SECTION_FINDER.matcher(javadoc); + int getterSectionHeaderStart = -1; + int getterSectionStart = -1; + int getterSectionEnd = -1; + while (m.find()) { + if (m.group(1).equalsIgnoreCase(sectionName)) { + getterSectionStart = m.end() + 1; + getterSectionHeaderStart = m.start(); + } else if (getterSectionStart != -1) { + getterSectionEnd = m.start(); + } + } + + if (getterSectionStart != -1) { + if (getterSectionEnd != -1) { + return new String[] {javadoc.substring(getterSectionStart, getterSectionEnd), javadoc.substring(0, getterSectionHeaderStart) + javadoc.substring(getterSectionEnd)}; + } else { + return new String[] {javadoc.substring(getterSectionStart), javadoc.substring(0, getterSectionHeaderStart)}; + } + } + + return null; + } + + public static enum CopyJavadoc { + VERBATIM, GETTER { + @Override public String[] split(String javadoc) { + // step 1: Check if there is a 'GETTER' section. If yes, that becomes the new method's javadoc and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, "GETTER"); + if (out != null) return out; + // failing that, create a copy, but strip @return from the original and @param from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@returns?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@param(?:eter)?\\s+.*"); + return new String[] {copy, javadoc}; + } + }, + SETTER { + @Override public String[] split(String javadoc) { + return splitForSetters(javadoc, "SETTER"); + } + }, + WITHER { + @Override public String[] split(String javadoc) { + return splitForSetters(javadoc, "WITHER"); + } + }; + + private static String[] splitForSetters(String javadoc, String sectionName) { + // step 1: Check if there is a 'SETTER' section. If yes, that becomes the new one and we strip that from the original. + String[] out = splitJavadocOnSectionIfPresent(javadoc, sectionName); + if (out != null) return out; + // failing that, create a copy, but strip @param from the original and @return from the copy. + String copy = javadoc; + javadoc = stripLinesWithTagFromJavadoc(javadoc, "@param(?:eter)?\\s+.*"); + copy = stripLinesWithTagFromJavadoc(copy, "@returns?\\s+.*"); + return new String[] {copy, javadoc}; + } + + /** Splits the javadoc into the section to be copied (ret[0]) and the section to replace the original with (ret[1]) */ + public String[] split(String javadoc) { + return new String[] {javadoc, javadoc}; + } + } + + /** + * Copies javadoc on one node to the other. + * + * in 'GETTER' copyMode, first a 'GETTER' segment is searched for. If it exists, that will become the javadoc for the 'to' node, and this section is + * stripped out of the 'from' node. If no 'GETTER' segment is found, then the entire javadoc is taken minus any {@code @param} lines. any {@code @return} lines + * are stripped from 'from'. + * + * in 'SETTER' mode, stripping works similarly to 'GETTER' mode, except {@code param} are copied and stripped from the original and {@code @return} are skipped. + */ + public static void copyJavadoc(JavacNode from, JCTree to, CopyJavadoc copyMode) { + if (copyMode == null) copyMode = CopyJavadoc.VERBATIM; + try { + JCCompilationUnit cu = ((JCCompilationUnit) from.top().get()); + if (cu.docComments != null) { + String javadoc = cu.docComments.get(from.get()); + + if (javadoc != null) { + String[] filtered = copyMode.split(javadoc); + cu.docComments.put(to, filtered[0]); + cu.docComments.put(from.get(), filtered[1]); + } + } + } catch (Exception ignore) {} + } } diff --git a/src/core/lombok/javac/handlers/NonNullHandler.java b/src/core/lombok/javac/handlers/NonNullHandler.java new file mode 100644 index 00000000..d74fb55d --- /dev/null +++ b/src/core/lombok/javac/handlers/NonNullHandler.java @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2013 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.handlers; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.tree.JCTree.JCAnnotation; +import com.sun.tools.javac.tree.JCTree.JCBinary; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCParens; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCThrow; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; + +import lombok.NonNull; +import lombok.core.AnnotationValues; +import lombok.core.AST.Kind; +import lombok.javac.JavacAnnotationHandler; +import lombok.javac.JavacNode; + +@ProviderFor(JavacAnnotationHandler.class) +public class NonNullHandler extends JavacAnnotationHandler<NonNull> { + @Override public void handle(AnnotationValues<NonNull> annotation, JCAnnotation ast, JavacNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((JCVariableDecl) annotationNode.up().get()).vartype)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + JCMethodDecl declaration; + + try { + declaration = (JCMethodDecl) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (JavacHandlerUtil.isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + JCStatement nullCheck = recursiveSetGeneratedBy(generateNullCheck(annotationNode.getTreeMaker(), annotationNode.up()), ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + List<JCStatement> statements = declaration.body.stats; + + String expectedName = annotationNode.up().getName(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat)) continue; + String varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (varNameOfNullCheck.equals(expectedName)) return; + } + + List<JCStatement> tail = statements; + List<JCStatement> head = List.nil(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat) || JavacHandlerUtil.isGenerated(stat)) { + tail = tail.tail; + head = head.prepend(stat); + continue; + } + break; + } + + List<JCStatement> newList = tail.prepend(nullCheck); + for (JCStatement stat : head) newList = newList.prepend(stat); + declaration.body.stats = newList; + } + + /** + * Checks if the statement is of the form 'if (x == null) {throw WHATEVER;}, + * where the block braces are optional. If it is of this form, returns "x". + * If it is not of this form, returns null. + */ + private String returnVarNameIfNullCheck(JCStatement stat) { + if (!(stat instanceof JCIf)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + JCStatement then = ((JCIf) stat).thenpart; + if (then instanceof JCBlock) { + List<JCStatement> stats = ((JCBlock) then).stats; + if (stats.length() == 0) return null; + then = stats.get(0); + } + if (!(then instanceof JCThrow)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + JCExpression cond = ((JCIf) stat).cond; + while (cond instanceof JCParens) cond = ((JCParens) cond).expr; + if (!(cond instanceof JCBinary)) return null; + JCBinary bin = (JCBinary) cond; + if (compareCTC(getTag(bin), CTC_EQUAL)) return null; + if (!(bin.lhs instanceof JCIdent)) return null; + if (!(bin.rhs instanceof JCLiteral)) return null; + if (compareCTC(getTypeTag((JCLiteral) bin.rhs), CTC_BOT)) return null; + return ((JCIdent) bin.lhs).name.toString(); + } + } +} |