From 9630fc96e8382d68505a4cb8ab2ae08aec48e776 Mon Sep 17 00:00:00 2001 From: Roel Spilker Date: Tue, 26 Mar 2013 02:42:14 +0100 Subject: Massive performance improvements, and a few potentially breaking changes for other lombok plugin developers. --- src/utils/lombok/core/ImmutableList.java | 188 +++++++++++++++++++++++++++++++ src/utils/lombok/eclipse/Eclipse.java | 6 +- 2 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 src/utils/lombok/core/ImmutableList.java (limited to 'src/utils/lombok') diff --git a/src/utils/lombok/core/ImmutableList.java b/src/utils/lombok/core/ImmutableList.java new file mode 100644 index 00000000..a151ae6f --- /dev/null +++ b/src/utils/lombok/core/ImmutableList.java @@ -0,0 +1,188 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +public final class ImmutableList implements Iterable { + private Object[] content; + private static final ImmutableList EMPTY = new ImmutableList(new Object[0]); + + @SuppressWarnings("unchecked") + public static ImmutableList of() { + return (ImmutableList) EMPTY; + } + + public static ImmutableList of(T a) { + return new ImmutableList(new Object[] {a}); + } + + public static ImmutableList of(T a, T b) { + return new ImmutableList(new Object[] {a, b}); + } + + public static ImmutableList of(T a, T b, T c) { + return new ImmutableList(new Object[] {a, b, c}); + } + + public static ImmutableList of(T a, T b, T c, T d) { + return new ImmutableList(new Object[] {a, b, c, d}); + } + + public static ImmutableList of(T a, T b, T c, T d, T e) { + return new ImmutableList(new Object[] {a, b, c, d, e}); + } + + public static ImmutableList of(T a, T b, T c, T d, T e, T f, @SuppressWarnings("unchecked") T... g) { + Object[] rest = g == null ? new Object[] {null} : g; + Object[] val = new Object[rest.length + 6]; + System.arraycopy(rest, 0, val, 6, rest.length); + val[0] = a; + val[1] = b; + val[2] = c; + val[3] = d; + val[4] = e; + val[5] = f; + return new ImmutableList(val); + } + + public static ImmutableList copyOf(Collection list) { + return new ImmutableList(list.toArray()); + } + + public static ImmutableList copyOf(Iterable iterable) { + List list = new ArrayList(); + for (T o : iterable) list.add(o); + return copyOf(list); + } + + private ImmutableList(Object[] content) { + this.content = content; + } + + public ImmutableList replaceElementAt(int idx, T newValue) { + Object[] newContent = content.clone(); + newContent[idx] = newValue; + return new ImmutableList(newContent); + } + + public ImmutableList append(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 0, len); + newContent[len] = newValue; + return new ImmutableList(newContent); + } + + public ImmutableList prepend(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 1, len); + newContent[0] = newValue; + return new ImmutableList(newContent); + } + + public int indexOf(T val) { + int len = content.length; + if (val == null) { + for (int i = 0; i < len; i++) if (content[i] == null) return i; + return -1; + } + + for (int i = 0; i < len; i++) if (val.equals(content[i])) return i; + return -1; + } + + public ImmutableList removeElement(T val) { + int idx = indexOf(val); + return idx == -1 ? this : removeElementAt(idx); + } + + public ImmutableList removeElementAt(int idx) { + int len = content.length; + Object[] newContent = new Object[len - 1]; + if (idx > 0) System.arraycopy(content, 0, newContent, 0, idx); + if (idx < len - 1) System.arraycopy(content, idx + 1, newContent, idx, len - idx - 1); + return new ImmutableList(newContent); + } + + public boolean isEmpty() { + return content.length == 0; + } + + public int size() { + return content.length; + } + + @SuppressWarnings("unchecked") + public T get(int idx) { + return (T) content[idx]; + } + + public boolean contains(T in) { + if (in == null) { + for (Object e : content) if (e == null) return true; + return false; + } + + for (Object e : content) if (in.equals(e)) return true; + return false; + } + + public Iterator iterator() { + return new Iterator() { + private int idx = 0; + @Override public boolean hasNext() { + return idx < content.length; + } + + @SuppressWarnings("unchecked") + @Override public T next() { + if (idx < content.length) return (T) content[idx++]; + throw new NoSuchElementException(); + } + + @Override public void remove() { + throw new UnsupportedOperationException("List is immutable"); + } + }; + } + + @Override public String toString() { + return Arrays.toString(content); + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof ImmutableList)) return false; + if (obj == this) return true; + return Arrays.equals(content, ((ImmutableList) obj).content); + } + + @Override public int hashCode() { + return Arrays.hashCode(content); + } +} diff --git a/src/utils/lombok/eclipse/Eclipse.java b/src/utils/lombok/eclipse/Eclipse.java index 301925d1..150f3a96 100644 --- a/src/utils/lombok/eclipse/Eclipse.java +++ b/src/utils/lombok/eclipse/Eclipse.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 @@ -58,7 +58,9 @@ public class Eclipse { * but we need to deal with it. This turns [[java][lang][String]] into "java.lang.String". */ public static String toQualifiedName(char[][] typeName) { - StringBuilder sb = new StringBuilder(); + int len = typeName.length - 1; + for (char[] c : typeName) len += c.length; + StringBuilder sb = new StringBuilder(len); boolean first = true; for (char[] c : typeName) { sb.append(first ? "" : ".").append(c); -- cgit From 4152eee126bcb6a0403d3e7a79fe336944031268 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 24 Mar 2013 19:14:10 +0100 Subject: Added a method to obtain latest java language spec supported by host platform and implemented it for javac BUT NOT FOR ECJ! --- src/core/lombok/core/AST.java | 8 ++++++++ src/core/lombok/core/LombokNode.java | 9 +++++++++ src/core/lombok/javac/JavacAST.java | 4 ++++ src/utils/lombok/javac/CommentCatcher.java | 4 ++-- src/utils/lombok/javac/Javac.java | 20 +++++++++++++++++++- test/core/src/lombok/DirectoryRunner.java | 14 +++----------- 6 files changed, 45 insertions(+), 14 deletions(-) (limited to 'src/utils/lombok') diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index 644d9449..a2279efe 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -146,6 +146,14 @@ public abstract class AST, L extends LombokNode, return nodeMap.get(node); } + /** + * 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}. + */ + public int getLatestJavaSpecSupported() { + return 6; + } + @SuppressWarnings({"unchecked", "rawtypes"}) L replaceNewWithExistingOld(Map oldNodes, L newNode) { L oldNode = oldNodes.get(newNode.get()); diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 588adc55..aa161a8d 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -180,6 +180,15 @@ public abstract class LombokNode, L extends LombokNode { for (JavacNode child : node.down()) child.traverse(visitor); } + @Override public int getLatestJavaSpecSupported() { + return Javac.getJavaCompilerVersion(); + } + /** @return A Name object generated for the proper name table belonging to this AST. */ public Name toName(String name) { return elements.getName(name); diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java index 474dc43d..2825cd30 100644 --- a/src/utils/lombok/javac/CommentCatcher.java +++ b/src/utils/lombok/javac/CommentCatcher.java @@ -62,7 +62,7 @@ public class CommentCatcher { private static void registerCommentsCollectingScannerFactory(Context context) { try { - if (JavaCompiler.version().startsWith("1.6")) { + if (Javac.getJavaCompilerVersion() <= 6) { Class.forName("lombok.javac.java6.CommentCollectingScannerFactory").getMethod("preRegister", Context.class).invoke(null, context); } else { Class.forName("lombok.javac.java7.CommentCollectingScannerFactory").getMethod("preRegister", Context.class).invoke(null, context); @@ -76,7 +76,7 @@ public class CommentCatcher { private static void setInCompiler(JavaCompiler compiler, Context context, Map> commentsMap) { try { - if (JavaCompiler.version().startsWith("1.6")) { + if (Javac.getJavaCompilerVersion() <= 6) { Class parserFactory = Class.forName("lombok.javac.java6.CommentCollectingParserFactory"); parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); } else { diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index 75bb2dbf..b4e58b8f 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.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,9 +21,11 @@ */ package lombok.javac; +import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sun.tools.javac.code.TypeTags; +import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; @@ -42,6 +44,22 @@ public class Javac { private static final Pattern PRIMITIVE_TYPE_NAME_PATTERN = Pattern.compile( "^(boolean|byte|short|int|long|float|double|char)$"); + private static final Pattern VERSION_PARSER = Pattern.compile("^(\\d{1,6})\\.(\\d{1,6}).*$"); + + /** + * Returns the version of this java compiler, i.e. the JDK that it shipped in. For example, for javac v1.7, this returns {@code 7}. + */ + public static int getJavaCompilerVersion() { + Matcher m = VERSION_PARSER.matcher(JavaCompiler.version()); + if (m.matches()) { + int major = Integer.parseInt(m.group(1)); + int minor = Integer.parseInt(m.group(2)); + if (major == 1) return minor; + } + + return 6; + } + /** * Checks if the given expression (that really ought to refer to a type expression) represents a primitive type. */ diff --git a/test/core/src/lombok/DirectoryRunner.java b/test/core/src/lombok/DirectoryRunner.java index a3b4de00..855db023 100644 --- a/test/core/src/lombok/DirectoryRunner.java +++ b/test/core/src/lombok/DirectoryRunner.java @@ -32,25 +32,18 @@ import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.javac.Javac; + import org.junit.runner.Description; import org.junit.runner.Runner; import org.junit.runner.notification.Failure; import org.junit.runner.notification.RunNotifier; -import com.sun.tools.javac.main.JavaCompiler; - public class DirectoryRunner extends Runner { public enum Compiler { DELOMBOK { @Override public int getVersion() { - Matcher m = VERSION_PARSER.matcher(JavaCompiler.version()); - if (m.matches()) { - int major = Integer.parseInt(m.group(1)); - int minor = Integer.parseInt(m.group(2)); - if (major == 1) return minor; - } - - return 6; + return Javac.getJavaCompilerVersion(); } }, JAVAC { @@ -64,7 +57,6 @@ public class DirectoryRunner extends Runner { } }; - private static final Pattern VERSION_PARSER = Pattern.compile("^(\\d+)\\.(\\d+).*$"); public abstract int getVersion(); } -- cgit From 9c1e29842e65bf20895db9e19336b2ca948236ad Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 22:58:34 +0200 Subject: Added methods to obtain JLS support-level version information from AST/LombokNode. Tests updates to honour these with //version X at the top of any test file (now also in eclipse, which until now always said it was v6) --- src/core/lombok/core/AST.java | 10 ++++++++++ src/core/lombok/core/LombokNode.java | 9 +++++++++ src/core/lombok/eclipse/EclipseAST.java | 14 ++++++++++++++ src/core/lombok/javac/JavacAST.java | 10 ++++++++++ src/utils/lombok/eclipse/Eclipse.java | 19 +++++++++++++++++++ test/core/src/lombok/DirectoryRunner.java | 3 ++- 6 files changed, 64 insertions(+), 1 deletion(-) (limited to 'src/utils/lombok') diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index a2279efe..30297418 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -146,9 +146,19 @@ public abstract class AST, L extends LombokNode, 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; diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index aa161a8d..30bacc56 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -189,6 +189,15 @@ public abstract class LombokNode, L extends LombokNode { return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName()); } + @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(); + } + /** * Runs through the entire AST, starting at the compilation unit, calling the provided visitor's visit methods * for each node, depth first. diff --git a/src/core/lombok/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index 9e15516e..8197dae6 100644 --- a/src/core/lombok/javac/JavacAST.java +++ b/src/core/lombok/javac/JavacAST.java @@ -34,6 +34,7 @@ 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; @@ -113,6 +114,15 @@ public class JavacAST extends AST { 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(); } diff --git a/src/utils/lombok/eclipse/Eclipse.java b/src/utils/lombok/eclipse/Eclipse.java index 150f3a96..edfe1536 100644 --- a/src/utils/lombok/eclipse/Eclipse.java +++ b/src/utils/lombok/eclipse/Eclipse.java @@ -21,6 +21,7 @@ */ package lombok.eclipse; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -38,6 +39,7 @@ import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; public class Eclipse { @@ -177,4 +179,21 @@ public class Eclipse { return null; } + + private static int ecjCompilerVersionCached = -1; + + public static int getEcjCompilerVersion() { + if (ecjCompilerVersionCached >= 0) return ecjCompilerVersionCached; + + for (Field f : CompilerOptions.class.getDeclaredFields()) { + try { + if (f.getName().startsWith("VERSION_1_")) { + ecjCompilerVersionCached = Math.max(ecjCompilerVersionCached, Integer.parseInt(f.getName().substring("VERSION_1_".length()))); + } + } catch (Exception ignore) {} + } + + if (ecjCompilerVersionCached < 5) ecjCompilerVersionCached = 5; + return ecjCompilerVersionCached; + } } diff --git a/test/core/src/lombok/DirectoryRunner.java b/test/core/src/lombok/DirectoryRunner.java index 855db023..5325c1e3 100644 --- a/test/core/src/lombok/DirectoryRunner.java +++ b/test/core/src/lombok/DirectoryRunner.java @@ -32,6 +32,7 @@ import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; +import lombok.eclipse.Eclipse; import lombok.javac.Javac; import org.junit.runner.Description; @@ -53,7 +54,7 @@ public class DirectoryRunner extends Runner { }, ECJ { @Override public int getVersion() { - return 6; + return Eclipse.getEcjCompilerVersion(); } }; -- cgit From 6b5a0e2cb349a4fb7d8bb5f943e57a0b65596ca0 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 24 May 2013 00:52:37 +0200 Subject: Fixed more issues related to java7's try-with-resources, and updated ECJ version detection. --- src/core/lombok/core/AST.java | 28 ++++++++++++--------- src/core/lombok/javac/JavacAST.java | 43 ++++++++++++++++++++++++++++++++ src/utils/lombok/eclipse/Eclipse.java | 41 +++++++++++++++++++++++++++++- test/core/src/lombok/RunTestsViaEcj.java | 8 +++--- 4 files changed, 103 insertions(+), 17 deletions(-) (limited to 'src/utils/lombok') diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index 30297418..e6721b80 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -54,7 +54,7 @@ public abstract class AST, L extends LombokNode, private final String fileName; private final String packageDeclaration; private final ImportList imports; - Map identityDetector = new IdentityHashMap(); + Map identityDetector = new IdentityHashMap(); private Map nodeMap = new IdentityHashMap(); private boolean changed = false; @@ -105,7 +105,7 @@ public abstract class AST, L extends LombokNode, */ protected L putInMap(L node) { nodeMap.put(node.get(), node); - identityDetector.put(node.get(), null); + identityDetector.put(node.get(), node.get()); return node; } @@ -117,7 +117,7 @@ public abstract class AST, L extends LombokNode, /** 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(); + identityDetector = new IdentityHashMap(); nodeMap = new IdentityHashMap(); } @@ -127,9 +127,7 @@ public abstract class AST, L extends LombokNode, * 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() { @@ -234,14 +232,12 @@ public abstract class AST, L extends LombokNode, } } - 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); } @@ -258,6 +254,14 @@ public abstract class AST, L extends LombokNode, * though some platforms (such as Eclipse) group these under one common supertype. */ protected abstract Collection> 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/javac/JavacAST.java b/src/core/lombok/javac/JavacAST.java index 8197dae6..5a91258c 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; @@ -39,6 +40,7 @@ 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; @@ -224,6 +226,46 @@ public class JavacAST extends AST { 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 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) rv; + return Collections.emptyList(); + } + + private JavacNode buildTry(JCTry tryNode) { + if (setAndGetAsHandled(tryNode)) return null; + List childNodes = new ArrayList(); + 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 childNodes = new ArrayList(); @@ -265,6 +307,7 @@ public class JavacAST extends AST { 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; diff --git a/src/utils/lombok/eclipse/Eclipse.java b/src/utils/lombok/eclipse/Eclipse.java index edfe1536..c2a863d5 100644 --- a/src/utils/lombok/eclipse/Eclipse.java +++ b/src/utils/lombok/eclipse/Eclipse.java @@ -37,8 +37,10 @@ import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.Literal; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.TryStatement; 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.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.lookup.TypeIds; @@ -180,8 +182,31 @@ public class Eclipse { return null; } - private static int ecjCompilerVersionCached = -1; + private static long latestEcjCompilerVersionConstantCached = 0; + + public static long getLatestEcjCompilerVersionConstant() { + if (latestEcjCompilerVersionConstantCached != 0) return latestEcjCompilerVersionConstantCached; + + int highestVersionSoFar = 0; + for (Field f : ClassFileConstants.class.getDeclaredFields()) { + try { + if (f.getName().startsWith("JDK1_")) { + int thisVersion = Integer.parseInt(f.getName().substring("JDK1_".length())); + if (thisVersion > highestVersionSoFar) { + highestVersionSoFar = thisVersion; + latestEcjCompilerVersionConstantCached = (Long) f.get(null); + } + } + } catch (Exception ignore) {} + } + + if (highestVersionSoFar > 6 && !ecjSupportsJava7Features()) { + latestEcjCompilerVersionConstantCached = ClassFileConstants.JDK1_6; + } + return latestEcjCompilerVersionConstantCached; + } + private static int ecjCompilerVersionCached = -1; public static int getEcjCompilerVersion() { if (ecjCompilerVersionCached >= 0) return ecjCompilerVersionCached; @@ -194,6 +219,20 @@ public class Eclipse { } if (ecjCompilerVersionCached < 5) ecjCompilerVersionCached = 5; + if (!ecjSupportsJava7Features()) ecjCompilerVersionCached = Math.min(6, ecjCompilerVersionCached); return ecjCompilerVersionCached; } + + /** + * Certain ECJ versions that only go up to -source 6 report that they support -source 7 and even fail to error when -source 7 is applied. + * We detect this and correctly say that no more than -source 6 is supported. (when this is the case, this method returns false). + */ + private static boolean ecjSupportsJava7Features() { + try { + TryStatement.class.getDeclaredField("resources"); + return true; + } catch (NoSuchFieldException e) { + return false; + } + } } diff --git a/test/core/src/lombok/RunTestsViaEcj.java b/test/core/src/lombok/RunTestsViaEcj.java index ca443620..12f2e252 100644 --- a/test/core/src/lombok/RunTestsViaEcj.java +++ b/test/core/src/lombok/RunTestsViaEcj.java @@ -33,6 +33,7 @@ import java.util.Locale; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; +import lombok.eclipse.Eclipse; import lombok.javac.CapturingDiagnosticListener.CompilerMessage; import org.eclipse.jdt.core.compiler.CategorizedProblem; @@ -43,7 +44,6 @@ import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.batch.CompilationUnit; import org.eclipse.jdt.internal.compiler.batch.FileSystem; -import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; import org.eclipse.jdt.internal.compiler.impl.CompilerOptions; import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; @@ -51,9 +51,9 @@ import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory; public class RunTestsViaEcj extends AbstractRunTests { protected CompilerOptions ecjCompilerOptions() { CompilerOptions options = new CompilerOptions(); - options.complianceLevel = ClassFileConstants.JDK1_6; - options.sourceLevel = ClassFileConstants.JDK1_6; - options.targetJDK = ClassFileConstants.JDK1_6; + options.complianceLevel = Eclipse.getLatestEcjCompilerVersionConstant(); + options.sourceLevel = Eclipse.getLatestEcjCompilerVersionConstant(); + options.targetJDK = Eclipse.getLatestEcjCompilerVersionConstant(); options.parseLiteralExpressionsAsConstants = true; options.inlineJsrBytecode = true; options.reportUnusedDeclaredThrownExceptionExemptExceptionAndThrowable = false; -- cgit From 5a3e9bd8049469169410107011ad0e26b3b629e3 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Fri, 31 May 2013 01:03:38 +0200 Subject: Added @NonNull on parameters feature (issue 514), including docs and changelog. --- buildScripts/website.ant.xml | 3 + doc/changelog.markdown | 1 + src/core/lombok/NonNull.java | 16 ++- .../eclipse/handlers/EclipseHandlerUtil.java | 7 +- .../lombok/eclipse/handlers/NonNullHandler.java | 147 ++++++++++++++++++++ .../lombok/javac/handlers/HandleSneakyThrows.java | 12 +- .../lombok/javac/handlers/JavacHandlerUtil.java | 23 +++- src/core/lombok/javac/handlers/NonNullHandler.java | 148 +++++++++++++++++++++ src/utils/lombok/javac/Javac.java | 32 +++++ .../resource/after-delombok/DataOnLocalClass.java | 8 +- .../after-delombok/NonNullOnParameter.java | 48 +++++++ .../resource/after-delombok/NonNullPlain.java | 8 +- .../resource/after-delombok/SetterOnClass.java | 4 +- .../resource/after-delombok/WitherOnClass.java | 4 +- .../resource/after-ecj/DataOnLocalClass.java | 8 +- .../resource/after-ecj/NonNullOnParameter.java | 61 +++++++++ .../transform/resource/after-ecj/NonNullPlain.java | 8 +- .../resource/after-ecj/SetterOnClass.java | 4 +- .../resource/after-ecj/WitherOnClass.java | 4 +- .../resource/before/NonNullOnParameter.java | 30 +++++ .../NonNullOnParameter.java.messages | 1 + .../messages-delombok/NonNullPlain.java.messages | 1 + .../messages-ecj/NonNullOnParameter.java.messages | 3 + .../messages-ecj/NonNullPlain.java.messages | 1 + .../NonNullOnParameter.java.messages | 1 + .../messages-idempotent/NonNullPlain.java.messages | 3 + usage_examples/NonNullExample_post.jpage | 13 ++ usage_examples/NonNullExample_pre.jpage | 10 ++ website/features/Cleanup.html | 2 +- website/features/Data.html | 2 +- website/features/Delegate.html | 11 +- website/features/GetterLazy.html | 2 +- website/features/GetterSetter.html | 2 +- website/features/Log.html | 2 +- website/features/NonNull.html | 73 ++++++++++ website/features/SneakyThrows.html | 4 +- website/features/Synchronized.html | 2 +- website/features/ToString.html | 2 +- website/features/index.html | 18 +-- website/features/val.html | 2 +- 40 files changed, 674 insertions(+), 57 deletions(-) create mode 100644 src/core/lombok/eclipse/handlers/NonNullHandler.java create mode 100644 src/core/lombok/javac/handlers/NonNullHandler.java create mode 100644 test/transform/resource/after-delombok/NonNullOnParameter.java create mode 100644 test/transform/resource/after-ecj/NonNullOnParameter.java create mode 100644 test/transform/resource/before/NonNullOnParameter.java create mode 100644 test/transform/resource/messages-delombok/NonNullOnParameter.java.messages create mode 100644 test/transform/resource/messages-delombok/NonNullPlain.java.messages create mode 100644 test/transform/resource/messages-ecj/NonNullOnParameter.java.messages create mode 100644 test/transform/resource/messages-ecj/NonNullPlain.java.messages create mode 100644 test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages create mode 100644 test/transform/resource/messages-idempotent/NonNullPlain.java.messages create mode 100644 usage_examples/NonNullExample_post.jpage create mode 100644 usage_examples/NonNullExample_pre.jpage create mode 100644 website/features/NonNull.html (limited to 'src/utils/lombok') diff --git a/buildScripts/website.ant.xml b/buildScripts/website.ant.xml index 405b388f..41130bd2 100644 --- a/buildScripts/website.ant.xml +++ b/buildScripts/website.ant.xml @@ -145,6 +145,9 @@ such as converting the changelog into HTML, and creating javadoc. + + + diff --git a/doc/changelog.markdown b/doc/changelog.markdown index e2e3d6b5..aaf66030 100644 --- a/doc/changelog.markdown +++ b/doc/changelog.markdown @@ -2,6 +2,7 @@ Lombok Changelog ---------------- ### v0.11.9 (Edgy Guinea Pig) +* FEATURE: `@NonNull` on a method or constructor parameter now generates a null-check statement at the start of your method. This nullcheck will throw a `NullPointerException` with the name of the parameter as the message. [Issue #514](https://code.google.com/p/projectlombok/issues/detail?id=514) * BUGFIX: Usage of `Lombok.sneakyThrow()` or `@SneakyThrows` would sometimes result in invalid classes (classes which fail with `VerifyError`). [Issue #470](https://code.google.com/p/projectlombok/issues/detail?id=470) * BUGFIX: Using `val` in try-with-resources did not work for javac. [Issue #520](https://code.google.com/p/projectlombok/issues/detail?id=520) * BUGFIX: When using `@Data`, warnings are not generated if certain aspects are not generated because you wrote explicit versions of them. However, this gets confusing with `equals` / `hashCode` / `canEqual`, as nothing is generated if any one of those methods is present. Now, if one of `equals` or `hashCode` is present but not the other one (or `canEqual` is present but `equals` and/or `hashCode` is missing), a warning is emitted to explain that lombok will not generate any of the equals / hashCode methods, and that you should either write them all yourself or remove them all. [Issue #513](https://code.google.com/p/projectlombok/issues/detail?id=513) 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. + *

+ * 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 only 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 be deleted from the lombok package. If the need to update an import statement scares diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 7703336f..dc99dabf 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -61,6 +61,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; @@ -1334,7 +1335,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/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 { + @Override public void handle(AnnotationValues 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 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/javac/handlers/HandleSneakyThrows.java b/src/core/lombok/javac/handlers/HandleSneakyThrows.java index c2394fc8..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; @@ -114,14 +112,6 @@ public class HandleSneakyThrows extends JavacAnnotationHandler { } } - 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 JCStatement buildTryCatchBlock(JavacNode node, List contents, String exception, JCTree source) { TreeMaker maker = node.getTreeMaker(); diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index ef1a9f50..7cbaa5ac 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -50,9 +50,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; @@ -121,6 +123,7 @@ public class JavacHandlerUtil { } public static T recursiveSetGeneratedBy(T node, JCTree source) { + if (node == null) return null; setGeneratedBy(node, source); node.accept(new MarkingScanner(source)); @@ -543,6 +546,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. */ @@ -890,7 +910,8 @@ public class JavacHandlerUtil { JCExpression npe = chainDots(variable, "java", "lang", "NullPointerException"); JCTree exception = treeMaker.NewClass(null, List.nil(), npe, List.of(treeMaker.Literal(fieldName.toString())), null); JCStatement throwStatement = treeMaker.Throw(exception); - return treeMaker.If(treeMaker.Binary(CTC_EQUAL, treeMaker.Ident(fieldName), treeMaker.Literal(CTC_BOT, null)), throwStatement, null); + JCBlock throwBlock = treeMaker.Block(0, List.of(throwStatement)); + return treeMaker.If(treeMaker.Binary(CTC_EQUAL, treeMaker.Ident(fieldName), treeMaker.Literal(CTC_BOT, null)), throwBlock, null); } /** diff --git a/src/core/lombok/javac/handlers/NonNullHandler.java b/src/core/lombok/javac/handlers/NonNullHandler.java new file mode 100644 index 00000000..415d6032 --- /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 { + @Override public void handle(AnnotationValues 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 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 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 tail = statements; + List head = List.nil(); + for (JCStatement stat : statements) { + if (JavacHandlerUtil.isConstructorCall(stat) || JavacHandlerUtil.isGenerated(stat)) { + tail = tail.tail; + head = head.prepend(stat); + continue; + } + break; + } + + List 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 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 (getTag(bin) != CTC_EQUAL) return null; + if (!(bin.lhs instanceof JCIdent)) return null; + if (!(bin.rhs instanceof JCLiteral)) return null; + if (((JCLiteral) bin.rhs).typetag != CTC_BOT) return null; + return ((JCIdent) bin.lhs).name.toString(); + } + } +} diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index b4e58b8f..08c7c957 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -21,6 +21,8 @@ */ package lombok.javac; +import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -129,4 +131,34 @@ public class Javac { throw new RuntimeException(e); } } + + private static final Field JCTREE_TAG; + private static final Method JCTREE_GETTAG; + static { + Field f = null; + try { + f = JCTree.class.getDeclaredField("tag"); + } catch (NoSuchFieldException e) {} + JCTREE_TAG = f; + + Method m = null; + try { + m = JCTree.class.getDeclaredMethod("getTag"); + } catch (NoSuchMethodException e) {} + JCTREE_GETTAG = m; + } + + public static int getTag(JCTree node) { + if (JCTREE_GETTAG != null) { + try { + return (Integer) JCTREE_GETTAG.invoke(node); + } catch (Exception e) {} + } + try { + return (Integer) JCTREE_TAG.get(node); + } catch (Exception e) { + throw new IllegalStateException("Can't get node tag"); + } + } + } diff --git a/test/transform/resource/after-delombok/DataOnLocalClass.java b/test/transform/resource/after-delombok/DataOnLocalClass.java index ed4d30ca..abe2757b 100644 --- a/test/transform/resource/after-delombok/DataOnLocalClass.java +++ b/test/transform/resource/after-delombok/DataOnLocalClass.java @@ -63,7 +63,9 @@ class DataOnLocalClass2 { String name; @java.lang.SuppressWarnings("all") public InnerLocal(@lombok.NonNull final String name) { - if (name == null) throw new java.lang.NullPointerException("name"); + if (name == null) { + throw new java.lang.NullPointerException("name"); + } this.name = name; } @lombok.NonNull @@ -73,7 +75,9 @@ class DataOnLocalClass2 { } @java.lang.SuppressWarnings("all") public void setName(@lombok.NonNull final String name) { - if (name == null) throw new java.lang.NullPointerException("name"); + if (name == null) { + throw new java.lang.NullPointerException("name"); + } this.name = name; } @java.lang.Override diff --git a/test/transform/resource/after-delombok/NonNullOnParameter.java b/test/transform/resource/after-delombok/NonNullOnParameter.java new file mode 100644 index 00000000..a27d19c9 --- /dev/null +++ b/test/transform/resource/after-delombok/NonNullOnParameter.java @@ -0,0 +1,48 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + } + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if (arg2 == null) { + throw new java.lang.NullPointerException("arg2"); + } + if (arg == null) throw new NullPointerException(); + } + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + if (arg3 == null) { + throw new java.lang.NullPointerException("arg3"); + } + if (arg2 == null) { + throw new NullPointerException("arg2"); + } + if (arg == null) System.out.println("Hello"); + } + public void test3(@lombok.NonNull String arg) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + if (arg != null) throw new IllegalStateException(); + } + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + if (stringArg == null) { + throw new java.lang.NullPointerException("stringArg"); + } + if (arg2 == null) { + throw new java.lang.NullPointerException("arg2"); + } + } + public void test(@lombok.NonNull String arg) { + if (arg == null) { + throw new java.lang.NullPointerException("arg"); + } + System.out.println("Hey"); + if (arg == null) throw new NullPointerException(); + } +} diff --git a/test/transform/resource/after-delombok/NonNullPlain.java b/test/transform/resource/after-delombok/NonNullPlain.java index 064e00b9..6b85cbf7 100644 --- a/test/transform/resource/after-delombok/NonNullPlain.java +++ b/test/transform/resource/after-delombok/NonNullPlain.java @@ -16,7 +16,9 @@ class NonNullPlain { @java.beans.ConstructorProperties({"i", "s"}) @java.lang.SuppressWarnings("all") public NonNullPlain(@lombok.NonNull final int i, @lombok.NonNull final String s) { - if (s == null) throw new java.lang.NullPointerException("s"); + if (s == null) { + throw new java.lang.NullPointerException("s"); + } this.i = i; this.s = s; } @@ -45,7 +47,9 @@ class NonNullPlain { @java.lang.SuppressWarnings("all") public void setS(@lombok.NonNull final String s) { - if (s == null) throw new java.lang.NullPointerException("s"); + if (s == null) { + throw new java.lang.NullPointerException("s"); + } this.s = s; } diff --git a/test/transform/resource/after-delombok/SetterOnClass.java b/test/transform/resource/after-delombok/SetterOnClass.java index 151bc179..7077c492 100644 --- a/test/transform/resource/after-delombok/SetterOnClass.java +++ b/test/transform/resource/after-delombok/SetterOnClass.java @@ -53,7 +53,9 @@ class SetterOnClass6 { } @java.lang.SuppressWarnings("all") public void setNonNull(@lombok.NonNull final String nonNull) { - if (nonNull == null) throw new java.lang.NullPointerException("nonNull"); + if (nonNull == null) { + throw new java.lang.NullPointerException("nonNull"); + } this.nonNull = nonNull; } } \ No newline at end of file diff --git a/test/transform/resource/after-delombok/WitherOnClass.java b/test/transform/resource/after-delombok/WitherOnClass.java index 783fede1..45d0c4b5 100644 --- a/test/transform/resource/after-delombok/WitherOnClass.java +++ b/test/transform/resource/after-delombok/WitherOnClass.java @@ -35,7 +35,9 @@ class WitherOnClass3 { } @java.lang.SuppressWarnings("all") public WitherOnClass3 withNonNull(@lombok.NonNull final String nonNull) { - if (nonNull == null) throw new java.lang.NullPointerException("nonNull"); + if (nonNull == null) { + throw new java.lang.NullPointerException("nonNull"); + } return this.nonNull == nonNull ? this : new WitherOnClass3(this.couldBeNull, nonNull); } } diff --git a/test/transform/resource/after-ecj/DataOnLocalClass.java b/test/transform/resource/after-ecj/DataOnLocalClass.java index 137edf50..2f8dcca1 100644 --- a/test/transform/resource/after-ecj/DataOnLocalClass.java +++ b/test/transform/resource/after-ecj/DataOnLocalClass.java @@ -63,7 +63,9 @@ class DataOnLocalClass2 { } public @java.lang.SuppressWarnings("all") void setName(final @lombok.NonNull String name) { if ((name == null)) - throw new java.lang.NullPointerException("name"); + { + throw new java.lang.NullPointerException("name"); + } this.name = name; } public @java.lang.Override @java.lang.SuppressWarnings("all") boolean equals(final java.lang.Object o) { @@ -96,7 +98,9 @@ class DataOnLocalClass2 { public @java.lang.SuppressWarnings("all") InnerLocal(final @lombok.NonNull String name) { super(); if ((name == null)) - throw new java.lang.NullPointerException("name"); + { + throw new java.lang.NullPointerException("name"); + } this.name = name; } } diff --git a/test/transform/resource/after-ecj/NonNullOnParameter.java b/test/transform/resource/after-ecj/NonNullOnParameter.java new file mode 100644 index 00000000..bbceb153 --- /dev/null +++ b/test/transform/resource/after-ecj/NonNullOnParameter.java @@ -0,0 +1,61 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + } + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if ((arg2 == null)) + { + throw new java.lang.NullPointerException("arg2"); + } + if ((arg == null)) + throw new NullPointerException(); + } + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + if ((arg3 == null)) + { + throw new java.lang.NullPointerException("arg3"); + } + if ((arg2 == null)) + { + throw new NullPointerException("arg2"); + } + if ((arg == null)) + System.out.println("Hello"); + } + public void test3(@lombok.NonNull String arg) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + if ((arg != null)) + throw new IllegalStateException(); + } + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + if ((stringArg == null)) + { + throw new java.lang.NullPointerException("stringArg"); + } + if ((arg2 == null)) + { + throw new java.lang.NullPointerException("arg2"); + } + } + public void test(@lombok.NonNull String arg) { + if ((arg == null)) + { + throw new java.lang.NullPointerException("arg"); + } + System.out.println("Hey"); + if ((arg == null)) + throw new NullPointerException(); + } +} \ No newline at end of file diff --git a/test/transform/resource/after-ecj/NonNullPlain.java b/test/transform/resource/after-ecj/NonNullPlain.java index c9c96d0a..6e937f6a 100644 --- a/test/transform/resource/after-ecj/NonNullPlain.java +++ b/test/transform/resource/after-ecj/NonNullPlain.java @@ -8,7 +8,9 @@ import java.lang.annotation.*; public @java.beans.ConstructorProperties({"i", "s"}) @java.lang.SuppressWarnings("all") NonNullPlain(final @lombok.NonNull int i, final @lombok.NonNull String s) { super(); if ((s == null)) - throw new java.lang.NullPointerException("s"); + { + throw new java.lang.NullPointerException("s"); + } this.i = i; this.s = s; } @@ -26,7 +28,9 @@ import java.lang.annotation.*; } public @java.lang.SuppressWarnings("all") void setS(final @lombok.NonNull String s) { if ((s == null)) - throw new java.lang.NullPointerException("s"); + { + throw new java.lang.NullPointerException("s"); + } this.s = s; } public @java.lang.SuppressWarnings("all") void setO(final Object o) { diff --git a/test/transform/resource/after-ecj/SetterOnClass.java b/test/transform/resource/after-ecj/SetterOnClass.java index da928f24..aa3459bb 100644 --- a/test/transform/resource/after-ecj/SetterOnClass.java +++ b/test/transform/resource/after-ecj/SetterOnClass.java @@ -63,7 +63,9 @@ } public @java.lang.SuppressWarnings("all") void setNonNull(final @lombok.NonNull String nonNull) { if ((nonNull == null)) - throw new java.lang.NullPointerException("nonNull"); + { + throw new java.lang.NullPointerException("nonNull"); + } this.nonNull = nonNull; } } diff --git a/test/transform/resource/after-ecj/WitherOnClass.java b/test/transform/resource/after-ecj/WitherOnClass.java index ff4566e5..82132e87 100644 --- a/test/transform/resource/after-ecj/WitherOnClass.java +++ b/test/transform/resource/after-ecj/WitherOnClass.java @@ -33,7 +33,9 @@ } public @java.lang.SuppressWarnings("all") WitherOnClass3 withNonNull(final @lombok.NonNull String nonNull) { if ((nonNull == null)) - throw new java.lang.NullPointerException("nonNull"); + { + throw new java.lang.NullPointerException("nonNull"); + } return ((this.nonNull == nonNull) ? this : new WitherOnClass3(this.couldBeNull, nonNull)); } } diff --git a/test/transform/resource/before/NonNullOnParameter.java b/test/transform/resource/before/NonNullOnParameter.java new file mode 100644 index 00000000..7eb4c565 --- /dev/null +++ b/test/transform/resource/before/NonNullOnParameter.java @@ -0,0 +1,30 @@ +class NonNullOnParameter extends Thread { + NonNullOnParameter(@lombok.NonNull String arg) { + this(arg, ""); + } + + NonNullOnParameter(@lombok.NonNull String arg, @lombok.NonNull String arg2) { + super(arg); + if (arg == null) throw new NullPointerException(); + } + + public void test2(@lombok.NonNull String arg, @lombok.NonNull String arg2, @lombok.NonNull String arg3) { + if (arg2 == null) { + throw new NullPointerException("arg2"); + } + if (arg == null) System.out.println("Hello"); + } + + public void test3(@lombok.NonNull String arg) { + if (arg != null) throw new IllegalStateException(); + } + + public void test(@lombok.NonNull String stringArg, @lombok.NonNull String arg2, @lombok.NonNull int primitiveArg) { + + } + + public void test(@lombok.NonNull String arg) { + System.out.println("Hey"); + if (arg == null) throw new NullPointerException(); + } +} \ No newline at end of file diff --git a/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages b/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages new file mode 100644 index 00000000..ac87adcd --- /dev/null +++ b/test/transform/resource/messages-delombok/NonNullOnParameter.java.messages @@ -0,0 +1 @@ +22:89 @NonNull is meaningless on a primitive. diff --git a/test/transform/resource/messages-delombok/NonNullPlain.java.messages b/test/transform/resource/messages-delombok/NonNullPlain.java.messages new file mode 100644 index 00000000..67eb8abe --- /dev/null +++ b/test/transform/resource/messages-delombok/NonNullPlain.java.messages @@ -0,0 +1 @@ +7:9 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages b/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages new file mode 100644 index 00000000..1539929b --- /dev/null +++ b/test/transform/resource/messages-ecj/NonNullOnParameter.java.messages @@ -0,0 +1,3 @@ +15:460 Dead code +22:683 @NonNull is meaningless on a primitive. +28:823 Dead code \ No newline at end of file diff --git a/test/transform/resource/messages-ecj/NonNullPlain.java.messages b/test/transform/resource/messages-ecj/NonNullPlain.java.messages new file mode 100644 index 00000000..96eed252 --- /dev/null +++ b/test/transform/resource/messages-ecj/NonNullPlain.java.messages @@ -0,0 +1 @@ +7:116 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages b/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages new file mode 100644 index 00000000..fd23a32a --- /dev/null +++ b/test/transform/resource/messages-idempotent/NonNullOnParameter.java.messages @@ -0,0 +1 @@ +33:89 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/test/transform/resource/messages-idempotent/NonNullPlain.java.messages b/test/transform/resource/messages-idempotent/NonNullPlain.java.messages new file mode 100644 index 00000000..c48da311 --- /dev/null +++ b/test/transform/resource/messages-idempotent/NonNullPlain.java.messages @@ -0,0 +1,3 @@ +4:9 @NonNull is meaningless on a primitive. +18:29 @NonNull is meaningless on a primitive. +44:26 @NonNull is meaningless on a primitive. \ No newline at end of file diff --git a/usage_examples/NonNullExample_post.jpage b/usage_examples/NonNullExample_post.jpage new file mode 100644 index 00000000..24175e06 --- /dev/null +++ b/usage_examples/NonNullExample_post.jpage @@ -0,0 +1,13 @@ +import lombok.NonNull; + +public class NonNullExample extends Something { + private String name; + + public NonNullExample(@NonNull Person person) { + super("Hello"); + if (person == null) { + throw new NullPointerException("person"); + } + this.name = person.getName(); + } +} diff --git a/usage_examples/NonNullExample_pre.jpage b/usage_examples/NonNullExample_pre.jpage new file mode 100644 index 00000000..47556ce7 --- /dev/null +++ b/usage_examples/NonNullExample_pre.jpage @@ -0,0 +1,10 @@ +import lombok.NonNull; + +public class NonNullExample extends Something { + private String name; + + public NonNullExample(@NonNull Person person) { + super("Hello"); + this.name = person.getName(); + } +} diff --git a/website/features/Cleanup.html b/website/features/Cleanup.html index d1637dd4..a6e41f39 100644 --- a/website/features/Cleanup.html +++ b/website/features/Cleanup.html @@ -60,7 +60,7 @@

diff --git a/website/features/Data.html b/website/features/Data.html index 8ace96cb..1c8510b7 100644 --- a/website/features/Data.html +++ b/website/features/Data.html @@ -75,7 +75,7 @@
diff --git a/website/features/Delegate.html b/website/features/Delegate.html index 532f3f54..02cdf290 100644 --- a/website/features/Delegate.html +++ b/website/features/Delegate.html @@ -55,16 +55,15 @@ When passing classes to the annotation's types or excludes parameter, you cannot include generics. This is a limitation of java. Use private inner interfaces or classes that extend the intended type including the generics parameter to work around this problem. -

-

+

When passing classes to the annotation, these classes do not need to be supertypes of the field. See the example. -

-

+

@Delegate cannot be used on static fields or methods. -

+

+
diff --git a/website/features/GetterLazy.html b/website/features/GetterLazy.html index bc5ecb0c..f70c8e78 100644 --- a/website/features/GetterLazy.html +++ b/website/features/GetterLazy.html @@ -46,7 +46,7 @@
diff --git a/website/features/GetterSetter.html b/website/features/GetterSetter.html index 03704119..c78b03bd 100644 --- a/website/features/GetterSetter.html +++ b/website/features/GetterSetter.html @@ -75,7 +75,7 @@
diff --git a/website/features/Log.html b/website/features/Log.html index 2fb91956..2d4fa375 100644 --- a/website/features/Log.html +++ b/website/features/Log.html @@ -60,7 +60,7 @@
diff --git a/website/features/NonNull.html b/website/features/NonNull.html new file mode 100644 index 00000000..9faad502 --- /dev/null +++ b/website/features/NonNull.html @@ -0,0 +1,73 @@ + + + + + + + + val +
+
+
+ +

@NonNull

+ +
+

Overview

+

+ NEW in Lombok 0.11.10: You can use @NonNull on the parameter of a method or constructor to have lombok generate a null-check statement for you. +

+ Lombok has always treated any annotation named @NonNull on a field as a signal to generate a null-check if lombok generates an entire method or constructor for you, via + for example @Data. Now, however, using lombok's own @lombok.NonNull on a parameter results in the insertion of just the null-check + statement inside your own method or constructor. +

+ The null-check looks like if (param == null) throw new NullPointerException("param"); and will be inserted at the very top of your method. For constructors, the null-check + will be inserted immediately following any explicit this() or super() calls. +

+ If a null-check is already present at the top, no additional null-check will be generated. +

+
+
+
+

With Lombok

+
@HTML_PRE@
+
+
+
+

Vanilla Java

+
@HTML_POST@
+
+
+
+
+

Small print

+

+ Lombok's detection scheme for already existing null-checks consists of scanning for if statements that look just like lombok's own. Any 'throws' statement as + the 'then' part of the if statement, whether in braces or not, counts. The conditional of the if statement must look exactly like PARAMNAME == null. + The first statement in your method that is not such a null-check stops the process of inspecting for null-checks. +

+ While @Data and other method-generating lombok annotations will trigger on any annotation named @NonNull regardless of casing or package name, + this feature only triggers on lombok's own @NonNull annotation from the lombok package. +

+ A @NonNull on a primitive parameter results in a warning. No null-check will be generated. +

+
+
+ +
+
+
+ + + diff --git a/website/features/SneakyThrows.html b/website/features/SneakyThrows.html index 808a7879..573bd95c 100644 --- a/website/features/SneakyThrows.html +++ b/website/features/SneakyThrows.html @@ -54,8 +54,6 @@

Small print

- @SneakyThrows is an implementation of this feature request: http://bugs.sun.com/view_bug.do?bug_id=6534270. -

Because @SneakyThrows is an implementation detail and not part of your method signature, it is an error if you try to declare a checked exception as sneakily thrown when you don't call any methods that throw this exception. (Doing so is perfectly legal for throws statements to accommodate subclasses). Similarly, @SneakyThrows does not inherit. @@ -72,7 +70,7 @@

diff --git a/website/features/Synchronized.html b/website/features/Synchronized.html index 4b6ef251..9ab6c87f 100644 --- a/website/features/Synchronized.html +++ b/website/features/Synchronized.html @@ -59,7 +59,7 @@
diff --git a/website/features/ToString.html b/website/features/ToString.html index c3b389ba..585dc72b 100644 --- a/website/features/ToString.html +++ b/website/features/ToString.html @@ -68,7 +68,7 @@
diff --git a/website/features/index.html b/website/features/index.html index 9751a37d..8b3765c1 100644 --- a/website/features/index.html +++ b/website/features/index.html @@ -13,10 +13,14 @@

Lombok features

+
val
+
Finally! Hassle-free final local variables.
+
@NonNull
+
or: How I learned to stop worrying and love the NullPointerException.
+
@Cleanup
+
Automatic resource management: Call your close() methods safely with no hassle.
@Getter / @Setter
Never write public int getFoo() {return foo;} again.
-
@Getter(lazy=true)
-
Laziness is a virtue!
@ToString
No need to start a debugger to see your fields: Just let lombok generate a toString for you!
@EqualsAndHashCode
@@ -26,16 +30,14 @@
@Data
All together now: A shortcut for @ToString, @EqualsAndHashCode, @Getter on all fields, and @Setter on all non-final fields, and @RequiredArgsConstructor!
-
@Cleanup
-
Automatic resource management: Call your close() methods safely with no hassle.
-
@Synchronized
-
synchronized done right: Don't expose your locks.
@SneakyThrows
To boldly throw checked exceptions where no one has thrown them before!
+
@Synchronized
+
synchronized done right: Don't expose your locks.
+
@Getter(lazy=true)
+
Laziness is a virtue!
@Log
Captain's Log, stardate 24435.7: "What was that line again?"
-
val
-
Finally! Hassle-free final local variables.
@Delegate
Don't lose your composition.
experimental features
diff --git a/website/features/val.html b/website/features/val.html index cec799e9..c4c8ad27 100644 --- a/website/features/val.html +++ b/website/features/val.html @@ -50,7 +50,7 @@
-- cgit From bf43dc747791f9bbf953cfea8200fac478f62d80 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 21:12:03 +0200 Subject: Removed a SuppressWarnings which old eclipse doesn't care about for some reason... now I'm just confused. Do we need it or not? --- src/utils/lombok/core/ImmutableList.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/utils/lombok') diff --git a/src/utils/lombok/core/ImmutableList.java b/src/utils/lombok/core/ImmutableList.java index a151ae6f..8b478dbc 100644 --- a/src/utils/lombok/core/ImmutableList.java +++ b/src/utils/lombok/core/ImmutableList.java @@ -57,7 +57,7 @@ public final class ImmutableList implements Iterable { return new ImmutableList(new Object[] {a, b, c, d, e}); } - public static ImmutableList of(T a, T b, T c, T d, T e, T f, @SuppressWarnings("unchecked") T... g) { + public static ImmutableList of(T a, T b, T c, T d, T e, T f, T... g) { Object[] rest = g == null ? new Object[] {null} : g; Object[] val = new Object[rest.length + 6]; System.arraycopy(rest, 0, val, 6, rest.length); -- cgit From 2d76b1d22dea1e78326ebafdb48967512183cede Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Thu, 23 May 2013 21:12:01 +0200 Subject: First steps Builder support --- .../lombok/eclipse/handlers/HandleBuilder.java | 71 +++++++++++++ src/core/lombok/experimental/Builder.java | 112 +++++++++++++++++++++ src/utils/lombok/core/JavaIdentifiers.java | 57 +++++++++++ 3 files changed, 240 insertions(+) create mode 100644 src/core/lombok/eclipse/handlers/HandleBuilder.java create mode 100644 src/core/lombok/experimental/Builder.java create mode 100644 src/utils/lombok/core/JavaIdentifiers.java (limited to 'src/utils/lombok') diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java new file mode 100644 index 00000000..13271165 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -0,0 +1,71 @@ +/* + * 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.handlers.EclipseHandlerUtil.*; + +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; + +import lombok.AccessLevel; +import lombok.core.AnnotationValues; +import lombok.core.ImmutableList; +import lombok.core.JavaIdentifiers; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.Builder; + +public class HandleBuilder extends EclipseAnnotationHandler { + @Override public void handle(AnnotationValues annotation, Annotation ast, EclipseNode annotationNode) { + String builderMethodName = annotation.getInstance().builderMethodName(); + if (builderMethodName == null) builderMethodName = "builder"; + if (builderMethodName.length() == 0) { + annotationNode.addError("builderMethodName cannot be the empty string."); + return; + } + + if (!JavaIdentifiers.isValidJavaIdentifier(builderMethodName)) { + annotationNode.addError("builderMethodName must be a valid java method name."); + return; + } + + EclipseNode parent = annotationNode.up(); + + if (parent.get() instanceof ConstructorDeclaration) { + + } + + if (parent.get() instanceof MethodDeclaration) { + + } + + if (parent.get() instanceof TypeDeclaration) { + // TODO: How do we ensure this one will 'win' over the implicit constructors generated by @Data and @Value. + new HandleConstructor().generateAllArgsConstructor(parent, AccessLevel.PRIVATE, null, true, Collections.emptyList(), ast); + } + } +} diff --git a/src/core/lombok/experimental/Builder.java b/src/core/lombok/experimental/Builder.java new file mode 100644 index 00000000..b6667462 --- /dev/null +++ b/src/core/lombok/experimental/Builder.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.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}. + *

+ * 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. + *

+ * The effect of {@code @Builder} is that an inner class is generated named TBuilder, + * with a private constructor. Instances of TBuilder are made with the static + * method named {@code builder()} which is also generated for you in the class itself (not in the builder class). + *

+ * The TBuilder 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 build() 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. + *

+ * Complete documentation is found at the project lombok features page for @Builder. + *

+ *

+ * Before: + * + *

+ * @Builder
+ * class Example {
+ * 	private int foo;
+ * 	private final String bar;
+ * }
+ * 
+ * + * After: + * + *
+ * 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);
+ * 		}
+ * 	}
+ * }
+ * 
+ */ +@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"; +} diff --git a/src/utils/lombok/core/JavaIdentifiers.java b/src/utils/lombok/core/JavaIdentifiers.java new file mode 100644 index 00000000..dfec8815 --- /dev/null +++ b/src/utils/lombok/core/JavaIdentifiers.java @@ -0,0 +1,57 @@ +/* + * 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; + +/** + * Utility functions for validating potential java verifiers. + */ +public class JavaIdentifiers { + private JavaIdentifiers() {} + + private static final ImmutableList KEYWORDS = ImmutableList.of( + "public", "private", "protected", + "default", "switch", "case", + "for", "do", "goto", "const", "strictfp", "while", "if", "else", + "byte", "short", "int", "long", "float", "double", "void", "boolean", "char", + "null", "false", "true", + "continue", "break", "return", "instanceof", + "synchronized", "volatile", "transient", "final", "static", + "interface", "class", "extends", "implements", "throws", + "throw", "catch", "try", "finally", "abstract", "assert", + "enum", "import", "package", "native", "new", "super", "this"); + + public static boolean isValidJavaIdentifier(String identifier) { + if (identifier == null) return false; + if (identifier.isEmpty()) return false; + + if (!Character.isJavaIdentifierStart(identifier.charAt(0))) return false; + for (int i = 1; i < identifier.length(); i++) { + if (!Character.isJavaIdentifierPart(identifier.charAt(i))) return false; + } + + return !isKeyword(identifier); + } + + public static boolean isKeyword(String keyword) { + return KEYWORDS.contains(keyword); + } +} -- cgit From e047d2b94e1206bbf304725c20ee20afbd1681fb Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 18 Jun 2013 03:33:13 +0200 Subject: Added a ClassDef wrapper, because its signature changed between javac1.6 and javac1.7. (The wrapper uses reflection). Need for: javac @Builder impl. Also added some utilities to JavacHandlerUtil. --- .../lombok/javac/handlers/JavacHandlerUtil.java | 15 +++++--- src/utils/lombok/javac/Javac.java | 42 ++++++++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) (limited to 'src/utils/lombok') diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 23bc2daf..92cebf4c 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -730,11 +730,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)); @@ -760,7 +760,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) { @@ -1026,6 +1026,13 @@ public class JavacHandlerUtil { return result.toList(); } + public static List copyTypeParams(TreeMaker maker, List params) { + if (params == null || params.isEmpty()) return params; + ListBuffer 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 params) { ListBuffer typeArgs = ListBuffer.lb(); diff --git a/src/utils/lombok/javac/Javac.java b/src/utils/lombok/javac/Javac.java index 08c7c957..4f316d9f 100644 --- a/src/utils/lombok/javac/Javac.java +++ b/src/utils/lombok/javac/Javac.java @@ -22,17 +22,25 @@ package lombok.javac; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.sun.tools.javac.code.TypeTags; import com.sun.tools.javac.main.JavaCompiler; import com.sun.tools.javac.tree.JCTree; +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.JCLiteral; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Name; /** * Container for static utility methods relevant to lombok's operation on javac. @@ -161,4 +169,38 @@ public class Javac { } } + private static Method method; + + public static JCClassDecl ClassDef(TreeMaker maker, JCModifiers mods, Name name, List typarams, JCExpression extending, List implementing, List defs) { + if (method == null) try { + method = TreeMaker.class.getDeclaredMethod("ClassDef", JCModifiers.class, Name.class, List.class, JCExpression.class, List.class, List.class); + } catch (NoSuchMethodException ignore) {} + if (method == null) try { + method = TreeMaker.class.getDeclaredMethod("ClassDef", JCModifiers.class, Name.class, List.class, JCTree.class, List.class, List.class); + } catch (NoSuchMethodException ignore) {} + + if (method == null) throw new IllegalStateException("Lombok bug #20130617-1310: ClassDef doesn't look like anything we thought it would look like."); + if (!Modifier.isPublic(method.getModifiers()) && !method.isAccessible()) { + method.setAccessible(true); + } + + try { + return (JCClassDecl) method.invoke(maker, mods, name, typarams, extending, implementing, defs); + } catch (InvocationTargetException e) { + throw sneakyThrow(e.getCause()); + } catch (IllegalAccessException e) { + throw sneakyThrow(e.getCause()); + } + } + + private static RuntimeException sneakyThrow(Throwable t) { + if (t == null) throw new NullPointerException("t"); + Javac.sneakyThrow0(t); + return null; + } + + @SuppressWarnings("unchecked") + private static void sneakyThrow0(Throwable t) throws T { + throw (T)t; + } } -- cgit From 446a8e33e00cb9effe1d1e181cac192a70648412 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 06:33:18 +0200 Subject: FINALLY! Found the cause of a really weird eclipse bug, where _ANY_ mention of com.sun.tools.javac.tree.TreeMaker, anywhere in a source file, would disable pretty much every intelligent part of what makes the 'I' in IDE in eclipse: No auto-complete, no 'go to declaration', etcetera, but only since Eclipse Juno (not fixed in Kepler either). It's the presence of src/stubs/com/sun/tools/javac/util/Context.java. I've moved Context to a special stubs directory that's only used for javac (so that we still get the benefit of getting some warnings and such when making command line builds), and removed the @Override annotations for where the stubbing is relevant (for methods that exist in javac7 but not in javac6 on interfaces we create implementations of). Furthermore, I did some extremely tricky work in making our version actuall compatible with the exact class signatures of both javac6- and javac7+'s versions; generation of synthetic methods for reified type parameters was causing havoc. A big stack of 'here be voodoo' comments unfortunately added to explain it all; necessary evil. --- build.xml | 1 + .../com/sun/tools/javac/util/Context.java | 31 ++++++++++++++++++++++ src/stubs/com/sun/tools/javac/util/Context.java | 31 ---------------------- .../java6/CommentCollectingScannerFactory.java | 27 ++++++++++++++++--- .../java7/CommentCollectingScannerFactory.java | 29 +++++++++++++++++--- 5 files changed, 80 insertions(+), 39 deletions(-) create mode 100644 src/javac-only-stubs/com/sun/tools/javac/util/Context.java delete mode 100644 src/stubs/com/sun/tools/javac/util/Context.java (limited to 'src/utils/lombok') diff --git a/build.xml b/build.xml index 9ebbe9e7..bf13eeff 100644 --- a/build.xml +++ b/build.xml @@ -147,6 +147,7 @@ the common tasks and can be called on to run the main aspects of all the sub-scr + diff --git a/src/javac-only-stubs/com/sun/tools/javac/util/Context.java b/src/javac-only-stubs/com/sun/tools/javac/util/Context.java new file mode 100644 index 00000000..06b8ff4d --- /dev/null +++ b/src/javac-only-stubs/com/sun/tools/javac/util/Context.java @@ -0,0 +1,31 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.util; + +public class Context { + public static class Key { + } + + public interface Factory { + T make(Context c); + T make(); + } + + public void put(Key key, Factory fac) { + } + + public void put(Key key, T data) { + } + + public void put(Class clazz, T data) { + } + + public T get(Key key) { + return null; + } + + public T get(Class clazz) { + return null; + } +} diff --git a/src/stubs/com/sun/tools/javac/util/Context.java b/src/stubs/com/sun/tools/javac/util/Context.java deleted file mode 100644 index 06b8ff4d..00000000 --- a/src/stubs/com/sun/tools/javac/util/Context.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. - */ -package com.sun.tools.javac.util; - -public class Context { - public static class Key { - } - - public interface Factory { - T make(Context c); - T make(); - } - - public void put(Key key, Factory fac) { - } - - public void put(Key key, T data) { - } - - public void put(Class clazz, T data) { - } - - public T get(Key key) { - return null; - } - - public T get(Class clazz) { - return null; - } -} diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index 30acbd5a..c345526e 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-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,14 +28,33 @@ import com.sun.tools.javac.util.Context; public class CommentCollectingScannerFactory extends Scanner.Factory { + @SuppressWarnings("all") public static void preRegister(final Context context) { if (context.get(scannerFactoryKey) == null) { - context.put(scannerFactoryKey, new Context.Factory() { - public CommentCollectingScanner.Factory make() { + // Careful! There is voodoo magic here! + // + // Context.Factory is parameterized. make() is for javac6 and below; make(Context) is for javac7 and up. + // this anonymous inner class definition is intentionally 'raw' - the return type of both 'make' methods is 'T', + // which means the compiler will only generate the correct "real" override method (with returntype Object, which is + // the lower bound for T, as a synthetic accessor for the make with returntype ScannerFactory) for that make method which + // is actually on the classpath (either make() for javac6-, or make(Context) for javac7+). + // + // We normally solve this issue via src/stubs, with BOTH make methods listed, but for some reason the presence of a stubbed out + // Context (or even a complete copy, it doesn't matter) results in a really strange eclipse bug, where any mention of any kind + // of com.sun.tools.javac.tree.TreeMaker in a source file disables ALL usage of 'go to declaration' and auto-complete in the entire + // source file. + // + // Thus, in short: + // * Do NOT parameterize the anonymous inner class literal. + // * Leave the return types as 'j.l.Object'. + // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. + // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. + context.put(scannerFactoryKey, new Context.Factory() { + public Object make() { return new CommentCollectingScannerFactory(context); } - public CommentCollectingScanner.Factory make(Context c) { + public Object make(Context c) { return new CommentCollectingScannerFactory(c); } }); diff --git a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java index 9a29528e..2032e494 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Project Lombok Authors. + * Copyright (C) 2011-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 @@ -29,14 +29,35 @@ import com.sun.tools.javac.util.Context; public class CommentCollectingScannerFactory extends ScannerFactory { + @SuppressWarnings("all") public static void preRegister(final Context context) { if (context.get(scannerFactoryKey) == null) { - context.put(scannerFactoryKey, new Context.Factory() { - public ScannerFactory make() { + // Careful! There is voodoo magic here! + // + // Context.Factory is parameterized. make() is for javac6 and below; make(Context) is for javac7 and up. + // this anonymous inner class definition is intentionally 'raw' - the return type of both 'make' methods is 'T', + // which means the compiler will only generate the correct "real" override method (with returntype Object, which is + // the lower bound for T, as a synthetic accessor for the make with returntype ScannerFactory) for that make method which + // is actually on the classpath (either make() for javac6-, or make(Context) for javac7+). + // + // We normally solve this issue via src/stubs, with BOTH make methods listed, but for some reason the presence of a stubbed out + // Context (or even a complete copy, it doesn't matter) results in a really strange eclipse bug, where any mention of any kind + // of com.sun.tools.javac.tree.TreeMaker in a source file disables ALL usage of 'go to declaration' and auto-complete in the entire + // source file. + // + // Thus, in short: + // * Do NOT parameterize the anonymous inner class literal. + // * Leave the return types as 'j.l.Object'. + // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. + // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. + context.put(scannerFactoryKey, new Context.Factory() { + // This overrides the javac6- version of make. + public Object make() { return new CommentCollectingScannerFactory(context); } - @Override public ScannerFactory make(Context c) { + // This overrides the javac7+ version. + public Object make(Context c) { return new CommentCollectingScannerFactory(c); } }); -- cgit From 4923506d737718cec49e35aa9a273b3a999eefc6 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Sun, 7 Jul 2013 22:05:41 +0200 Subject: more work on the tricky Context hack to make 'ant compile' not emit warnings. --- .../lombok/javac/java6/CommentCollectingScannerFactory.java | 10 ++++++++-- .../lombok/javac/java7/CommentCollectingScannerFactory.java | 7 +++++-- 2 files changed, 13 insertions(+), 4 deletions(-) (limited to 'src/utils/lombok') diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index c345526e..b7d8ed13 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -49,15 +49,21 @@ public class CommentCollectingScannerFactory extends Scanner.Factory { // * Leave the return types as 'j.l.Object'. // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. - context.put(scannerFactoryKey, new Context.Factory() { + @SuppressWarnings("all") + class MyFactory implements Context.Factory { + // This overrides the javac6- version of make. public Object make() { return new CommentCollectingScannerFactory(context); } + // This overrides the javac7+ version of make. public Object make(Context c) { return new CommentCollectingScannerFactory(c); } - }); + } + + @SuppressWarnings("unchecked") Context.Factory factory = new MyFactory(); + context.put(scannerFactoryKey, factory); } } diff --git a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java index 2032e494..626d3d63 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScannerFactory.java @@ -50,7 +50,8 @@ public class CommentCollectingScannerFactory extends ScannerFactory { // * Leave the return types as 'j.l.Object'. // * Leave both make methods intact; deleting one has no effect on javac6- / javac7+, but breaks the other. Hard to test for. // * Do not stub com.sun.tools.javac.util.Context or any of its inner types, like Factory. - context.put(scannerFactoryKey, new Context.Factory() { + @SuppressWarnings("all") + class MyFactory implements Context.Factory { // This overrides the javac6- version of make. public Object make() { return new CommentCollectingScannerFactory(context); @@ -60,7 +61,9 @@ public class CommentCollectingScannerFactory extends ScannerFactory { public Object make(Context c) { return new CommentCollectingScannerFactory(c); } - }); + } + @SuppressWarnings("unchecked") Context.Factory factory = new MyFactory(); + context.put(scannerFactoryKey, factory); } } -- cgit From a6c1be550fd1911084faaf7f54ae7bbbd5673642 Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Mon, 8 Jul 2013 04:58:00 +0200 Subject: A lot of refactoring on how javadoc is handled, to prepare for copying javadoc from field to setter/getter in javac. --- .../lombok/delombok/PrettyCommentsPrinter.java | 32 +- .../sun/tools/javac/parser/DocCommentScanner.java | 25 ++ src/stubs/com/sun/tools/javac/parser/Scanner.java | 7 + .../com/sun/tools/javadoc/DocCommentScanner.java | 25 ++ src/utils/lombok/javac/CommentCatcher.java | 4 +- src/utils/lombok/javac/CommentInfo.java | 2 +- .../java6/CommentCollectingParserFactory.java | 2 +- .../javac/java6/CommentCollectingScanner.java | 5 +- .../java6/CommentCollectingScannerFactory.java | 2 +- .../lombok/javac/java6/DocCommentScanner.java | 461 +++++++++++++++++++++ .../java7/CommentCollectingParserFactory.java | 4 +- .../javac/java7/CommentCollectingScanner.java | 7 +- 12 files changed, 557 insertions(+), 19 deletions(-) create mode 100644 src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java create mode 100644 src/stubs/com/sun/tools/javadoc/DocCommentScanner.java create mode 100644 src/utils/lombok/javac/java6/DocCommentScanner.java (limited to 'src/utils/lombok') diff --git a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java index 9c6a2bd7..342e3323 100644 --- a/src/delombok/lombok/delombok/PrettyCommentsPrinter.java +++ b/src/delombok/lombok/delombok/PrettyCommentsPrinter.java @@ -224,12 +224,21 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { return tree.getEndPosition(cu.endPositions); } - private void consumeComments(int till) throws IOException { + private void consumeComments(int until) throws IOException { + consumeComments(until, null); + } + private void consumeComments(int until, JCTree tree) throws IOException { boolean prevNewLine = onNewLine; boolean found = false; CommentInfo head = comments.head; - while (comments.nonEmpty() && head.pos < till) { - printComment(head); + while (comments.nonEmpty() && head.pos < until) { + if (tree != null && docComments != null && docComments.containsKey(tree) && head.isJavadoc() && noFurtherJavadocForthcoming(until)) { + // This is (presumably) the exact same javadoc that has already been associated with the node that we're just about to + // print. These javadoc can be modified by lombok handlers, and as such we should NOT print them from the consumed comments db, + // and instead print the actual javadoc associated with the upcoming node (which the visit method for that node will take care of). + } else { + printComment(head); + } comments = comments.tail; head = comments.head; } @@ -237,6 +246,17 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { println(); } } + + private boolean noFurtherJavadocForthcoming(int until) { + List c = comments; + if (c.nonEmpty()) c = c.tail; + while (c.nonEmpty()) { + if (c.head.pos >= until) return true; + if (c.head.isJavadoc()) return false; + c = c.tail; + } + return true; + } private void consumeTrailingComments(int from) throws IOException { boolean prevNewLine = onNewLine; @@ -408,7 +428,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { this.prec = prec; if (tree == null) print("/*missing*/"); else { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); tree.accept(this); int endPos = endPos(tree); consumeTrailingComments(endPos); @@ -620,7 +640,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { docComments = tree.docComments; printDocComment(tree); if (tree.pid != null) { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); print("package "); printExpr(tree.pid); print(";"); @@ -694,7 +714,7 @@ public class PrettyCommentsPrinter extends JCTree.Visitor { public void visitClassDef(JCClassDecl tree) { try { - consumeComments(tree.pos); + consumeComments(tree.pos, tree); println(); align(); printDocComment(tree); printAnnotations(tree.mods.annotations); diff --git a/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java b/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java new file mode 100644 index 00000000..d461d54b --- /dev/null +++ b/src/stubs/com/sun/tools/javac/parser/DocCommentScanner.java @@ -0,0 +1,25 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javac.parser; + +import java.nio.CharBuffer; + +public class DocCommentScanner extends Scanner { + protected DocCommentScanner(Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + protected DocCommentScanner(ScannerFactory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(ScannerFactory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + +} diff --git a/src/stubs/com/sun/tools/javac/parser/Scanner.java b/src/stubs/com/sun/tools/javac/parser/Scanner.java index af94bacc..266208e5 100644 --- a/src/stubs/com/sun/tools/javac/parser/Scanner.java +++ b/src/stubs/com/sun/tools/javac/parser/Scanner.java @@ -59,4 +59,11 @@ public class Scanner implements Lexer { public char[] getRawCharacters(int beginIndex, int endIndex) { return null; } + + public void nextToken() { + } + + public char[] getRawCharacters() { + return new char[0]; + } } diff --git a/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java b/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java new file mode 100644 index 00000000..e2ea9614 --- /dev/null +++ b/src/stubs/com/sun/tools/javadoc/DocCommentScanner.java @@ -0,0 +1,25 @@ +/* + * These are stub versions of various bits of javac-internal API (for various different versions of javac). Lombok is compiled against these. + */ +package com.sun.tools.javadoc; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.util.Context; + +public class DocCommentScanner extends Scanner { + protected DocCommentScanner(com.sun.tools.javadoc.DocCommentScanner.Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + protected DocCommentScanner(com.sun.tools.javadoc.DocCommentScanner.Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + public static class Factory extends Scanner.Factory { + protected Factory(Context context) { + super(context); + } + } +} diff --git a/src/utils/lombok/javac/CommentCatcher.java b/src/utils/lombok/javac/CommentCatcher.java index 2825cd30..565a166d 100644 --- a/src/utils/lombok/javac/CommentCatcher.java +++ b/src/utils/lombok/javac/CommentCatcher.java @@ -78,10 +78,10 @@ public class CommentCatcher { try { if (Javac.getJavaCompilerVersion() <= 6) { Class parserFactory = Class.forName("lombok.javac.java6.CommentCollectingParserFactory"); - parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); + parserFactory.getMethod("setInCompiler", JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); } else { Class parserFactory = Class.forName("lombok.javac.java7.CommentCollectingParserFactory"); - parserFactory.getMethod("setInCompiler",JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); + parserFactory.getMethod("setInCompiler", JavaCompiler.class, Context.class, Map.class).invoke(null, compiler, context, commentsMap); } } catch (Exception e) { if (e instanceof RuntimeException) throw (RuntimeException)e; diff --git a/src/utils/lombok/javac/CommentInfo.java b/src/utils/lombok/javac/CommentInfo.java index 7375d51a..afdd8469 100644 --- a/src/utils/lombok/javac/CommentInfo.java +++ b/src/utils/lombok/javac/CommentInfo.java @@ -66,7 +66,7 @@ public final class CommentInfo { } public boolean isJavadoc() { - return content.startsWith("/**"); + return content.startsWith("/**") && content.length() > 4; } @Override diff --git a/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java index b2a248c8..7e34b723 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingParserFactory.java @@ -25,7 +25,7 @@ public class CommentCollectingParserFactory extends Parser.Factory { } @Override public Parser newParser(Lexer S, boolean keepDocComments, boolean genEndPos) { - Object x = new CommentCollectingParser(this, S, keepDocComments, commentsMap); + Object x = new CommentCollectingParser(this, S, true, commentsMap); return (Parser) x; // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either //javac6's EndPosParser which extends Parser, or javac7's EndPosParser which implements Parser. diff --git a/src/utils/lombok/javac/java6/CommentCollectingScanner.java b/src/utils/lombok/javac/java6/CommentCollectingScanner.java index 66e1514d..b584ec16 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScanner.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScanner.java @@ -27,12 +27,10 @@ import lombok.javac.CommentInfo; import lombok.javac.CommentInfo.EndConnection; import lombok.javac.CommentInfo.StartConnection; -import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; - -public class CommentCollectingScanner extends Scanner { +public class CommentCollectingScanner extends DocCommentScanner { private final ListBuffer comments = ListBuffer.lb(); private int endComment = 0; @@ -56,6 +54,7 @@ public class CommentCollectingScanner extends Scanner { CommentInfo comment = new CommentInfo(prevEndPos, pos, endPos, content, start, end); comments.append(comment); + super.processComment(style); } private EndConnection determineEndConnection(int pos) { diff --git a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java index b7d8ed13..f3d6bd72 100644 --- a/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java +++ b/src/utils/lombok/javac/java6/CommentCollectingScannerFactory.java @@ -26,7 +26,7 @@ import java.nio.CharBuffer; import com.sun.tools.javac.parser.Scanner; import com.sun.tools.javac.util.Context; -public class CommentCollectingScannerFactory extends Scanner.Factory { +public class CommentCollectingScannerFactory extends DocCommentScanner.Factory { @SuppressWarnings("all") public static void preRegister(final Context context) { diff --git a/src/utils/lombok/javac/java6/DocCommentScanner.java b/src/utils/lombok/javac/java6/DocCommentScanner.java new file mode 100644 index 00000000..ff3eadd4 --- /dev/null +++ b/src/utils/lombok/javac/java6/DocCommentScanner.java @@ -0,0 +1,461 @@ +package lombok.javac.java6; + +/* + * Copyright 2004-2006 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +import static com.sun.tools.javac.util.LayoutCharacters.*; + +import java.nio.CharBuffer; + +import com.sun.tools.javac.parser.Scanner; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.Position; + +/** An extension to the base lexical analyzer that captures + * and processes the contents of doc comments. It does so by + * translating Unicode escape sequences and by stripping the + * leading whitespace and starts from each line of the comment. + * + *

