diff options
| author | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-07-22 23:23:46 +0200 |
|---|---|---|
| committer | Reinier Zwitserloot <reinier@zwitserloot.com> | 2013-07-22 23:23:46 +0200 |
| commit | 45697b50816df79475a8bb69dc89ff68747fbfe6 (patch) | |
| tree | 25cb023eec1f74baf5063cc5a58a5351ee43d6f0 /src/core/lombok/eclipse | |
| parent | 4c03e3d220900431085897878d4888bf530b31ec (diff) | |
| parent | deed98be16e5099af52d951fc611f86a82a42858 (diff) | |
| download | lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.tar.gz lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.tar.bz2 lombok-45697b50816df79475a8bb69dc89ff68747fbfe6.zip | |
Merge branch 'master' into jdk8. Also added some major fixes whilst merging.
Conflicts:
src/core/lombok/javac/handlers/JavacHandlerUtil.java
src/utils/lombok/javac/CommentCatcher.java
src/utils/lombok/javac/Javac.java
Diffstat (limited to 'src/core/lombok/eclipse')
20 files changed, 979 insertions, 165 deletions
diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 8ab42140..370b40fc 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2010 The Project Lombok Authors. + * Copyright (C) 2009-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -24,12 +24,14 @@ package lombok.eclipse; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; +import org.eclipse.jdt.internal.compiler.CompilationResult; import java.util.Collection; import java.util.Collections; import java.util.List; import lombok.Lombok; import lombok.core.AST; +import lombok.core.LombokImmutableList; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; @@ -55,7 +57,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * @param ast The compilation unit, which serves as the top level node in the tree to be built. */ public EclipseAST(CompilationUnitDeclaration ast) { - super(toFileName(ast), packageDeclaration(ast), imports(ast)); + super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast)); this.compilationUnitDeclaration = ast; setTop(buildCompilationUnit(ast)); this.completeParse = isComplete(ast); @@ -67,16 +69,18 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { return pkg == null ? null : Eclipse.toQualifiedName(pkg.getImportName()); } - private static Collection<String> imports(CompilationUnitDeclaration cud) { - List<String> imports = new ArrayList<String>(); - if (cud.imports == null) return imports; - for (ImportReference imp : cud.imports) { - if (imp == null) continue; - String qualifiedName = Eclipse.toQualifiedName(imp.getImportName()); - if ((imp.bits & ASTNode.OnDemand) != 0) qualifiedName += ".*"; - imports.add(qualifiedName); - } - return imports; + @Override public int getSourceVersion() { + long sl = compilationUnitDeclaration.problemReporter.options.sourceLevel; + long cl = compilationUnitDeclaration.problemReporter.options.complianceLevel; + sl >>= 16; + cl >>= 16; + if (sl == 0) sl = cl; + if (cl == 0) cl = sl; + return Math.min((int)(sl - 44), (int)(cl - 44)); + } + + @Override public int getLatestJavaSpecSupported() { + return Eclipse.getEcjCompilerVersion(); } /** @@ -88,8 +92,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } void traverseChildren(EclipseASTVisitor visitor, EclipseNode node) { - for (EclipseNode child : node.down()) { - child.traverse(visitor); + LombokImmutableList<EclipseNode> children = node.down(); + int len = children.size(); + for (int i = 0; i < len; i++) { + children.get(i).traverse(visitor); } } @@ -118,7 +124,8 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } void addToCompilationResult() { - addProblemToCompilationResult((CompilationUnitDeclaration) top().get(), + CompilationUnitDeclaration cud = (CompilationUnitDeclaration) top().get(); + addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, isWarning, message, sourceStart, sourceEnd); } } @@ -142,11 +149,10 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; try { - EcjReflectionCheck.addProblemToCompilationResult.invoke(null, ast, isWarning, message, sourceStart, sourceEnd); + EcjReflectionCheck.addProblemToCompilationResult.invoke(null, fileNameArray, result, isWarning, message, sourceStart, sourceEnd); } catch (NoClassDefFoundError e) { //ignore, we don't have access to the correct ECJ classes, so lombok can't possibly //do anything useful here. @@ -163,7 +169,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { //do anything useful here. } } - + private final CompilationUnitDeclaration compilationUnitDeclaration; private boolean completeParse; @@ -353,7 +359,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } private static class EcjReflectionCheck { - private static final String CUD_TYPE = "org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration"; + private static final String COMPILATIONRESULT_TYPE = "org.eclipse.jdt.internal.compiler.CompilationResult"; public static Method addProblemToCompilationResult; public static final Throwable problem; @@ -362,7 +368,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { Throwable problem_ = null; Method m = null; try { - m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", Class.forName(CUD_TYPE), boolean.class, String.class, int.class, int.class); + m = EclipseAstProblemView.class.getMethod("addProblemToCompilationResult", char[].class, Class.forName(COMPILATIONRESULT_TYPE), boolean.class, String.class, int.class, int.class); } catch (Throwable t) { // That's problematic, but as long as no local classes are used we don't actually need it. // Better fail on local classes than crash altogether. diff --git a/src/core/lombok/eclipse/EclipseAnnotationHandler.java b/src/core/lombok/eclipse/EclipseAnnotationHandler.java index 84304339..ca9965f7 100644 --- a/src/core/lombok/eclipse/EclipseAnnotationHandler.java +++ b/src/core/lombok/eclipse/EclipseAnnotationHandler.java @@ -29,7 +29,7 @@ import lombok.core.SpiLoadUtil; * * You MUST replace 'T' with a specific annotation type, such as: * - * {@code public class HandleGetter implements EclipseAnnotationHandler<Getter>} + * {@code public class HandleGetter extends EclipseAnnotationHandler<Getter>} * * Because this generics parameter is inspected to figure out which class you're interested in. * diff --git a/src/core/lombok/eclipse/EclipseAstProblemView.java b/src/core/lombok/eclipse/EclipseAstProblemView.java index a2d5b833..c1179666 100644 --- a/src/core/lombok/eclipse/EclipseAstProblemView.java +++ b/src/core/lombok/eclipse/EclipseAstProblemView.java @@ -3,7 +3,6 @@ package lombok.eclipse; import org.eclipse.jdt.core.compiler.CategorizedProblem; import org.eclipse.jdt.internal.compiler.CompilationResult; -import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.problem.DefaultProblem; import org.eclipse.jdt.internal.compiler.problem.ProblemSeverities; import org.eclipse.jdt.internal.compiler.util.Util; @@ -13,14 +12,12 @@ public class EclipseAstProblemView { * Adds a problem to the provided CompilationResult object so that it will show up * in the Problems/Warnings view. */ - public static void addProblemToCompilationResult(CompilationUnitDeclaration ast, + public static void addProblemToCompilationResult(char[] fileNameArray, CompilationResult result, boolean isWarning, String message, int sourceStart, int sourceEnd) { - if (ast.compilationResult == null) return; - char[] fileNameArray = ast.getFileName(); + if (result == null) return; if (fileNameArray == null) fileNameArray = "(unknown).java".toCharArray(); int lineNumber = 0; int columnNumber = 1; - CompilationResult result = ast.compilationResult; int[] lineEnds = null; lineNumber = sourceStart >= 0 ? Util.getLineNumber(sourceStart, lineEnds = result.getLineSeparatorPositions(), 0, lineEnds.length-1) @@ -33,7 +30,7 @@ public class EclipseAstProblemView { fileNameArray, message, 0, new String[0], isWarning ? ProblemSeverities.Warning : ProblemSeverities.Error, sourceStart, sourceEnd, lineNumber, columnNumber); - ast.compilationResult.record(ecProblem, null); + result.record(ecProblem, null); } private static class LombokProblem extends DefaultProblem { diff --git a/src/core/lombok/eclipse/EclipseImportList.java b/src/core/lombok/eclipse/EclipseImportList.java new file mode 100644 index 00000000..69246b3c --- /dev/null +++ b/src/core/lombok/eclipse/EclipseImportList.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse; + +import static lombok.eclipse.Eclipse.toQualifiedName; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import lombok.core.ImportList; +import lombok.core.LombokInternalAliasing; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ImportReference; + +public class EclipseImportList implements ImportList { + private ImportReference[] imports; + private ImportReference pkg; + + public EclipseImportList(CompilationUnitDeclaration cud) { + this.pkg = cud.currentPackage; + this.imports = cud.imports; + } + + @Override public String getFullyQualifiedNameForSimpleName(String unqualified) { + if (imports != null) { + outer: + for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) != 0) continue; + char[][] tokens = imp.tokens; + char[] token = tokens.length == 0 ? new char[0] : tokens[tokens.length - 1]; + int len = token.length; + if (len != unqualified.length()) continue; + for (int i = 0; i < len; i++) if (token[i] != unqualified.charAt(i)) continue outer; + return LombokInternalAliasing.processAliases(toQualifiedName(tokens)); + } + } + return null; + } + + @Override public boolean hasStarImport(String packageName) { + for (Map.Entry<String, String> e : LombokInternalAliasing.IMPLIED_EXTRA_STAR_IMPORTS.entrySet()) { + if (e.getValue().equals(packageName) && hasStarImport(e.getKey())) return true; + } + if (isEqual(packageName, pkg)) return true; + if ("java.lang".equals(packageName)) return true; + if (imports != null) for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) == 0) continue; + if (imp.isStatic()) continue; + if (isEqual(packageName, imp)) return true; + } + return false; + } + + private static boolean isEqual(String packageName, ImportReference pkgOrStarImport) { + if (pkgOrStarImport == null || pkgOrStarImport.tokens == null || pkgOrStarImport.tokens.length == 0) return packageName.isEmpty(); + int pos = 0; + int len = packageName.length(); + for (int i = 0; i < pkgOrStarImport.tokens.length; i++) { + if (i != 0) { + if (pos >= len) return false; + if (packageName.charAt(pos++) != '.') return false; + } + for (int j = 0; j < pkgOrStarImport.tokens[i].length; j++) { + if (pos >= len) return false; + if (packageName.charAt(pos++) != pkgOrStarImport.tokens[i][j]) return false; + } + } + return true; + } + + @Override public Collection<String> applyNameToStarImports(String startsWith, String name) { + List<String> out = Collections.emptyList(); + + if (pkg != null && pkg.tokens != null && pkg.tokens.length != 0) { + char[] first = pkg.tokens[0]; + int len = first.length; + boolean match = true; + if (startsWith.length() == len) { + for (int i = 0; match && i < len; i++) { + if (startsWith.charAt(i) != first[i]) match = false; + } + if (match) out.add(toQualifiedName(pkg.tokens) + "." + name); + } + } + + if (imports != null) { + outer: + for (ImportReference imp : imports) { + if ((imp.bits & ASTNode.OnDemand) == 0) continue; + if (imp.isStatic()) continue; + if (imp.tokens == null || imp.tokens.length == 0) continue; + char[] firstToken = imp.tokens[0]; + if (firstToken.length != startsWith.length()) continue; + for (int i = 0; i < firstToken.length; i++) if (startsWith.charAt(i) != firstToken[i]) continue outer; + String fqn = toQualifiedName(imp.tokens) + "." + name; + if (out.isEmpty()) out = Collections.singletonList(fqn); + else if (out.size() == 1) { + out = new ArrayList<String>(out); + out.add(fqn); + } else { + out.add(fqn); + } + } + } + return out; + } + + @Override public String applyUnqualifiedNameToPackage(String unqualified) { + if (pkg == null || pkg.tokens == null || pkg.tokens.length == 0) return unqualified; + return toQualifiedName(pkg.tokens) + "." + unqualified; + } +} diff --git a/src/core/lombok/eclipse/HandlerLibrary.java b/src/core/lombok/eclipse/HandlerLibrary.java index 56744793..242e923c 100644 --- a/src/core/lombok/eclipse/HandlerLibrary.java +++ b/src/core/lombok/eclipse/HandlerLibrary.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -218,30 +218,27 @@ public class HandlerLibrary { * @param annotation 'node.get()' - convenience parameter. */ public void handleAnnotation(CompilationUnitDeclaration ast, EclipseNode annotationNode, org.eclipse.jdt.internal.compiler.ast.Annotation annotation, long priority) { - String pkgName = annotationNode.getPackageDeclaration(); - Collection<String> imports = annotationNode.getImportStatements(); - - TypeResolver resolver = new TypeResolver(pkgName, imports); + TypeResolver resolver = new TypeResolver(annotationNode.getImportList()); TypeReference rawType = annotation.type; if (rawType == null) return; - for (String fqn : resolver.findTypeMatches(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName()))) { - AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); - if (container == null) continue; - if (priority != container.getPriority()) continue; - - if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) { - if (needsHandling(annotation)) container.preHandle(annotation, annotationNode); - continue; - } - - try { - if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode); - } catch (AnnotationValueDecodeFail fail) { - fail.owner.setError(fail.getMessage(), fail.idx); - } catch (Throwable t) { - error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); - } + String fqn = resolver.typeRefToFullyQualifiedName(annotationNode, typeLibrary, toQualifiedName(annotation.type.getTypeName())); + if (fqn == null) return; + AnnotationHandlerContainer<?> container = annotationHandlers.get(fqn); + if (container == null) return; + if (priority != container.getPriority()) return; + + if (!annotationNode.isCompleteParse() && container.deferUntilPostDiet()) { + if (needsHandling(annotation)) container.preHandle(annotation, annotationNode); + return; + } + + try { + if (checkAndSetHandled(annotation)) container.handle(annotation, annotationNode); + } catch (AnnotationValueDecodeFail fail) { + fail.owner.setError(fail.getMessage(), fail.idx); + } catch (Throwable t) { + error(ast, String.format("Lombok annotation handler %s failed", container.handler.getClass()), t); } } diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index 47e620f6..11caf5c2 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -144,7 +144,7 @@ public class TransformEclipseAST { try { String message = "Lombok can't parse this source: " + t.toString(); - EclipseAST.addProblemToCompilationResult(ast, false, message, 0, 0); + EclipseAST.addProblemToCompilationResult(ast.getFileName(), ast.compilationResult, false, message, 0, 0); t.printStackTrace(); } catch (Throwable t2) { try { diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 78780522..9bd634f7 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -22,6 +22,7 @@ package lombok.eclipse.handlers; import static lombok.eclipse.Eclipse.*; +import static lombok.core.TransformationsUtil.*; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -61,6 +62,7 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ArrayQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Block; import org.eclipse.jdt.internal.compiler.ast.CastExpression; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; @@ -142,7 +144,7 @@ public class EclipseHandlerUtil { } catch (NoClassDefFoundError e) { //standalone ecj does not jave Platform, ILog, IStatus, and friends. new TerminalLogger().error(message, bundleName, error); } - if (cud != null) EclipseAST.addProblemToCompilationResult(cud, false, message + " - See error log.", 0, 0); + if (cud != null) EclipseAST.addProblemToCompilationResult(cud.getFileName(), cud.compilationResult, false, message + " - See error log.", 0, 0); } /** @@ -290,11 +292,34 @@ public class EclipseHandlerUtil { if (!lastPartA.equals(lastPartB)) return false; String typeName = toQualifiedName(typeRef.getTypeName()); - TypeResolver resolver = new TypeResolver(node.getPackageDeclaration(), node.getImportStatements()); + TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); } + public static void sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(EclipseNode typeNode, EclipseNode errorNode) { + List<String> disallowed = null; + for (EclipseNode child : typeNode.down()) { + for (Class<? extends java.lang.annotation.Annotation> annType : INVALID_ON_BUILDERS) { + if (annotationTypeMatches(annType, child)) { + if (disallowed == null) disallowed = new ArrayList<String>(); + disallowed.add(annType.getSimpleName()); + } + } + } + + int size = disallowed == null ? 0 : disallowed.size(); + if (size == 0) return; + if (size == 1) { + errorNode.addError("@" + disallowed.get(0) + " is not allowed on builder classes."); + return; + } + StringBuilder out = new StringBuilder(); + for (String a : disallowed) out.append("@").append(a).append(", "); + out.setLength(out.length() - 2); + errorNode.addError(out.append(" are not allowed on builder classes.").toString()); + } + public static Annotation copyAnnotation(Annotation annotation, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; @@ -358,6 +383,20 @@ public class EclipseHandlerUtil { return out; } + public static TypeReference namePlusTypeParamsToTypeReference(char[] typeName, TypeParameter[] params, long p) { + if (params != null && params.length > 0) { + TypeReference[] refs = new TypeReference[params.length]; + int idx = 0; + for (TypeParameter param : params) { + TypeReference typeRef = new SingleTypeReference(param.name, p); + refs[idx++] = typeRef; + } + return new ParameterizedSingleTypeReference(typeName, refs, 0, p); + } + + return new SingleTypeReference(typeName, p); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -830,15 +869,20 @@ public class EclipseHandlerUtil { private static final Object MARKER = new Object(); static void registerCreatedLazyGetter(FieldDeclaration field, char[] methodName, TypeReference returnType) { - if (!nameEquals(returnType.getTypeName(), "boolean") || returnType.dimensions() > 0) return; - generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + if (isBoolean(returnType)) { + generatedLazyGettersWithPrimitiveBoolean.put(field, MARKER); + } + } + + public static boolean isBoolean(TypeReference typeReference) { + return nameEquals(typeReference.getTypeName(), "boolean") && typeReference.dimensions() == 0; } private static GetterMethod findGetter(EclipseNode field) { FieldDeclaration fieldDeclaration = (FieldDeclaration) field.get(); boolean forceBool = generatedLazyGettersWithPrimitiveBoolean.containsKey(fieldDeclaration); TypeReference fieldType = fieldDeclaration.type; - boolean isBoolean = forceBool || (nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0); + boolean isBoolean = forceBool || isBoolean(fieldType); EclipseNode typeNode = field.up(); for (String potentialGetterName : toAllGetterNames(field, isBoolean)) { @@ -1207,15 +1251,15 @@ public class EclipseHandlerUtil { * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. * The field carries the @{@link SuppressWarnings}("all") annotation. */ - public static void injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { field.annotations = createSuppressWarningsAll(field, field.annotations); - injectField(type, field); + return injectField(type, field); } /** * Inserts a field into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectField(EclipseNode type, FieldDeclaration field) { + public static EclipseNode injectField(EclipseNode type, FieldDeclaration field) { TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.fields == null) { @@ -1242,7 +1286,7 @@ public class EclipseHandlerUtil { } } - type.add(field, Kind.FIELD); + return type.add(field, Kind.FIELD); } private static boolean isEnumConstant(final FieldDeclaration field) { @@ -1252,7 +1296,7 @@ public class EclipseHandlerUtil { /** * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ - public static void injectMethod(EclipseNode type, AbstractMethodDeclaration method) { + public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) { method.annotations = createSuppressWarningsAll(method, method.annotations); TypeDeclaration parent = (TypeDeclaration) type.get(); @@ -1285,7 +1329,29 @@ public class EclipseHandlerUtil { parent.methods = newArray; } - type.add(method, Kind.METHOD); + return type.add(method, Kind.METHOD); + } + + /** + * Adds an inner type (class, interface, enum) to the given type. Cannot inject top-level types. + * + * @param typeNode parent type to inject new type into + * @param type New type (class, interface, etc) to inject. + */ + public static EclipseNode injectType(final EclipseNode typeNode, final TypeDeclaration type) { + type.annotations = createSuppressWarningsAll(type, type.annotations); + TypeDeclaration parent = (TypeDeclaration) typeNode.get(); + + if (parent.memberTypes == null) { + parent.memberTypes = new TypeDeclaration[] { type }; + } else { + TypeDeclaration[] newArray = new TypeDeclaration[parent.memberTypes.length + 1]; + System.arraycopy(parent.memberTypes, 0, newArray, 0, parent.memberTypes.length); + newArray[parent.memberTypes.length] = type; + parent.memberTypes = newArray; + } + + return typeNode.add(type, Kind.TYPE); } private static final char[] ALL = "all".toCharArray(); @@ -1334,7 +1400,11 @@ public class EclipseHandlerUtil { EqualExpression equalExpression = new EqualExpression(varName, nullLiteral, OperatorIds.EQUAL_EQUAL); equalExpression.sourceStart = pS; equalExpression.statementEnd = equalExpression.sourceEnd = pE; setGeneratedBy(equalExpression, source); - IfStatement ifStatement = new IfStatement(equalExpression, throwStatement, 0, 0); + Block throwBlock = new Block(0); + throwBlock.statements = new Statement[] {throwStatement}; + throwBlock.sourceStart = pS; throwBlock.sourceEnd = pE; + setGeneratedBy(throwBlock, source); + IfStatement ifStatement = new IfStatement(equalExpression, throwBlock, 0, 0); setGeneratedBy(ifStatement, source); return ifStatement; } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java new file mode 100644 index 00000000..70110a9c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import static lombok.eclipse.Eclipse.*; +import static lombok.core.handlers.HandlerUtil.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.ast.TypeParameter; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeBinding; +import org.mangosdk.spi.ProviderFor; + +import lombok.AccessLevel; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.TransformationsUtil; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; +import lombok.experimental.Builder; +import lombok.experimental.NonFinal; + +@ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. +public class HandleBuilder extends EclipseAnnotationHandler<Builder> { + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { + long p = (long) ast.sourceStart << 32 | ast.sourceEnd; + + Builder builderInstance = annotation.getInstance(); + String builderMethodName = builderInstance.builderMethodName(); + String buildMethodName = builderInstance.buildMethodName(); + String builderClassName = builderInstance.builderClassName(); + + if (builderMethodName == null) builderMethodName = "builder"; + if (buildMethodName == null) builderMethodName = "build"; + if (builderClassName == null) builderClassName = ""; + + if (!checkName("builderMethodName", builderMethodName, annotationNode)) return; + if (!checkName("buildMethodName", buildMethodName, annotationNode)) return; + if (!builderClassName.isEmpty()) { + if (!checkName("builderClassName", builderClassName, annotationNode)) return; + } + + EclipseNode parent = annotationNode.up(); + + List<TypeReference> typesOfParameters = new ArrayList<TypeReference>(); + List<char[]> namesOfParameters = new ArrayList<char[]>(); + TypeReference returnType; + TypeParameter[] typeParams; + TypeReference[] thrownExceptions; + char[] nameOfStaticBuilderMethod; + EclipseNode tdParent; + + AbstractMethodDeclaration fillParametersFrom = null; + + if (parent.get() instanceof TypeDeclaration) { + tdParent = parent; + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + + List<EclipseNode> fields = new ArrayList<EclipseNode>(); + @SuppressWarnings("deprecation") + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation(lombok.experimental.Value.class, parent)); + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent)) { + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + // final fields with an initializer cannot be written to, so they can't be 'builderized'. Unfortunately presence of @Value makes + // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. + // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. + if (fd.initialization != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; + namesOfParameters.add(fd.name); + typesOfParameters.add(fd.type); + fields.add(fieldNode); + } + + new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, true, Collections.<Annotation>emptyList(), ast); + + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = null; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(td.name) + "Builder"; + } else if (parent.get() instanceof ConstructorDeclaration) { + ConstructorDeclaration cd = (ConstructorDeclaration) parent.get(); + if (cd.typeParameters != null && cd.typeParameters.length > 0) { + annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); + return; + } + + tdParent = parent.up(); + TypeDeclaration td = (TypeDeclaration) tdParent.get(); + fillParametersFrom = cd; + returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); + typeParams = td.typeParameters; + thrownExceptions = cd.thrownExceptions; + nameOfStaticBuilderMethod = null; + if (builderClassName.isEmpty()) builderClassName = new String(cd.selector) + "Builder"; + } else if (parent.get() instanceof MethodDeclaration) { + MethodDeclaration md = (MethodDeclaration) parent.get(); + tdParent = parent.up(); + if (!md.isStatic()) { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + fillParametersFrom = md; + returnType = copyType(md.returnType, ast); + typeParams = md.typeParameters; + thrownExceptions = md.thrownExceptions; + nameOfStaticBuilderMethod = md.selector; + if (builderClassName.isEmpty()) { + char[] token; + if (md.returnType instanceof QualifiedTypeReference) { + char[][] tokens = ((QualifiedTypeReference) md.returnType).tokens; + token = tokens[tokens.length - 1]; + } else if (md.returnType instanceof SingleTypeReference) { + token = ((SingleTypeReference) md.returnType).token; + if (!(md.returnType instanceof ParameterizedSingleTypeReference) && typeParams != null) { + for (TypeParameter tp : typeParams) { + if (Arrays.equals(tp.name, token)) { + annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type."); + return; + } + } + } + } else { + annotationNode.addError("Unexpected kind of return type on annotated method. Specify 'builderClassName' to solve this problem."); + return; + } + + if (Character.isLowerCase(token[0])) { + char[] newToken = new char[token.length]; + System.arraycopy(token, 1, newToken, 1, token.length - 1); + newToken[0] = Character.toTitleCase(token[0]); + token = newToken; + } + + builderClassName = new String(token) + "Builder"; + } + } else { + annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + return; + } + + if (fillParametersFrom != null) { + if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { + namesOfParameters.add(a.name); + typesOfParameters.add(a.type); + } + } + + EclipseNode builderType = findInnerClass(tdParent, builderClassName); + if (builderType == null) { + builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + } else { + sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + } + List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); + List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>(); + for (EclipseNode fieldNode : fieldNodes) { + MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, ast, builderInstance.fluent(), builderInstance.chain()); + if (newMethod != null) newMethods.add(newMethod); + } + + if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { + ConstructorDeclaration cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), true, ast, Collections.<Annotation>emptyList()); + if (cd != null) injectMethod(builderType, cd); + } + + for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); + if (md != null) injectMethod(builderType, md); + } + + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { + MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + } + + private MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.typeParameters = copyTypeParams(typeParams, source); + AllocationExpression invoke = new AllocationExpression(); + invoke.type = namePlusTypeParamsToTypeReference(builderClassName.toCharArray(), typeParams, p); + out.statements = new Statement[] {new ReturnStatement(invoke, pS, pE)}; + + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); + return out; + } + + private MethodDeclaration generateBuildMethod(String name, char[] staticName, TypeReference returnType, List<char[]> fieldNames, EclipseNode type, ASTNode source, TypeReference[] thrownExceptions) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; + + MethodDeclaration out = new MethodDeclaration( + ((CompilationUnitDeclaration) type.top().get()).compilationResult); + + out.modifiers = ClassFileConstants.AccPublic; + TypeDeclaration typeDecl = (TypeDeclaration) type.get(); + out.selector = name.toCharArray(); + out.thrownExceptions = copyTypes(thrownExceptions, source); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = returnType; + + List<Expression> assigns = new ArrayList<Expression>(); + for (char[] fieldName : fieldNames) { + SingleNameReference nameRef = new SingleNameReference(fieldName, p); + assigns.add(nameRef); + } + + Statement statement; + + if (staticName == null) { + AllocationExpression allocationStatement = new AllocationExpression(); + allocationStatement.type = copyType(out.returnType, source); + allocationStatement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + statement = new ReturnStatement(allocationStatement, (int)(p >> 32), (int)p); + } else { + MessageSend invoke = new MessageSend(); + invoke.selector = staticName; + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), p); + TypeParameter[] tps = ((TypeDeclaration) type.get()).typeParameters; + if (tps != null) { + TypeReference[] trs = new TypeReference[tps.length]; + for (int i = 0; i < trs.length; i++) { + trs[i] = new SingleTypeReference(tps[i].name, p); + } + invoke.typeArguments = trs; + } + invoke.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); + if (returnType instanceof SingleTypeReference && Arrays.equals(TypeBinding.VOID.simpleName, ((SingleTypeReference) returnType).token)) { + statement = invoke; + } else { + statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + } + } + + out.statements = new Statement[] { statement }; + + out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); + return out; + } + + private List<EclipseNode> addFieldsToBuilder(EclipseNode builderType, List<char[]> namesOfParameters, List<TypeReference> typesOfParameters, ASTNode source) { + int len = namesOfParameters.size(); + TypeDeclaration td = (TypeDeclaration) builderType.get(); + FieldDeclaration[] existing = td.fields; + if (existing == null) existing = new FieldDeclaration[0]; + + List<EclipseNode> out = new ArrayList<EclipseNode>(); + + top: + for (int i = len - 1; i >= 0; i--) { + char[] name = namesOfParameters.get(i); + for (FieldDeclaration exists : existing) { + if (Arrays.equals(exists.name, name)) { + out.add(builderType.getNodeFor(exists)); + continue top; + } + } + TypeReference fieldReference = copyType(typesOfParameters.get(i), source); + FieldDeclaration newField = new FieldDeclaration(name, 0, 0); + newField.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + newField.modifiers = ClassFileConstants.AccPrivate; + newField.type = fieldReference; + out.add(injectField(builderType, newField)); + } + + Collections.reverse(out); + + return out; + } + + private static final AbstractMethodDeclaration[] EMPTY = {}; + + private MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, ASTNode source, boolean fluent, boolean chain) { + TypeDeclaration td = (TypeDeclaration) builderType.get(); + AbstractMethodDeclaration[] existing = td.methods; + if (existing == null) existing = EMPTY; + int len = existing.length; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + char[] name = fd.name; + + for (int i = 0; i < len; i++) { + if (!(existing[i] instanceof MethodDeclaration)) continue; + char[] existingName = existing[i].selector; + if (Arrays.equals(name, existingName)) return null; + } + + boolean isBoolean = isBoolean(fd.type); + String setterName = fluent ? fieldNode.getName() : TransformationsUtil.toSetterName(null, fieldNode.getName(), isBoolean); + + return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, + source, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + } + + private EclipseNode findInnerClass(EclipseNode parent, String name) { + char[] c = name.toCharArray(); + for (EclipseNode child : parent.down()) { + if (child.getKind() != Kind.TYPE) continue; + TypeDeclaration td = (TypeDeclaration) child.get(); + if (Arrays.equals(td.name, c)) return child; + } + return null; + } + + private EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + TypeDeclaration parent = (TypeDeclaration) tdParent.get(); + TypeDeclaration builder = new TypeDeclaration(parent.compilationResult); + builder.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + builder.modifiers |= ClassFileConstants.AccPublic | ClassFileConstants.AccStatic; + builder.typeParameters = copyTypeParams(typeParams, source); + builder.name = builderClassName.toCharArray(); + builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return injectType(tdParent, builder); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index 8ccad77f..1ae680d9 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -39,6 +39,7 @@ import lombok.core.AnnotationValues; import lombok.core.TransformationsUtil; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.experimental.Builder; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; @@ -53,18 +54,14 @@ import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; -import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; -import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.StringLiteral; import org.eclipse.jdt.internal.compiler.ast.ThisReference; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; -import org.eclipse.jdt.internal.compiler.ast.TypeParameter; -import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.mangosdk.spi.ProviderFor; @@ -82,7 +79,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, false, false, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, false, onConstructor, ast); } } @@ -100,7 +97,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -117,7 +114,7 @@ public class HandleConstructor { return fields; } - private static List<EclipseNode> findAllFields(EclipseNode typeNode) { + static List<EclipseNode> findAllFields(EclipseNode typeNode) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; @@ -146,7 +143,7 @@ public class HandleConstructor { List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, false, suppressConstructorProperties, onConstructor, ast); + new HandleConstructor().generateConstructor(typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, suppressConstructorProperties, onConstructor, ast); } } @@ -164,25 +161,34 @@ public class HandleConstructor { return true; } - public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { + public void generateRequiredArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, boolean skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { + public void generateAllArgsConstructor(EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, ASTNode source) { generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, false, onConstructor, source); } - public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, boolean skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) { + public enum SkipIfConstructorExists { + YES, NO, I_AM_BUILDER; + } + + public void generateConstructor(EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, boolean suppressConstructorProperties, List<Annotation> onConstructor, ASTNode source) { boolean staticConstrRequired = staticName != null && !staticName.equals(""); - if (skipIfConstructorExists && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; - if (skipIfConstructorExists) { + if (skipIfConstructorExists != SkipIfConstructorExists.NO && constructorExists(typeNode) != MemberExistsResult.NOT_EXISTS) return; + if (skipIfConstructorExists != SkipIfConstructorExists.NO) { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { - if (annotationTypeMatches(NoArgsConstructor.class, child) || + boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)) { - + annotationTypeMatches(RequiredArgsConstructor.class, child)); + + if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { + skipGeneration = annotationTypeMatches(Builder.class, child); + } + + if (skipGeneration) { if (staticConstrRequired) { // @Data has asked us to generate a constructor, but we're going to skip this instruction, as an explicit 'make a constructor' annotation // will take care of it. However, @Data also wants a specific static name; this will be ignored; the appropriate way to do this is to use @@ -235,7 +241,7 @@ public class HandleConstructor { return new Annotation[] { ann }; } - private ConstructorDeclaration createConstructor( + static ConstructorDeclaration createConstructor( AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean suppressConstructorProperties, ASTNode source, List<Annotation> onConstructor) { @@ -307,7 +313,7 @@ public class HandleConstructor { return constructor; } - private boolean isLocalType(EclipseNode type) { + private static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; if (kind == Kind.TYPE) return isLocalType(type.up()); @@ -321,18 +327,9 @@ public class HandleConstructor { MethodDeclaration constructor = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); - constructor.modifiers = toEclipseModifier(level) | Modifier.STATIC; + constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; TypeDeclaration typeDecl = (TypeDeclaration) type.get(); - if (typeDecl.typeParameters != null && typeDecl.typeParameters.length > 0) { - TypeReference[] refs = new TypeReference[typeDecl.typeParameters.length]; - int idx = 0; - for (TypeParameter param : typeDecl.typeParameters) { - TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); - setGeneratedBy(typeRef, source); - refs[idx++] = typeRef; - } - constructor.returnType = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); - } else constructor.returnType = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); + constructor.returnType = EclipseHandlerUtil.namePlusTypeParamsToTypeReference(typeDecl.name, typeDecl.typeParameters, p); constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 3a43bd3f..aa309489 100644 --- a/src/core/lombok/eclipse/handlers/HandleData.java +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -28,6 +28,7 @@ import lombok.Data; import lombok.core.AnnotationValues; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -64,6 +65,6 @@ public class HandleData extends EclipseAnnotationHandler<Data> { new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast); + new HandleConstructor().generateRequiredArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index 0c82b74c..6990e609 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 The Project Lombok Authors. + * Copyright (C) 2009-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -204,18 +204,27 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } boolean isFinal = (typeDecl.modifiers & ClassFileConstants.AccFinal) != 0; - boolean needsCanEqual = !isDirectDescendantOfObject || !isFinal; - java.util.List<MemberExistsResult> existsResults = new ArrayList<MemberExistsResult>(); - existsResults.add(methodExists("equals", typeNode, 1)); - existsResults.add(methodExists("hashCode", typeNode, 0)); - existsResults.add(methodExists("canEqual", typeNode, 1)); - switch (Collections.max(existsResults)) { + boolean needsCanEqual = !isFinal || !isDirectDescendantOfObject; + MemberExistsResult equalsExists = methodExists("equals", typeNode, 1); + MemberExistsResult hashCodeExists = methodExists("hashCode", typeNode, 0); + MemberExistsResult canEqualExists = methodExists("canEqual", typeNode, 1); + switch (Collections.max(Arrays.asList(equalsExists, hashCodeExists, canEqualExists))) { case EXISTS_BY_LOMBOK: return; case EXISTS_BY_USER: if (whineIfExists) { String msg = String.format("Not generating equals%s: A method with one of those names already exists. (Either all or none of these methods will be generated).", needsCanEqual ? ", hashCode and canEquals" : " and hashCode"); errorNode.addWarning(msg); + } else if (equalsExists == MemberExistsResult.NOT_EXISTS || hashCodeExists == MemberExistsResult.NOT_EXISTS) { + // This means equals OR hashCode exists and not both (or neither, but canEqual is there). + // Even though we should suppress the message about not generating these, this is such a weird and surprising situation we should ALWAYS generate a warning. + // The user code couldn't possibly (barring really weird subclassing shenanigans) be in a shippable state anyway; the implementations of these 3 methods are + // all inter-related and should be written by the same entity. + String msg = String.format("Not generating %s: One of equals, hashCode, and canEqual exists. " + + "You should either write all of these are none of these (in the latter case, lombok generates them).", + equalsExists == MemberExistsResult.NOT_EXISTS && hashCodeExists == MemberExistsResult.NOT_EXISTS ? "equals and hashCode" : + equalsExists == MemberExistsResult.NOT_EXISTS ? "equals" : "hashCode"); + errorNode.addWarning(msg); } return; case NOT_EXISTS: diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 0d21fc08..d6d839cc 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -43,7 +43,7 @@ import org.mangosdk.spi.ProviderFor; * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) -@HandlerPriority(-512) //-2^9; to ensure @Setter and such pick up on messing with the fields' 'final' state, run earlier. +@HandlerPriority(-2048) //-2^11; to ensure @Value picks up on messing with the fields' 'final' state, run earlier. public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> { public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { @@ -112,6 +112,14 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> return; } + if (level == AccessLevel.PACKAGE) { + annotationNode.addError("Setting 'level' to PACKAGE does nothing. To force fields as package private, use the @PackagePrivate annotation on the field."); + } + + if (!makeFinal && annotation.isExplicit("makeFinal")) { + annotationNode.addError("Setting 'makeFinal' to false does nothing. To force fields to be non-final, use the @NonFinal annotation on the field."); + } + if (node == null) return; generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 7f788c5d..787f6f6c 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -49,6 +49,8 @@ import org.eclipse.jdt.internal.compiler.ast.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.Assignment; import org.eclipse.jdt.internal.compiler.ast.BinaryExpression; import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.CastExpression; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.EqualExpression; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; @@ -67,6 +69,7 @@ import org.eclipse.jdt.internal.compiler.ast.SynchronizedStatement; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.mangosdk.spi.ProviderFor; /** @@ -184,7 +187,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String getterName = toGetterName(fieldNode, isBoolean); if (getterName == null) { @@ -286,7 +289,6 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { } private static final char[][] AR = fromQualifiedName("java.util.concurrent.atomic.AtomicReference"); - private static final TypeReference[][] AR_PARAMS = new TypeReference[5][]; private static final java.util.Map<String, char[][]> TYPE_MAP; static { @@ -305,41 +307,54 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { private static char[] valueName = "value".toCharArray(); private static char[] actualValueName = "actualValue".toCharArray(); + private static final int PARENTHESIZED = (1 << ASTNode.ParenthesizedSHIFT) & ASTNode.ParenthesizedMASK; + private Statement[] createLazyGetterBody(ASTNode source, EclipseNode fieldNode) { /* - java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); + java.lang.Object value = this.fieldName.get(); if (value == null) { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + final RawValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } } - return value.get(); + [IF PRIMITIVE] + return (BoxedValueType) value; + [ELSE] + return (BoxedValueType) (value == this.fieldName ? null : value); + [END IF] */ FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - TypeReference componentType = copyType(field.type, source); + TypeReference rawComponentType = copyType(field.type, source); + TypeReference boxedComponentType = null; + boolean isPrimitive = false; if (field.type instanceof SingleTypeReference && !(field.type instanceof ArrayTypeReference)) { char[][] newType = TYPE_MAP.get(new String(((SingleTypeReference)field.type).token)); if (newType != null) { - componentType = new QualifiedTypeReference(newType, poss(source, 3)); + boxedComponentType = new QualifiedTypeReference(newType, poss(source, 3)); + isPrimitive = true; } } + if (boxedComponentType == null) boxedComponentType = copyType(field.type, source); + boxedComponentType.sourceStart = pS; boxedComponentType.sourceEnd = boxedComponentType.statementEnd = pE; Statement[] statements = new Statement[3]; - /* java.util.concurrent.atomic.AtomicReference<ValueType> value = this.fieldName.get(); */ { + /* java.lang.Object value = this.fieldName.get(); */ { LocalDeclaration valueDecl = new LocalDeclaration(valueName, pS, pE); - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - valueDecl.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + valueDecl.type = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); valueDecl.type.sourceStart = pS; valueDecl.type.sourceEnd = valueDecl.type.statementEnd = pE; MessageSend getter = new MessageSend(); @@ -356,8 +371,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { synchronized (this.fieldName) { value = this.fieldName.get(); if (value == null) { - final ValueType actualValue = new ValueType(); - value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); + final ValueType actualValue = INITIALIZER_EXPRESSION; + [IF PRIMITIVE] + value = actualValue; + [ELSE] + value = actualValue == null ? this.fieldName : actualValue; + [END IF] this.fieldName.set(value); } } @@ -383,28 +402,37 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { EqualExpression innerCond = new EqualExpression( new SingleNameReference(valueName, p), new NullLiteral(pS, pE), BinaryExpression.EQUAL_EQUAL); + innerCond.sourceStart = pS; innerCond.sourceEnd = innerCond.statementEnd = pE; Block innerThen = new Block(0); innerThen.statements = new Statement[3]; - /* final ValueType actualValue = new ValueType(); */ { + /* final ValueType actualValue = INITIALIZER_EXPRESSION */ { LocalDeclaration actualValueDecl = new LocalDeclaration(actualValueName, pS, pE); - actualValueDecl.type = copyType(field.type, source); + actualValueDecl.type = rawComponentType; actualValueDecl.type.sourceStart = pS; actualValueDecl.type.sourceEnd = actualValueDecl.type.statementEnd = pE; actualValueDecl.initialization = field.initialization; actualValueDecl.modifiers = ClassFileConstants.AccFinal; innerThen.statements[0] = actualValueDecl; } - /* value = new java.util.concurrent.atomic.AtomicReference<ValueType>(actualValue); */ { - AllocationExpression create = new AllocationExpression(); - create.sourceStart = pS; create.sourceEnd = create.statementEnd = pE; - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(componentType, source)}; - create.type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); - create.type.sourceStart = pS; create.type.sourceEnd = create.type.statementEnd = pE; - create.arguments = new Expression[] {new SingleNameReference(actualValueName, p)}; - Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), create, pE); - innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; - - innerThen.statements[1] = innerAssign; + /* [IF PRIMITIVE] value = actualValue; */ { + if (isPrimitive) { + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), new SingleNameReference(actualValueName, p), pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } + } + /* [ELSE] value = actualValue == null ? this.fieldName : actualValue; */ { + if (!isPrimitive) { + EqualExpression avIsNull = new EqualExpression( + new SingleNameReference(actualValueName, p), new NullLiteral(pS, pE), + BinaryExpression.EQUAL_EQUAL); + avIsNull.sourceStart = pS; avIsNull.sourceEnd = avIsNull.statementEnd = pE; + Expression fieldRef = createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source); + ConditionalExpression ternary = new ConditionalExpression(avIsNull, fieldRef, new SingleNameReference(actualValueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + Assignment innerAssign = new Assignment(new SingleNameReference(valueName, p), ternary, pE); + innerAssign.sourceStart = pS; innerAssign.statementEnd = innerAssign.sourceEnd = pE; + innerThen.statements[1] = innerAssign; + } } /* this.fieldName.set(value); */ { @@ -428,26 +456,34 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { statements[1] = ifStatement; } - /* return value.get(); */ { - MessageSend getter = new MessageSend(); - getter.sourceStart = pS; getter.sourceEnd = getter.statementEnd = pE; - getter.selector = new char[] {'g', 'e', 't'}; - getter.receiver = new SingleNameReference(valueName, p); - - statements[2] = new ReturnStatement(getter, pS, pE); + /* [IF PRIMITIVE] return (BoxedValueType)value; */ { + if (isPrimitive) { + CastExpression cast = makeCastExpression(new SingleNameReference(valueName, p), boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } + } + /* [ELSE] return (BoxedValueType)(value == this.fieldName ? null : value); */ { + if (!isPrimitive) { + EqualExpression vIsThisFieldName = new EqualExpression( + new SingleNameReference(valueName, p), createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), + BinaryExpression.EQUAL_EQUAL); + vIsThisFieldName.sourceStart = pS; vIsThisFieldName.sourceEnd = vIsThisFieldName.statementEnd = pE; + ConditionalExpression ternary = new ConditionalExpression(vIsThisFieldName, new NullLiteral(pS, pE), new SingleNameReference(valueName, p)); + ternary.sourceStart = pS; ternary.sourceEnd = ternary.statementEnd = pE; + ternary.bits |= PARENTHESIZED; + CastExpression cast = makeCastExpression(ternary, boxedComponentType, source); + statements[2] = new ReturnStatement(cast, pS, pE); + } } - // update the field type and init last - /* private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType> fieldName = new java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicReference<ValueType>>(); */ { - - LocalDeclaration first = (LocalDeclaration) statements[0]; - TypeReference innerType = copyType(first.type, source); - - TypeReference[][] typeParams = AR_PARAMS.clone(); - typeParams[4] = new TypeReference[] {copyType(innerType, source)}; + /* private final java.util.concurrent.atomic.AtomicReference<java.lang.Object> fieldName = new java.util.concurrent.atomic.AtomicReference<java.lang.Object>(); */ { + TypeReference innerType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, poss(source, 3)); + TypeReference[][] typeParams = new TypeReference[5][]; + typeParams[4] = new TypeReference[] {innerType}; TypeReference type = new ParameterizedQualifiedTypeReference(AR, typeParams, 0, poss(source, 5)); + // Some magic here type.sourceStart = -1; type.sourceEnd = -2; diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index bffe2d62..2e7b4475 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2012 The Project Lombok Authors. + * Copyright (C) 2010-2013 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -178,6 +178,16 @@ public class HandleLog { } /** + * Handles the {@link lombok.extern.log4j.Log4j2} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleLog4j2Log extends EclipseAnnotationHandler<lombok.extern.log4j.Log4j2> { + @Override public void handle(AnnotationValues<lombok.extern.log4j.Log4j2> annotation, Annotation source, EclipseNode annotationNode) { + processAnnotation(LoggingFramework.LOG4J2, annotation, source, annotationNode); + } + } + + /** * Handles the {@link lombok.extern.slf4j.Slf4j} annotation for Eclipse. */ @ProviderFor(EclipseAnnotationHandler.class) @@ -224,6 +234,9 @@ public class HandleLog { // private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(TargetType.class); LOG4J("org.apache.log4j.Logger", "org.apache.log4j.Logger", "getLogger", "@Log4j"), + // private static final org.apache.logging.log4j.Logger log = org.apache.logging.log4j.LogManager.getLogger(TargetType.class); + LOG4J2("org.apache.logging.log4j.Logger", "org.apache.logging.log4j.LogManager", "getLogger", "@Log4j2"), + // private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TargetType.class); SLF4J("org.slf4j.Logger", "org.slf4j.LoggerFactory", "getLogger", "@Slf4j"), diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index 9b46b704..3bfcc51c 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -159,7 +159,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String setterName = toSetterName(fieldNode, isBoolean); boolean shouldReturnThis = shouldReturnThis(fieldNode); @@ -192,7 +192,7 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { injectMethod(fieldNode.up(), method); } - private MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, ASTNode source, List<Annotation> onMethod, List<Annotation> onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; diff --git a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java index b7c8a5d8..aa78ca3b 100644 --- a/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java +++ b/src/core/lombok/eclipse/handlers/HandleSneakyThrows.java @@ -40,6 +40,8 @@ import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.Argument; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; import org.eclipse.jdt.internal.compiler.ast.MemberValuePair; import org.eclipse.jdt.internal.compiler.ast.MessageSend; @@ -147,7 +149,21 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> { return; } - if (method.statements == null) return; + if (method.statements == null || method.statements.length == 0) { + boolean hasConstructorCall = false; + if (method instanceof ConstructorDeclaration) { + ExplicitConstructorCall constructorCall = ((ConstructorDeclaration) method).constructorCall; + hasConstructorCall = constructorCall != null && !constructorCall.isImplicitSuper() && !constructorCall.isImplicitThis(); + } + + if (hasConstructorCall) { + annotation.addWarning("Calls to sibling / super constructors are always excluded from @SneakyThrows; @SneakyThrows has been ignored because there is no other code in this constructor."); + } else { + annotation.addWarning("This method or constructor is empty; @SneakyThrows has been ignored."); + } + + return; + } Statement[] contents = method.statements; @@ -160,9 +176,9 @@ public class HandleSneakyThrows extends EclipseAnnotationHandler<SneakyThrows> { } private Statement buildTryCatchBlock(Statement[] contents, DeclaredException exception, ASTNode source, AbstractMethodDeclaration method) { - int methodStart = method.bodyStart; - int methodEnd = method.bodyEnd; - long methodPosEnd = methodEnd << 32 | (methodEnd & 0xFFFFFFFFL); + int methodStart = method.bodyStart; + int methodEnd = method.bodyEnd; + long methodPosEnd = ((long) methodEnd) << 32 | (methodEnd & 0xFFFFFFFFL); TryStatement tryStatement = new TryStatement(); setGeneratedBy(tryStatement, source); diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index 75d4acef..1193af31 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -170,7 +170,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } } - private MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, + static MethodDeclaration createToString(EclipseNode type, Collection<EclipseNode> fields, boolean includeFieldNames, boolean callSuper, ASTNode source, FieldAccess fieldAccess) { String typeName = getTypeName(type); char[] suffix = ")".toCharArray(); @@ -209,21 +209,25 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { } for (EclipseNode field : fields) { - TypeReference fType = getFieldType(field, fieldAccess); + TypeReference fieldType = getFieldType(field, fieldAccess); Expression fieldAccessor = createFieldAccessor(field, fieldAccess, source); + // The distinction between primitive and object will be useful if we ever add a 'hideNulls' option. + boolean fieldBaseTypeIsPrimitive = BUILT_IN_TYPES.contains(new String(fieldType.getLastToken())); + boolean fieldIsPrimitive = fieldType.dimensions() == 0 && fieldBaseTypeIsPrimitive; + boolean fieldIsPrimitiveArray = fieldType.dimensions() == 1 && fieldBaseTypeIsPrimitive; + boolean fieldIsObjectArray = fieldType.dimensions() > 0 && !fieldIsPrimitiveArray; + @SuppressWarnings("unused") + boolean fieldIsObject = !fieldIsPrimitive && !fieldIsPrimitiveArray && !fieldIsObjectArray; + Expression ex; - if (fType.dimensions() > 0) { + if (fieldIsPrimitiveArray || fieldIsObjectArray) { MessageSend arrayToString = new MessageSend(); arrayToString.sourceStart = pS; arrayToString.sourceEnd = pE; arrayToString.receiver = generateQualifiedNameRef(source, TypeConstants.JAVA, TypeConstants.UTIL, "Arrays".toCharArray()); arrayToString.arguments = new Expression[] { fieldAccessor }; setGeneratedBy(arrayToString.arguments[0], source); - if (fType.dimensions() > 1 || !BUILT_IN_TYPES.contains(new String(fType.getLastToken()))) { - arrayToString.selector = "deepToString".toCharArray(); - } else { - arrayToString.selector = "toString".toCharArray(); - } + arrayToString.selector = (fieldIsObjectArray ? "deepToString" : "toString").toCharArray(); ex = arrayToString; } else { ex = fieldAccessor; @@ -278,7 +282,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { return method; } - private String getTypeName(EclipseNode type) { + private static String getTypeName(EclipseNode type) { String typeName = getSingleTypeName(type); EclipseNode upType = type.up(); while (upType.getKind() == Kind.TYPE) { @@ -288,7 +292,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { return typeName; } - private String getSingleTypeName(EclipseNode type) { + private static String getSingleTypeName(EclipseNode type) { TypeDeclaration typeDeclaration = (TypeDeclaration)type.get(); char[] rawTypeName = typeDeclaration.name; return rawTypeName == null ? "" : new String(rawTypeName); @@ -297,7 +301,7 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { private static final Set<String> BUILT_IN_TYPES = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList( "byte", "short", "int", "long", "char", "boolean", "double", "float"))); - private NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { + private static NameReference generateQualifiedNameRef(ASTNode source, char[]... varNames) { int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; NameReference ref; diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index b69b1669..0607137b 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -30,8 +30,9 @@ import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.HandleConstructor.SkipIfConstructorExists; import lombok.experimental.NonFinal; -import lombok.experimental.Value; +import lombok.Value; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; @@ -78,6 +79,6 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), true, Collections.<Annotation>emptyList(), ast); + new HandleConstructor().generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), ast); } } diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 9d74cbd1..27fbc635 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -160,7 +160,7 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); - boolean isBoolean = nameEquals(fieldType.getTypeName(), "boolean") && fieldType.dimensions() == 0; + boolean isBoolean = isBoolean(fieldType); String witherName = toWitherName(fieldNode, isBoolean); if (witherName == null) { diff --git a/src/core/lombok/eclipse/handlers/NonNullHandler.java b/src/core/lombok/eclipse/handlers/NonNullHandler.java new file mode 100644 index 00000000..5c58069c --- /dev/null +++ b/src/core/lombok/eclipse/handlers/NonNullHandler.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2013 The Project Lombok Authors. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package lombok.eclipse.handlers; + +import java.util.Arrays; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.AbstractVariableDeclaration; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.mangosdk.spi.ProviderFor; + +import lombok.NonNull; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.DeferUntilPostDiet; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +@DeferUntilPostDiet +@ProviderFor(EclipseAnnotationHandler.class) +public class NonNullHandler extends EclipseAnnotationHandler<NonNull> { + @Override public void handle(AnnotationValues<NonNull> annotation, Annotation ast, EclipseNode annotationNode) { + if (annotationNode.up().getKind() == Kind.FIELD) { + // This is meaningless unless the field is used to generate a method (@Setter, @RequiredArgsConstructor, etc), + // but in that case those handlers will take care of it. However, we DO check if the annotation is applied to + // a primitive, because those handlers trigger on any annotation named @NonNull and we only want the warning + // behaviour on _OUR_ 'lombok.NonNull'. + + try { + if (isPrimitive(((AbstractVariableDeclaration) annotationNode.up().get()).type)) { + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + } + } catch (Exception ignore) {} + + return; + } + + if (annotationNode.up().getKind() != Kind.ARGUMENT) return; + + Argument arg; + AbstractMethodDeclaration declaration; + + try { + arg = (Argument) annotationNode.up().get(); + declaration = (AbstractMethodDeclaration) annotationNode.up().up().get(); + } catch (Exception e) { + return; + } + + if (isGenerated(declaration)) return; + + // Possibly, if 'declaration instanceof ConstructorDeclaration', fetch declaration.constructorCall, search it for any references to our parameter, + // and if they exist, create a new method in the class: 'private static <T> T lombok$nullCheck(T expr, String msg) {if (expr == null) throw NPE; return expr;}' and + // wrap all references to it in the super/this to a call to this method. + + Statement nullCheck = generateNullCheck(arg, ast); + + if (nullCheck == null) { + // @NonNull applied to a primitive. Kinda pointless. Let's generate a warning. + annotationNode.addWarning("@NonNull is meaningless on a primitive."); + return; + } + + if (declaration.statements == null) { + declaration.statements = new Statement[] {nullCheck}; + } else { + char[] expectedName = arg.name; + for (Statement stat : declaration.statements) { + char[] varNameOfNullCheck = returnVarNameIfNullCheck(stat); + if (varNameOfNullCheck == null) break; + if (Arrays.equals(expectedName, varNameOfNullCheck)) return; + } + + Statement[] newStatements = new Statement[declaration.statements.length + 1]; + int skipOver = 0; + for (Statement stat : declaration.statements) { + if (isGenerated(stat)) skipOver++; + else break; + } + System.arraycopy(declaration.statements, 0, newStatements, 0, skipOver); + System.arraycopy(declaration.statements, skipOver, newStatements, skipOver + 1, declaration.statements.length - skipOver); + newStatements[skipOver] = nullCheck; + declaration.statements = newStatements; + } + annotationNode.up().up().rebuild(); + } + + private char[] returnVarNameIfNullCheck(Statement stat) { + if (!(stat instanceof IfStatement)) return null; + + /* Check that the if's statement is a throw statement, possibly in a block. */ { + Statement then = ((IfStatement) stat).thenStatement; + if (then instanceof Block) { + Statement[] blockStatements = ((Block) then).statements; + if (blockStatements == null || blockStatements.length == 0) return null; + then = blockStatements[0]; + } + + if (!(then instanceof ThrowStatement)) return null; + } + + /* Check that the if's conditional is like 'x == null'. Return from this method (don't generate + a nullcheck) if 'x' is equal to our own variable's name: There's already a nullcheck here. */ { + Expression cond = ((IfStatement) stat).condition; + if (!(cond instanceof EqualExpression)) return null; + EqualExpression bin = (EqualExpression) cond; + int operatorId = ((bin.bits & ASTNode.OperatorMASK) >> ASTNode.OperatorSHIFT); + if (operatorId != OperatorIds.EQUAL_EQUAL) return null; + if (!(bin.left instanceof SingleNameReference)) return null; + if (!(bin.right instanceof NullLiteral)) return null; + return ((SingleNameReference) bin.left).token; + } + } +} |