This is NOT part of any API supported by Sun Microsystems. If + * you write code that depends on this, you do so at your own risk. + * This code and its internal interfaces are subject to change or + * deletion without notice. + */ +public class DocCommentScanner extends Scanner { + + /** A factory for creating scanners. */ + public static class Factory extends Scanner.Factory { + + @SuppressWarnings({"unchecked", "all"}) + public static void preRegister(final Context context) { + context.put(scannerFactoryKey, new Context.Factory() { + public Object make() { + return new Factory(context); + } + + public Object make(Context c) { + return new Factory(c); + } + }); + } + + /** Create a new scanner factory. */ + protected Factory(Context context) { + super(context); + } + + @Override + public Scanner newScanner(CharSequence input) { + if (input instanceof CharBuffer) { + return new DocCommentScanner(this, (CharBuffer)input); + } else { + char[] array = input.toString().toCharArray(); + return newScanner(array, array.length); + } + } + + @Override + public Scanner newScanner(char[] input, int inputLength) { + return new DocCommentScanner(this, input, inputLength); + } + } + + + /** Create a scanner from the input buffer. buffer must implement + * array() and compact(), and remaining() must be less than limit(). + */ + protected DocCommentScanner(Factory fac, CharBuffer buffer) { + super(fac, buffer); + } + + /** Create a scanner from the input array. The array must have at + * least a single character of extra space. + */ + protected DocCommentScanner(Factory fac, char[] input, int inputLength) { + super(fac, input, inputLength); + } + + /** Starting position of the comment in original source + */ + private int pos; + + /** The comment input buffer, index of next chacter to be read, + * index of one past last character in buffer. + */ + private char[] buf; + private int bp; + private int buflen; + + /** The current character. + */ + private char ch; + + /** The column number position of the current character. + */ + private int col; + + /** The buffer index of the last converted Unicode character + */ + private int unicodeConversionBp = 0; + + /** + * Buffer for doc comment. + */ + private char[] docCommentBuffer = new char[1024]; + + /** + * Number of characters in doc comment buffer. + */ + private int docCommentCount; + + /** + * Translated and stripped contents of doc comment + */ + private String docComment = null; + + + /** Unconditionally expand the comment buffer. + */ + private void expandCommentBuffer() { + char[] newBuffer = new char[docCommentBuffer.length * 2]; + System.arraycopy(docCommentBuffer, 0, newBuffer, + 0, docCommentBuffer.length); + docCommentBuffer = newBuffer; + } + + /** Convert an ASCII digit from its base (8, 10, or 16) + * to its value. + */ + private int digit(int base) { + char c = ch; + int result = Character.digit(c, base); + if (result >= 0 && c > 0x7f) { + ch = "0123456789abcdef".charAt(result); + } + return result; + } + + /** Convert Unicode escape; bp points to initial '\' character + * (Spec 3.3). + */ + private void convertUnicode() { + if (ch == '\\' && unicodeConversionBp != bp) { + bp++; ch = buf[bp]; col++; + if (ch == 'u') { + do { + bp++; ch = buf[bp]; col++; + } while (ch == 'u'); + int limit = bp + 3; + if (limit < buflen) { + int d = digit(16); + int code = d; + while (bp < limit && d >= 0) { + bp++; ch = buf[bp]; col++; + d = digit(16); + code = (code << 4) + d; + } + if (d >= 0) { + ch = (char)code; + unicodeConversionBp = bp; + return; + } + } + // "illegal.Unicode.esc", reported by base scanner + } else { + bp--; + ch = '\\'; + col--; + } + } + } + + + /** Read next character. + */ + private void scanChar() { + bp++; + ch = buf[bp]; + switch (ch) { + case '\r': // return + col = 0; + break; + case '\n': // newline + if (bp == 0 || buf[bp-1] != '\r') { + col = 0; + } + break; + case '\t': // tab + col = (col / TabInc * TabInc) + TabInc; + break; + case '\\': // possible Unicode + col++; + convertUnicode(); + break; + default: + col++; + break; + } + } + + /** + * Read next character in doc comment, skipping over double '\' characters. + * If a double '\' is skipped, put in the buffer and update buffer count. + */ + private void scanDocCommentChar() { + scanChar(); + if (ch == '\\') { + if (buf[bp+1] == '\\' && unicodeConversionBp != bp) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + bp++; col++; + } else { + convertUnicode(); + } + } + } + + /* Reset doc comment before reading each new token + */ + public void nextToken() { + docComment = null; + super.nextToken(); + } + + /** + * Returns the documentation string of the current token. + */ + public String docComment() { + return docComment; + } + + /** + * Process a doc comment and make the string content available. + * Strips leading whitespace and stars. + */ + @SuppressWarnings("fallthrough") + protected void processComment(CommentStyle style) { + if (style != CommentStyle.JAVADOC) { + return; + } + + pos = pos(); + buf = getRawCharacters(pos, endPos()); + buflen = buf.length; + bp = 0; + col = 0; + + docCommentCount = 0; + + boolean firstLine = true; + + // Skip over first slash + scanDocCommentChar(); + // Skip over first star + scanDocCommentChar(); + + // consume any number of stars + while (bp < buflen && ch == '*') { + scanDocCommentChar(); + } + // is the comment in the form /**/, /***/, /****/, etc. ? + if (bp < buflen && ch == '/') { + docComment = ""; + return; + } + + // skip a newline on the first line of the comment. + if (bp < buflen) { + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } else if (ch == CR) { + scanDocCommentChar(); + if (ch == LF) { + scanDocCommentChar(); + firstLine = false; + } + } + } + + outerLoop: + + // The outerLoop processes the doc comment, looping once + // for each line. For each line, it first strips off + // whitespace, then it consumes any stars, then it + // puts the rest of the line into our buffer. + while (bp < buflen) { + + // The wsLoop consumes whitespace from the beginning + // of each line. + wsLoop: + + while (bp < buflen) { + switch(ch) { + case ' ': + scanDocCommentChar(); + break; + case '\t': + col = ((col - 1) / TabInc * TabInc) + TabInc; + scanDocCommentChar(); + break; + case FF: + col = 0; + scanDocCommentChar(); + break; +// Treat newline at beginning of line (blank line, no star) +// as comment text. Old Javadoc compatibility requires this. +/*---------------------------------* + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch == LF) { + col = 0; + scanDocCommentChar(); + } + break; + case LF: // (Spec 3.4) + scanDocCommentChar(); + break; +*---------------------------------*/ + default: + // we've seen something that isn't whitespace; + // jump out. + break wsLoop; + } + } + + // Are there stars here? If so, consume them all + // and check for the end of comment. + if (ch == '*') { + // skip all of the stars + do { + scanDocCommentChar(); + } while (ch == '*'); + + // check for the closing slash. + if (ch == '/') { + // We're done with the doc comment + // scanChar() and breakout. + break outerLoop; + } + } else if (! firstLine) { + //The current line does not begin with a '*' so we will indent it. + for (int i = 1; i < col; i++) { + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ' '; + } + } + + // The textLoop processes the rest of the characters + // on the line, adding them to our buffer. + textLoop: + while (bp < buflen) { + switch (ch) { + case '*': + // Is this just a star? Or is this the + // end of a comment? + scanDocCommentChar(); + if (ch == '/') { + // This is the end of the comment, + // set ch and return our buffer. + break outerLoop; + } + // This is just an ordinary star. Add it to + // the buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = '*'; + break; + case ' ': + case '\t': + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break; + case FF: + scanDocCommentChar(); + break textLoop; // treat as end of line + case CR: // (Spec 3.4) + scanDocCommentChar(); + if (ch != LF) { + // Canonicalize CR-only line terminator to LF + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = (char)LF; + break textLoop; + } + /* fall through to LF case */ + case LF: // (Spec 3.4) + // We've seen a newline. Add it to our + // buffer and break out of this loop, + // starting fresh on a new line. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + break textLoop; + default: + // Add the character to our buffer. + if (docCommentCount == docCommentBuffer.length) + expandCommentBuffer(); + docCommentBuffer[docCommentCount++] = ch; + scanDocCommentChar(); + } + } // end textLoop + firstLine = false; + } // end outerLoop + + if (docCommentCount > 0) { + int i = docCommentCount - 1; + trailLoop: + while (i > -1) { + switch (docCommentBuffer[i]) { + case '*': + i--; + break; + default: + break trailLoop; + } + } + docCommentCount = i + 1; + + // Store the text of the doc comment + docComment = new String(docCommentBuffer, 0 , docCommentCount); + } else { + docComment = ""; + } + } + + /** Build a map for translating between line numbers and + * positions in the input. + * + * @return a LineMap */ + public Position.LineMap getLineMap() { + char[] buf = getRawCharacters(); + return Position.makeLineMap(buf, buf.length, true); + } +} diff --git a/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java b/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java index e361a5bd..e9575c14 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java +++ b/src/utils/lombok/javac/java7/CommentCollectingParserFactory.java @@ -30,8 +30,8 @@ public class CommentCollectingParserFactory extends ParserFactory { public Parser newParser(CharSequence input, boolean keepDocComments, boolean keepEndPos, boolean keepLineMap) { ScannerFactory scannerFactory = ScannerFactory.instance(context); - Lexer lexer = scannerFactory.newScanner(input, keepDocComments); - Object x = new CommentCollectingParser(this, lexer, keepDocComments, keepLineMap, commentsMap); + Lexer lexer = scannerFactory.newScanner(input, true); + Object x = new CommentCollectingParser(this, lexer, true, keepLineMap, commentsMap); return (Parser) x; // CCP is based on a stub which extends nothing, but at runtime the stub is replaced with either //javac6's EndPosParser which extends Parser, or javac7's EndPosParser which implements Parser. diff --git a/src/utils/lombok/javac/java7/CommentCollectingScanner.java b/src/utils/lombok/javac/java7/CommentCollectingScanner.java index e2d040f2..6ebd3ac1 100644 --- a/src/utils/lombok/javac/java7/CommentCollectingScanner.java +++ b/src/utils/lombok/javac/java7/CommentCollectingScanner.java @@ -26,12 +26,12 @@ import java.nio.CharBuffer; import lombok.javac.CommentInfo; import lombok.javac.CommentInfo.EndConnection; import lombok.javac.CommentInfo.StartConnection; + import com.sun.tools.javac.util.List; import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.parser.DocCommentScanner; -import com.sun.tools.javac.parser.Scanner; - -public class CommentCollectingScanner extends Scanner { +public class CommentCollectingScanner extends DocCommentScanner { private final ListBuffer comments = ListBuffer.lb(); private int endComment = 0; @@ -55,6 +55,7 @@ public class CommentCollectingScanner extends Scanner { CommentInfo comment = new CommentInfo(prevEndPos, pos, endPos, content, start, end); comments.append(comment); + super.processComment(style); } private EndConnection determineEndConnection(int pos) { -- cgit From ec0cc4348cf71d872b796d0733fb64fc576ef5df Mon Sep 17 00:00:00 2001 From: Reinier Zwitserloot Date: Tue, 16 Jul 2013 00:45:09 +0200 Subject: Renamed ImmutableList to LombokImmutableList, to reduce our ImmutableList coming up in autocomplete dialogs when guava's was intended. --- src/core/lombok/core/AST.java | 2 +- src/core/lombok/core/LombokNode.java | 8 +- src/core/lombok/eclipse/EclipseAST.java | 4 +- src/utils/lombok/core/ImmutableList.java | 188 ------------------------- src/utils/lombok/core/JavaIdentifiers.java | 2 +- src/utils/lombok/core/LombokImmutableList.java | 188 +++++++++++++++++++++++++ 6 files changed, 196 insertions(+), 196 deletions(-) delete mode 100644 src/utils/lombok/core/ImmutableList.java create mode 100644 src/utils/lombok/core/LombokImmutableList.java (limited to 'src/utils/lombok') diff --git a/src/core/lombok/core/AST.java b/src/core/lombok/core/AST.java index e6721b80..6fed0252 100644 --- a/src/core/lombok/core/AST.java +++ b/src/core/lombok/core/AST.java @@ -174,7 +174,7 @@ public abstract class AST, L extends LombokNode, oldChild.parent = targetNode; } - targetNode.children = ImmutableList.copyOf(children); + targetNode.children = LombokImmutableList.copyOf(children); return targetNode; } diff --git a/src/core/lombok/core/LombokNode.java b/src/core/lombok/core/LombokNode.java index 30bacc56..07c62151 100644 --- a/src/core/lombok/core/LombokNode.java +++ b/src/core/lombok/core/LombokNode.java @@ -42,7 +42,7 @@ public abstract class LombokNode, L extends LombokNode children; + protected LombokImmutableList 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, L extends LombokNodeof(); + this.children = children != null ? LombokImmutableList.copyOf(children) : LombokImmutableList.of(); for (L child : this.children) { child.parent = (L) this; if (!child.isStructurallySignificant) @@ -176,7 +176,7 @@ public abstract class LombokNode, L extends LombokNode down() { + public LombokImmutableList down() { return children; } @@ -253,7 +253,7 @@ public abstract class LombokNode, L extends LombokNode { } void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) { - ImmutableList children = node.down(); + LombokImmutableList children = node.down(); int len = children.size(); for (int i = 0; i < len; i++) { children.get(i).traverse(visitor); diff --git a/src/utils/lombok/core/ImmutableList.java b/src/utils/lombok/core/ImmutableList.java deleted file mode 100644 index 8b478dbc..00000000 --- a/src/utils/lombok/core/ImmutableList.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * 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.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.NoSuchElementException; - -public final class ImmutableList implements Iterable { - private Object[] content; - private static final ImmutableList EMPTY = new ImmutableList(new Object[0]); - - @SuppressWarnings("unchecked") - public static ImmutableList of() { - return (ImmutableList) EMPTY; - } - - public static ImmutableList of(T a) { - return new ImmutableList(new Object[] {a}); - } - - public static ImmutableList of(T a, T b) { - return new ImmutableList(new Object[] {a, b}); - } - - public static ImmutableList of(T a, T b, T c) { - return new ImmutableList(new Object[] {a, b, c}); - } - - public static ImmutableList of(T a, T b, T c, T d) { - return new ImmutableList(new Object[] {a, b, c, d}); - } - - public static ImmutableList of(T a, T b, T c, T d, T e) { - return new ImmutableList(new Object[] {a, b, c, d, e}); - } - - public static ImmutableList of(T a, T b, T c, T d, T e, T f, T... g) { - Object[] rest = g == null ? new Object[] {null} : g; - Object[] val = new Object[rest.length + 6]; - System.arraycopy(rest, 0, val, 6, rest.length); - val[0] = a; - val[1] = b; - val[2] = c; - val[3] = d; - val[4] = e; - val[5] = f; - return new ImmutableList(val); - } - - public static ImmutableList copyOf(Collection list) { - return new ImmutableList(list.toArray()); - } - - public static ImmutableList copyOf(Iterable iterable) { - List list = new ArrayList(); - for (T o : iterable) list.add(o); - return copyOf(list); - } - - private ImmutableList(Object[] content) { - this.content = content; - } - - public ImmutableList replaceElementAt(int idx, T newValue) { - Object[] newContent = content.clone(); - newContent[idx] = newValue; - return new ImmutableList(newContent); - } - - public ImmutableList append(T newValue) { - int len = content.length; - Object[] newContent = new Object[len + 1]; - System.arraycopy(content, 0, newContent, 0, len); - newContent[len] = newValue; - return new ImmutableList(newContent); - } - - public ImmutableList prepend(T newValue) { - int len = content.length; - Object[] newContent = new Object[len + 1]; - System.arraycopy(content, 0, newContent, 1, len); - newContent[0] = newValue; - return new ImmutableList(newContent); - } - - public int indexOf(T val) { - int len = content.length; - if (val == null) { - for (int i = 0; i < len; i++) if (content[i] == null) return i; - return -1; - } - - for (int i = 0; i < len; i++) if (val.equals(content[i])) return i; - return -1; - } - - public ImmutableList removeElement(T val) { - int idx = indexOf(val); - return idx == -1 ? this : removeElementAt(idx); - } - - public ImmutableList removeElementAt(int idx) { - int len = content.length; - Object[] newContent = new Object[len - 1]; - if (idx > 0) System.arraycopy(content, 0, newContent, 0, idx); - if (idx < len - 1) System.arraycopy(content, idx + 1, newContent, idx, len - idx - 1); - return new ImmutableList(newContent); - } - - public boolean isEmpty() { - return content.length == 0; - } - - public int size() { - return content.length; - } - - @SuppressWarnings("unchecked") - public T get(int idx) { - return (T) content[idx]; - } - - public boolean contains(T in) { - if (in == null) { - for (Object e : content) if (e == null) return true; - return false; - } - - for (Object e : content) if (in.equals(e)) return true; - return false; - } - - public Iterator iterator() { - return new Iterator() { - private int idx = 0; - @Override public boolean hasNext() { - return idx < content.length; - } - - @SuppressWarnings("unchecked") - @Override public T next() { - if (idx < content.length) return (T) content[idx++]; - throw new NoSuchElementException(); - } - - @Override public void remove() { - throw new UnsupportedOperationException("List is immutable"); - } - }; - } - - @Override public String toString() { - return Arrays.toString(content); - } - - @Override public boolean equals(Object obj) { - if (!(obj instanceof ImmutableList)) return false; - if (obj == this) return true; - return Arrays.equals(content, ((ImmutableList) obj).content); - } - - @Override public int hashCode() { - return Arrays.hashCode(content); - } -} diff --git a/src/utils/lombok/core/JavaIdentifiers.java b/src/utils/lombok/core/JavaIdentifiers.java index dfec8815..cbe90eed 100644 --- a/src/utils/lombok/core/JavaIdentifiers.java +++ b/src/utils/lombok/core/JavaIdentifiers.java @@ -27,7 +27,7 @@ package lombok.core; public class JavaIdentifiers { private JavaIdentifiers() {} - private static final ImmutableList KEYWORDS = ImmutableList.of( + private static final LombokImmutableList KEYWORDS = LombokImmutableList.of( "public", "private", "protected", "default", "switch", "case", "for", "do", "goto", "const", "strictfp", "while", "if", "else", diff --git a/src/utils/lombok/core/LombokImmutableList.java b/src/utils/lombok/core/LombokImmutableList.java new file mode 100644 index 00000000..e0e1136c --- /dev/null +++ b/src/utils/lombok/core/LombokImmutableList.java @@ -0,0 +1,188 @@ +/* + * 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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +public final class LombokImmutableList implements Iterable { + private Object[] content; + private static final LombokImmutableList EMPTY = new LombokImmutableList(new Object[0]); + + @SuppressWarnings("unchecked") + public static LombokImmutableList of() { + return (LombokImmutableList) EMPTY; + } + + public static LombokImmutableList of(T a) { + return new LombokImmutableList(new Object[] {a}); + } + + public static LombokImmutableList of(T a, T b) { + return new LombokImmutableList(new Object[] {a, b}); + } + + public static LombokImmutableList of(T a, T b, T c) { + return new LombokImmutableList(new Object[] {a, b, c}); + } + + public static LombokImmutableList of(T a, T b, T c, T d) { + return new LombokImmutableList(new Object[] {a, b, c, d}); + } + + public static LombokImmutableList of(T a, T b, T c, T d, T e) { + return new LombokImmutableList(new Object[] {a, b, c, d, e}); + } + + public static LombokImmutableList of(T a, T b, T c, T d, T e, T f, T... g) { + Object[] rest = g == null ? new Object[] {null} : g; + Object[] val = new Object[rest.length + 6]; + System.arraycopy(rest, 0, val, 6, rest.length); + val[0] = a; + val[1] = b; + val[2] = c; + val[3] = d; + val[4] = e; + val[5] = f; + return new LombokImmutableList(val); + } + + public static LombokImmutableList copyOf(Collection list) { + return new LombokImmutableList(list.toArray()); + } + + public static LombokImmutableList copyOf(Iterable iterable) { + List list = new ArrayList(); + for (T o : iterable) list.add(o); + return copyOf(list); + } + + private LombokImmutableList(Object[] content) { + this.content = content; + } + + public LombokImmutableList replaceElementAt(int idx, T newValue) { + Object[] newContent = content.clone(); + newContent[idx] = newValue; + return new LombokImmutableList(newContent); + } + + public LombokImmutableList append(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 0, len); + newContent[len] = newValue; + return new LombokImmutableList(newContent); + } + + public LombokImmutableList prepend(T newValue) { + int len = content.length; + Object[] newContent = new Object[len + 1]; + System.arraycopy(content, 0, newContent, 1, len); + newContent[0] = newValue; + return new LombokImmutableList(newContent); + } + + public int indexOf(T val) { + int len = content.length; + if (val == null) { + for (int i = 0; i < len; i++) if (content[i] == null) return i; + return -1; + } + + for (int i = 0; i < len; i++) if (val.equals(content[i])) return i; + return -1; + } + + public LombokImmutableList removeElement(T val) { + int idx = indexOf(val); + return idx == -1 ? this : removeElementAt(idx); + } + + public LombokImmutableList removeElementAt(int idx) { + int len = content.length; + Object[] newContent = new Object[len - 1]; + if (idx > 0) System.arraycopy(content, 0, newContent, 0, idx); + if (idx < len - 1) System.arraycopy(content, idx + 1, newContent, idx, len - idx - 1); + return new LombokImmutableList(newContent); + } + + public boolean isEmpty() { + return content.length == 0; + } + + public int size() { + return content.length; + } + + @SuppressWarnings("unchecked") + public T get(int idx) { + return (T) content[idx]; + } + + public boolean contains(T in) { + if (in == null) { + for (Object e : content) if (e == null) return true; + return false; + } + + for (Object e : content) if (in.equals(e)) return true; + return false; + } + + public Iterator iterator() { + return new Iterator() { + private int idx = 0; + @Override public boolean hasNext() { + return idx < content.length; + } + + @SuppressWarnings("unchecked") + @Override public T next() { + if (idx < content.length) return (T) content[idx++]; + throw new NoSuchElementException(); + } + + @Override public void remove() { + throw new UnsupportedOperationException("List is immutable"); + } + }; + } + + @Override public String toString() { + return Arrays.toString(content); + } + + @Override public boolean equals(Object obj) { + if (!(obj instanceof LombokImmutableList)) return false; + if (obj == this) return true; + return Arrays.equals(content, ((LombokImmutableList) obj).content); + } + + @Override public int hashCode() { + return Arrays.hashCode(content); + } +} -- cgit