diff options
Diffstat (limited to 'src/core/lombok/eclipse')
33 files changed, 3777 insertions, 866 deletions
diff --git a/src/core/lombok/eclipse/EclipseAST.java b/src/core/lombok/eclipse/EclipseAST.java index 6df5c4d7..dc2c9843 100644 --- a/src/core/lombok/eclipse/EclipseAST.java +++ b/src/core/lombok/eclipse/EclipseAST.java @@ -62,7 +62,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), new EclipseImportList(ast)); + super(toFileName(ast), packageDeclaration(ast), new EclipseImportList(ast), statementTypes()); this.compilationUnitDeclaration = ast; setTop(buildCompilationUnit(ast)); this.completeParse = isComplete(ast); @@ -70,7 +70,7 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { } private static volatile boolean skipEclipseWorkspaceBasedFileResolver = false; - private static final URI NOT_CALCULATED_MARKER = URI.create("http://projectlombok.org/not/calculated"); + private static final URI NOT_CALCULATED_MARKER = URI.create("https://projectlombok.org/not/calculated"); private URI memoizedAbsoluteFileLocation = NOT_CALCULATED_MARKER; public URI getAbsoluteFileLocation() { @@ -477,9 +477,9 @@ public class EclipseAST extends AST<EclipseAST, EclipseNode, ASTNode> { return putInMap(new EclipseNode(this, statement, childNodes, Kind.STATEMENT)); } - /** For Eclipse, only Statement counts, as Expression is a subclass of it, even though this isn't + /* For Eclipse, only Statement counts, as Expression is a subclass of it, even though this isn't * entirely correct according to the JLS spec (only some expressions can be used as statements, not all of them). */ - @Override protected Collection<Class<? extends ASTNode>> getStatementTypes() { + private static Collection<Class<? extends ASTNode>> statementTypes() { return Collections.<Class<? extends ASTNode>>singleton(Statement.class); } diff --git a/src/core/lombok/eclipse/EclipseASTVisitor.java b/src/core/lombok/eclipse/EclipseASTVisitor.java index aa19adc6..f5b49cbb 100644 --- a/src/core/lombok/eclipse/EclipseASTVisitor.java +++ b/src/core/lombok/eclipse/EclipseASTVisitor.java @@ -40,7 +40,7 @@ import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; /** - * Implement so you can ask any JavacAST.Node to traverse depth-first through all children, + * Implement so you can ask any EclipseAST.Node to traverse depth-first through all children, * calling the appropriate visit and endVisit methods. */ public interface EclipseASTVisitor { diff --git a/src/core/lombok/eclipse/EclipseNode.java b/src/core/lombok/eclipse/EclipseNode.java index 2c970db2..49867e62 100644 --- a/src/core/lombok/eclipse/EclipseNode.java +++ b/src/core/lombok/eclipse/EclipseNode.java @@ -54,70 +54,70 @@ public class EclipseNode extends lombok.core.LombokNode<EclipseAST, EclipseNode, switch (getKind()) { case COMPILATION_UNIT: - visitor.visitCompilationUnit(this, (CompilationUnitDeclaration)get()); + visitor.visitCompilationUnit(this, (CompilationUnitDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitCompilationUnit(this, (CompilationUnitDeclaration)get()); + visitor.endVisitCompilationUnit(this, (CompilationUnitDeclaration) get()); break; case TYPE: - visitor.visitType(this, (TypeDeclaration)get()); + visitor.visitType(this, (TypeDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitType(this, (TypeDeclaration)get()); + visitor.endVisitType(this, (TypeDeclaration) get()); break; case FIELD: - visitor.visitField(this, (FieldDeclaration)get()); + visitor.visitField(this, (FieldDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitField(this, (FieldDeclaration)get()); + visitor.endVisitField(this, (FieldDeclaration) get()); break; case INITIALIZER: - visitor.visitInitializer(this, (Initializer)get()); + visitor.visitInitializer(this, (Initializer) get()); ast.traverseChildren(visitor, this); - visitor.endVisitInitializer(this, (Initializer)get()); + visitor.endVisitInitializer(this, (Initializer) get()); break; case METHOD: if (get() instanceof Clinit) return; - visitor.visitMethod(this, (AbstractMethodDeclaration)get()); + visitor.visitMethod(this, (AbstractMethodDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitMethod(this, (AbstractMethodDeclaration)get()); + visitor.endVisitMethod(this, (AbstractMethodDeclaration) get()); break; case ARGUMENT: - AbstractMethodDeclaration method = (AbstractMethodDeclaration)up().get(); - visitor.visitMethodArgument(this, (Argument)get(), method); + AbstractMethodDeclaration method = (AbstractMethodDeclaration) up().get(); + visitor.visitMethodArgument(this, (Argument) get(), method); ast.traverseChildren(visitor, this); - visitor.endVisitMethodArgument(this, (Argument)get(), method); + visitor.endVisitMethodArgument(this, (Argument) get(), method); break; case LOCAL: - visitor.visitLocal(this, (LocalDeclaration)get()); + visitor.visitLocal(this, (LocalDeclaration) get()); ast.traverseChildren(visitor, this); - visitor.endVisitLocal(this, (LocalDeclaration)get()); + visitor.endVisitLocal(this, (LocalDeclaration) get()); break; case ANNOTATION: switch (up().getKind()) { case TYPE: - visitor.visitAnnotationOnType((TypeDeclaration)up().get(), this, (Annotation)get()); + visitor.visitAnnotationOnType((TypeDeclaration) up().get(), this, (Annotation) get()); break; case FIELD: - visitor.visitAnnotationOnField((FieldDeclaration)up().get(), this, (Annotation)get()); + visitor.visitAnnotationOnField((FieldDeclaration) up().get(), this, (Annotation) get()); break; case METHOD: - visitor.visitAnnotationOnMethod((AbstractMethodDeclaration)up().get(), this, (Annotation)get()); + visitor.visitAnnotationOnMethod((AbstractMethodDeclaration) up().get(), this, (Annotation) get()); break; case ARGUMENT: visitor.visitAnnotationOnMethodArgument( - (Argument)parent.get(), - (AbstractMethodDeclaration)parent.directUp().get(), - this, (Annotation)get()); + (Argument) parent.get(), + (AbstractMethodDeclaration) parent.directUp().get(), + this, (Annotation) get()); break; case LOCAL: - visitor.visitAnnotationOnLocal((LocalDeclaration)parent.get(), this, (Annotation)get()); + visitor.visitAnnotationOnLocal((LocalDeclaration) parent.get(), this, (Annotation) get()); break; default: throw new AssertionError("Annotation not expected as child of a " + up().getKind()); } break; case STATEMENT: - visitor.visitStatement(this, (Statement)get()); + visitor.visitStatement(this, (Statement) get()); ast.traverseChildren(visitor, this); - visitor.endVisitStatement(this, (Statement)get()); + visitor.endVisitStatement(this, (Statement) get()); break; default: throw new AssertionError("Unexpected kind during node traversal: " + getKind()); diff --git a/src/core/lombok/eclipse/TransformEclipseAST.java b/src/core/lombok/eclipse/TransformEclipseAST.java index e6528178..683465c9 100644 --- a/src/core/lombok/eclipse/TransformEclipseAST.java +++ b/src/core/lombok/eclipse/TransformEclipseAST.java @@ -106,7 +106,7 @@ public class TransformEclipseAST { EclipseAST existing = null; if (astCacheField != null) { try { - existing = (EclipseAST)astCacheField.get(ast); + existing = (EclipseAST) astCacheField.get(ast); } catch (Exception e) { // existing remains null } diff --git a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java index 8326e1d0..6617d21a 100644 --- a/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java +++ b/src/core/lombok/eclipse/handlers/EclipseHandlerUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2018 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 @@ -48,6 +48,7 @@ import lombok.core.TypeResolver; import lombok.core.configuration.NullCheckExceptionType; import lombok.core.debug.ProblemReporter; import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAST; import lombok.eclipse.EclipseNode; import lombok.experimental.Accessors; @@ -143,7 +144,7 @@ public class EclipseHandlerUtil { return getGeneratedBy(node) != null; } - public static ASTNode setGeneratedBy(ASTNode node, ASTNode source) { + public static <T extends ASTNode> T setGeneratedBy(T node, ASTNode source) { ASTNode_generatedBy.set(node, source); return node; } @@ -166,6 +167,7 @@ public class EclipseHandlerUtil { } public static boolean isFieldDeprecated(EclipseNode fieldNode) { + if (!(fieldNode.get() instanceof FieldDeclaration)) return false; FieldDeclaration field = (FieldDeclaration) fieldNode.get(); if ((field.modifiers & ClassFileConstants.AccDeprecated) != 0) { return true; @@ -195,16 +197,36 @@ public class EclipseHandlerUtil { TypeResolver resolver = new TypeResolver(node.getImportList()); return resolver.typeMatches(node, type.getName(), typeName); + } + + /** + * Checks if the given TypeReference node is likely to be a reference to the provided class. + * + * @param type An actual type. This method checks if {@code typeNode} is likely to be a reference to this type. + * @param node A Lombok AST node. Any node in the appropriate compilation unit will do (used to get access to import statements). + * @param typeRef A type reference to check. + */ + public static boolean typeMatches(String type, EclipseNode node, TypeReference typeRef) { + if (typeRef == null || typeRef.getTypeName() == null || typeRef.getTypeName().length == 0) return false; + String lastPartA = new String(typeRef.getTypeName()[typeRef.getTypeName().length -1]); + int lastIndex = type.lastIndexOf('.'); + String lastPartB = lastIndex == -1 ? type : type.substring(lastIndex + 1); + if (!lastPartA.equals(lastPartB)) return false; + String typeName = toQualifiedName(typeRef.getTypeName()); + TypeResolver resolver = new TypeResolver(node.getImportList()); + return resolver.typeMatches(node, type, 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 (child.getKind() != Kind.ANNOTATION) continue; + for (String annType : INVALID_ON_BUILDERS) { if (annotationTypeMatches(annType, child)) { if (disallowed == null) disallowed = new ArrayList<String>(); - disallowed.add(annType.getSimpleName()); + int lastIndex = annType.lastIndexOf('.'); + disallowed.add(lastIndex == -1 ? annType : annType.substring(lastIndex + 1)); } } } @@ -298,6 +320,10 @@ public class EclipseHandlerUtil { return new SingleTypeReference(typeName, p); } + public static TypeReference[] copyTypes(TypeReference[] refs) { + return copyTypes(refs, null); + } + /** * Convenience method that creates a new array and copies each TypeReference in the source array via * {@link #copyType(TypeReference, ASTNode)}. @@ -312,6 +338,10 @@ public class EclipseHandlerUtil { return outs; } + public static TypeReference copyType(TypeReference ref) { + return copyType(ref, null); + } + /** * You can't share TypeReference objects or subtle errors start happening. * Unfortunately the TypeReference type hierarchy is complicated and there's no clone @@ -336,22 +366,23 @@ public class EclipseHandlerUtil { } } } + TypeReference typeRef = new ParameterizedQualifiedTypeReference(iRef.tokens, args, iRef.dimensions(), copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof ArrayQualifiedTypeReference) { ArrayQualifiedTypeReference iRef = (ArrayQualifiedTypeReference) ref; TypeReference typeRef = new ArrayQualifiedTypeReference(iRef.tokens, iRef.dimensions(), copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof QualifiedTypeReference) { QualifiedTypeReference iRef = (QualifiedTypeReference) ref; TypeReference typeRef = new QualifiedTypeReference(iRef.tokens, copy(iRef.sourcePositions)); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -368,14 +399,14 @@ public class EclipseHandlerUtil { } TypeReference typeRef = new ParameterizedSingleTypeReference(iRef.token, args, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } if (ref instanceof ArrayTypeReference) { ArrayTypeReference iRef = (ArrayTypeReference) ref; TypeReference typeRef = new ArrayTypeReference(iRef.token, iRef.dimensions(), (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -386,14 +417,14 @@ public class EclipseHandlerUtil { wildcard.sourceStart = original.sourceStart; wildcard.sourceEnd = original.sourceEnd; if (original.bound != null) wildcard.bound = copyType(original.bound, source); - setGeneratedBy(wildcard, source); + if (source != null) setGeneratedBy(wildcard, source); return wildcard; } if (ref instanceof SingleTypeReference) { SingleTypeReference iRef = (SingleTypeReference) ref; TypeReference typeRef = new SingleTypeReference(iRef.token, (long)iRef.sourceStart << 32 | iRef.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); return typeRef; } @@ -401,19 +432,17 @@ public class EclipseHandlerUtil { } public static Annotation[] copyAnnotations(ASTNode source, Annotation[]... allAnnotations) { - boolean allNull = true; - - List<Annotation> result = new ArrayList<Annotation>(); + List<Annotation> result = null; for (Annotation[] annotations : allAnnotations) { if (annotations != null) { - allNull = false; for (Annotation annotation : annotations) { + if (result == null) result = new ArrayList<Annotation>(); result.add(copyAnnotation(annotation, source)); } } } - if (allNull) return null; - return result.toArray(new Annotation[0]); + + return result == null ? null : result.toArray(new Annotation[0]); } public static boolean hasAnnotation(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { @@ -434,6 +463,42 @@ public class EclipseHandlerUtil { } } + public static boolean hasAnnotation(String type, EclipseNode node) { + if (node == null) return false; + if (type == null) return false; + switch (node.getKind()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (EclipseNode child : node.down()) { + if (annotationTypeMatches(type, child)) return true; + } + // intentional fallthrough + default: + return false; + } + } + + public static EclipseNode findAnnotation(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { + if (node == null) return null; + if (type == null) return null; + switch (node.getKind()) { + case ARGUMENT: + case FIELD: + case LOCAL: + case TYPE: + case METHOD: + for (EclipseNode child : node.down()) { + if (annotationTypeMatches(type, child)) return child; + } + // intentional fallthrough + default: + return null; + } + } + /** * Checks if the provided annotation type is likely to be the intended type for the given annotation node. * @@ -441,11 +506,25 @@ public class EclipseHandlerUtil { */ public static boolean annotationTypeMatches(Class<? extends java.lang.annotation.Annotation> type, EclipseNode node) { if (node.getKind() != Kind.ANNOTATION) return false; - return typeMatches(type, node, ((Annotation)node.get()).type); + return typeMatches(type, node, ((Annotation) node.get()).type); + } + + /** + * Checks if the provided annotation type is likely to be the intended type for the given annotation node. + * + * This is a guess, but a decent one. + */ + public static boolean annotationTypeMatches(String type, EclipseNode node) { + if (node.getKind() != Kind.ANNOTATION) return false; + return typeMatches(type, node, ((Annotation) node.get()).type); + } + + public static TypeReference cloneSelfType(EclipseNode context) { + return cloneSelfType(context, null); } public static TypeReference cloneSelfType(EclipseNode context, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; long p = (long)pS << 32 | pE; EclipseNode type = context; TypeReference result = null; @@ -457,7 +536,7 @@ public class EclipseHandlerUtil { int idx = 0; for (TypeParameter param : typeDecl.typeParameters) { TypeReference typeRef = new SingleTypeReference(param.name, (long)param.sourceStart << 32 | param.sourceEnd); - setGeneratedBy(typeRef, source); + if (source != null) setGeneratedBy(typeRef, source); refs[idx++] = typeRef; } result = new ParameterizedSingleTypeReference(typeDecl.name, refs, 0, p); @@ -465,7 +544,7 @@ public class EclipseHandlerUtil { result = new SingleTypeReference(((TypeDeclaration)type.get()).name, p); } } - if (result != null) setGeneratedBy(result, source); + if (result != null && source != null) setGeneratedBy(result, source); return result; } @@ -668,47 +747,39 @@ public class EclipseHandlerUtil { */ public static <A extends java.lang.annotation.Annotation> AnnotationValues<A> createAnnotation(Class<A> type, final EclipseNode annotationNode) { + final Annotation annotation = (Annotation) annotationNode.get(); Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); - final MemberValuePair[] pairs = annotation.memberValuePairs(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); + MemberValuePair[] memberValuePairs = annotation.memberValuePairs(); + + if (memberValuePairs != null) for (final MemberValuePair pair : memberValuePairs) { List<String> raws = new ArrayList<String>(); List<Object> expressionValues = new ArrayList<Object>(); List<Object> guesses = new ArrayList<Object>(); - Expression fullExpression = null; Expression[] expressions = null; - if (pairs != null) for (MemberValuePair pair : pairs) { - char[] n = pair.name; - String mName = n == null ? "value" : new String(pair.name); - if (mName.equals(name)) fullExpression = pair.value; + char[] n = pair.name; + String mName = (n == null || n.length == 0) ? "value" : new String(pair.name); + final Expression rhs = pair.value; + if (rhs instanceof ArrayInitializer) { + expressions = ((ArrayInitializer)rhs).expressions; + } else if (rhs != null) { + expressions = new Expression[] { rhs }; } - - boolean isExplicit = fullExpression != null; - - if (isExplicit) { - if (fullExpression instanceof ArrayInitializer) { - expressions = ((ArrayInitializer)fullExpression).expressions; - } else expressions = new Expression[] { fullExpression }; - if (expressions != null) for (Expression ex : expressions) { - StringBuffer sb = new StringBuffer(); - ex.print(0, sb); - raws.add(sb.toString()); - expressionValues.add(ex); - guesses.add(calculateValue(ex)); - } + if (expressions != null) for (Expression ex : expressions) { + StringBuffer sb = new StringBuffer(); + ex.print(0, sb); + raws.add(sb.toString()); + expressionValues.add(ex); + guesses.add(calculateValue(ex)); } - final Expression fullExpr = fullExpression; final Expression[] exprs = expressions; - - values.put(name, new AnnotationValue(annotationNode, raws, expressionValues, guesses, isExplicit) { + values.put(mName, new AnnotationValue(annotationNode, raws, expressionValues, guesses, true) { @Override public void setError(String message, int valueIdx) { Expression ex; - if (valueIdx == -1) ex = fullExpr; + if (valueIdx == -1) ex = rhs; else ex = exprs != null ? exprs[valueIdx] : null; if (ex == null) ex = annotation; @@ -721,7 +792,7 @@ public class EclipseHandlerUtil { @Override public void setWarning(String message, int valueIdx) { Expression ex; - if (valueIdx == -1) ex = fullExpr; + if (valueIdx == -1) ex = rhs; else ex = exprs != null ? exprs[valueIdx] : null; if (ex == null) ex = annotation; @@ -734,6 +805,21 @@ public class EclipseHandlerUtil { }); } + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + if (!values.containsKey(name)) { + values.put(name, new AnnotationValue(annotationNode, new ArrayList<String>(), new ArrayList<Object>(), new ArrayList<Object>(), false) { + @Override public void setError(String message, int valueIdx) { + annotationNode.addError(message); + } + @Override public void setWarning(String message, int valueIdx) { + annotationNode.addWarning(message); + } + }); + } + } + return new AnnotationValues<A>(type, values, annotationNode); } @@ -811,7 +897,7 @@ public class EclipseHandlerUtil { // Check if the class has a @Getter annotation. - if (!hasGetterAnnotation && new HandleGetter().fieldQualifiesForGetterGeneration(field)) { + if (!hasGetterAnnotation && HandleGetter.fieldQualifiesForGetterGeneration(field)) { //Check if the class has @Getter or @Data annotation. EclipseNode containingType = field.up(); @@ -865,7 +951,7 @@ public class EclipseHandlerUtil { } static Expression createFieldAccessor(EclipseNode field, FieldAccess fieldAccess, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; long p = (long)pS << 32 | pE; boolean lookForGetter = lookForGetter(field, fieldAccess); @@ -881,14 +967,17 @@ public class EclipseHandlerUtil { ref.receiver = new SingleNameReference(((TypeDeclaration)containerNode.get()).name, p); } else { Expression smallRef = new FieldReference(field.getName().toCharArray(), p); - setGeneratedBy(smallRef, source); + if (source != null) setGeneratedBy(smallRef, source); return smallRef; } } else { ref.receiver = new ThisReference(pS, pE); } - setGeneratedBy(ref, source); - setGeneratedBy(ref.receiver, source); + + if (source != null) { + setGeneratedBy(ref, source); + setGeneratedBy(ref.receiver, source); + } return ref; } @@ -1145,9 +1234,8 @@ public class EclipseHandlerUtil { if (params < minArgs || params > maxArgs) continue; } - if (def.annotations != null) for (Annotation anno : def.annotations) { - if (typeMatches(Tolerate.class, node, anno.type)) continue top; - } + + if (isTolerate(node, def)) continue top; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } @@ -1158,6 +1246,13 @@ public class EclipseHandlerUtil { return MemberExistsResult.NOT_EXISTS; } + public static boolean isTolerate(EclipseNode node, AbstractMethodDeclaration def) { + if (def.annotations != null) for (Annotation anno : def.annotations) { + if (typeMatches(Tolerate.class, node, anno.type)) return true; + } + return false; + } + /** * Checks if there is a (non-default) constructor. In case of multiple constructors (overloading), only * the first constructor decides if EXISTS_BY_USER or EXISTS_BY_LOMBOK is returned. @@ -1171,13 +1266,11 @@ public class EclipseHandlerUtil { if (node != null && node.get() instanceof TypeDeclaration) { TypeDeclaration typeDecl = (TypeDeclaration)node.get(); - if (typeDecl.methods != null) top: for (AbstractMethodDeclaration def : typeDecl.methods) { + if (typeDecl.methods != null) for (AbstractMethodDeclaration def : typeDecl.methods) { if (def instanceof ConstructorDeclaration) { if ((def.bits & ASTNode.IsDefaultConstructor) != 0) continue; - if (def.annotations != null) for (Annotation anno : def.annotations) { - if (typeMatches(Tolerate.class, node, anno.type)) continue top; - } + if (isTolerate(node, def)) continue; return getGeneratedBy(def) == null ? MemberExistsResult.EXISTS_BY_USER : MemberExistsResult.EXISTS_BY_LOMBOK; } @@ -1191,8 +1284,9 @@ 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 EclipseNode injectFieldSuppressWarnings(EclipseNode type, FieldDeclaration field) { - field.annotations = createSuppressWarningsAll(field, field.annotations); + public static EclipseNode injectFieldAndMarkGenerated(EclipseNode type, FieldDeclaration field) { + field.annotations = addSuppressWarningsAll(type, field, field.annotations); + field.annotations = addGenerated(type, field, field.annotations); return injectField(type, field); } @@ -1237,7 +1331,8 @@ public class EclipseHandlerUtil { * Inserts a method into an existing type. The type must represent a {@code TypeDeclaration}. */ public static EclipseNode injectMethod(EclipseNode type, AbstractMethodDeclaration method) { - method.annotations = createSuppressWarningsAll(method, method.annotations); + method.annotations = addSuppressWarningsAll(type, method, method.annotations); + method.annotations = addGenerated(type, method, method.annotations); TypeDeclaration parent = (TypeDeclaration) type.get(); if (parent.methods == null) { @@ -1279,9 +1374,10 @@ public class EclipseHandlerUtil { * @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); + type.annotations = addSuppressWarningsAll(typeNode, type, type.annotations); + type.annotations = addGenerated(typeNode, type, type.annotations); TypeDeclaration parent = (TypeDeclaration) typeNode.get(); - + if (parent.memberTypes == null) { parent.memberTypes = new TypeDeclaration[] { type }; } else { @@ -1295,32 +1391,82 @@ public class EclipseHandlerUtil { } private static final char[] ALL = "all".toCharArray(); + private static final char[] JUSTIFICATION = "justification".toCharArray(); + private static final char[] GENERATED_CODE = "generated code".toCharArray(); + private static final char[] LOMBOK = "lombok".toCharArray(); + private static final char[][] JAVAX_ANNOTATION_GENERATED = Eclipse.fromQualifiedName("javax.annotation.Generated"); + private static final char[][] LOMBOK_GENERATED = Eclipse.fromQualifiedName("lombok.Generated"); + private static final char[][] EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS = Eclipse.fromQualifiedName("edu.umd.cs.findbugs.annotations.SuppressFBWarnings"); + + public static Annotation[] addSuppressWarningsAll(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { + Annotation[] anns = addAnnotation(source, originalAnnotationArray, TypeConstants.JAVA_LANG_SUPPRESSWARNINGS, new StringLiteral(ALL, 0, 0, 0)); + + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS))) { + MemberValuePair mvp = new MemberValuePair(JUSTIFICATION, 0, 0, new StringLiteral(GENERATED_CODE, 0, 0, 0)); + anns = addAnnotation(source, anns, EDU_UMD_CS_FINDBUGS_ANNOTATIONS_SUPPRESSFBWARNINGS, mvp); + } + + return anns; + } + + public static Annotation[] addGenerated(EclipseNode node, ASTNode source, Annotation[] originalAnnotationArray) { + Annotation[] result = originalAnnotationArray; + if (HandlerUtil.shouldAddGenerated(node)) { + result = addAnnotation(source, result, JAVAX_ANNOTATION_GENERATED, new StringLiteral(LOMBOK, 0, 0, 0)); + } + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_LOMBOK_GENERATED_ANNOTATIONS))) { + result = addAnnotation(source, result, LOMBOK_GENERATED, null); + } + return result; + } - public static Annotation[] createSuppressWarningsAll(ASTNode source, Annotation[] originalAnnotationArray) { + private static Annotation[] addAnnotation(ASTNode source, Annotation[] originalAnnotationArray, char[][] annotationTypeFqn, ASTNode arg) { + char[] simpleName = annotationTypeFqn[annotationTypeFqn.length - 1]; + if (originalAnnotationArray != null) for (Annotation ann : originalAnnotationArray) { - char[] lastToken = null; - if (ann.type instanceof QualifiedTypeReference) { char[][] t = ((QualifiedTypeReference) ann.type).tokens; - lastToken = t[t.length - 1]; - } else if (ann.type instanceof SingleTypeReference) { - lastToken = ((SingleTypeReference) ann.type).token; + if (Arrays.deepEquals(t, annotationTypeFqn)) return originalAnnotationArray; } - if (lastToken != null && new String(lastToken).equals("SuppressWarnings")) return originalAnnotationArray; + if (ann.type instanceof SingleTypeReference) { + char[] lastToken = ((SingleTypeReference) ann.type).token; + if (Arrays.equals(lastToken, simpleName)) return originalAnnotationArray; + } } int pS = source.sourceStart, pE = source.sourceEnd; long p = (long)pS << 32 | pE; - long[] poss = new long[3]; + long[] poss = new long[annotationTypeFqn.length]; Arrays.fill(poss, p); - QualifiedTypeReference suppressWarningsType = new QualifiedTypeReference(TypeConstants.JAVA_LANG_SUPPRESSWARNINGS, poss); - setGeneratedBy(suppressWarningsType, source); - SingleMemberAnnotation ann = new SingleMemberAnnotation(suppressWarningsType, pS); - ann.declarationSourceEnd = pE; - ann.memberValue = new StringLiteral(ALL, pS, pE, 0); + QualifiedTypeReference qualifiedType = new QualifiedTypeReference(annotationTypeFqn, poss); + setGeneratedBy(qualifiedType, source); + Annotation ann; + if (arg instanceof Expression) { + SingleMemberAnnotation sma = new SingleMemberAnnotation(qualifiedType, pS); + sma.declarationSourceEnd = pE; + arg.sourceStart = pS; + arg.sourceEnd = pE; + sma.memberValue = (Expression) arg; + setGeneratedBy(sma.memberValue, source); + ann = sma; + } else if (arg instanceof MemberValuePair) { + NormalAnnotation na = new NormalAnnotation(qualifiedType, pS); + na.declarationSourceEnd = pE; + arg.sourceStart = pS; + arg.sourceEnd = pE; + na.memberValuePairs = new MemberValuePair[] {(MemberValuePair) arg}; + setGeneratedBy(na.memberValuePairs[0], source); + setGeneratedBy(na.memberValuePairs[0].value, source); + na.memberValuePairs[0].value.sourceStart = pS; + na.memberValuePairs[0].value.sourceEnd = pE; + ann = na; + } else { + MarkerAnnotation ma = new MarkerAnnotation(qualifiedType, pS); + ma.declarationSourceEnd = pE; + ann = ma; + } setGeneratedBy(ann, source); - setGeneratedBy(ann.memberValue, source); if (originalAnnotationArray == null) return new Annotation[] { ann }; Annotation[] newAnnotationArray = new Annotation[originalAnnotationArray.length + 1]; System.arraycopy(originalAnnotationArray, 0, newAnnotationArray, 0, originalAnnotationArray.length); @@ -1489,7 +1635,7 @@ public class EclipseHandlerUtil { * with eclipse versions before 3.7. */ public static IntLiteral makeIntLiteral(char[] token, ASTNode source) { - int pS = source.sourceStart, pE = source.sourceEnd; + int pS = source == null ? 0 : source.sourceStart, pE = source == null ? 0 : source.sourceEnd; IntLiteral result; try { if (intLiteralConstructor != null) { @@ -1504,7 +1650,8 @@ public class EclipseHandlerUtil { } catch (InstantiationException e) { throw Lombok.sneakyThrow(e); } - setGeneratedBy(result, source); + + if (source != null) setGeneratedBy(result, source); return result; } @@ -1535,6 +1682,14 @@ public class EclipseHandlerUtil { return true; } + public static void addError(String errorName, EclipseNode node) { + if (node.getLatestJavaSpecSupported() < 8) { + node.addError("The correct format is " + errorName + "_={@SomeAnnotation, @SomeOtherAnnotation})"); + } else { + node.addError("The correct format is " + errorName + "=@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + } + } + public static List<Annotation> unboxAndRemoveAnnotationParameter(Annotation annotation, String annotationName, String errorName, EclipseNode errorNode) { if ("value".equals(annotationName)) { // We can't unbox this, because SingleMemberAnnotation REQUIRES a value, and this method @@ -1557,51 +1712,70 @@ public class EclipseHandlerUtil { char[] nameAsCharArray = annotationName.toCharArray(); + top: for (int i = 0; i < pairs.length; i++) { - if (pairs[i].name == null || !Arrays.equals(nameAsCharArray, pairs[i].name)) continue; + boolean allowRaw; + char[] name = pairs[i].name; + if (name == null) continue; + if (name.length < nameAsCharArray.length) continue; + for (int j = 0; j < nameAsCharArray.length; j++) { + if (name[j] != nameAsCharArray[j]) continue top; + } + allowRaw = name.length > nameAsCharArray.length; + for (int j = nameAsCharArray.length; j < name.length; j++) { + if (name[j] != '_') continue top; + } + // If we're still here it's the targeted annotation param. Expression value = pairs[i].value; MemberValuePair[] newPairs = new MemberValuePair[pairs.length - 1]; if (i > 0) System.arraycopy(pairs, 0, newPairs, 0, i); if (i < pairs.length - 1) System.arraycopy(pairs, i + 1, newPairs, i, pairs.length - i - 1); normalAnnotation.memberValuePairs = newPairs; - // We have now removed the annotation parameter and stored '@__({... annotations ...})', - // which we must now unbox. - if (!(value instanceof Annotation)) { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - return Collections.emptyList(); - } - - Annotation atDummyIdentifier = (Annotation) value; - if (!(atDummyIdentifier.type instanceof SingleTypeReference) || - !isAllValidOnXCharacters(((SingleTypeReference) atDummyIdentifier.type).token)) { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); - return Collections.emptyList(); - } - - if (atDummyIdentifier instanceof MarkerAnnotation) { - // It's @Getter(onMethod=@__). This is weird, but fine. - return Collections.emptyList(); - } + // We have now removed the annotation parameter and stored the value, + // which we must now unbox. It's either annotations, or @__(annotations). Expression content = null; - if (atDummyIdentifier instanceof NormalAnnotation) { - MemberValuePair[] mvps = ((NormalAnnotation) atDummyIdentifier).memberValuePairs; - if (mvps == null || mvps.length == 0) { - // It's @Getter(onMethod=@__()). This is weird, but fine. + if (value instanceof ArrayInitializer) { + if (!allowRaw) { + addError(errorName, errorNode); return Collections.emptyList(); } - if (mvps.length == 1 && Arrays.equals("value".toCharArray(), mvps[0].name)) { - content = mvps[0].value; + content = value; + } else if (!(value instanceof Annotation)) { + addError(errorName, errorNode); + return Collections.emptyList(); + } else { + Annotation atDummyIdentifier = (Annotation) value; + if (atDummyIdentifier.type instanceof SingleTypeReference && isAllValidOnXCharacters(((SingleTypeReference) atDummyIdentifier.type).token)) { + if (atDummyIdentifier instanceof MarkerAnnotation) { + return Collections.emptyList(); + } else if (atDummyIdentifier instanceof NormalAnnotation) { + MemberValuePair[] mvps = ((NormalAnnotation) atDummyIdentifier).memberValuePairs; + if (mvps == null || mvps.length == 0) { + return Collections.emptyList(); + } + if (mvps.length == 1 && Arrays.equals("value".toCharArray(), mvps[0].name)) { + content = mvps[0].value; + } + } else if (atDummyIdentifier instanceof SingleMemberAnnotation) { + content = ((SingleMemberAnnotation) atDummyIdentifier).memberValue; + } else { + addError(errorName, errorNode); + return Collections.emptyList(); + } + } else { + if (allowRaw) { + content = atDummyIdentifier; + } else { + addError(errorName, errorNode); + return Collections.emptyList(); + } } } - if (atDummyIdentifier instanceof SingleMemberAnnotation) { - content = ((SingleMemberAnnotation) atDummyIdentifier).memberValue; - } - if (content == null) { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, errorNode); return Collections.emptyList(); } @@ -1613,13 +1787,13 @@ public class EclipseHandlerUtil { if (expressions != null) for (Expression ex : expressions) { if (ex instanceof Annotation) result.add((Annotation) ex); else { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, errorNode); return Collections.emptyList(); } } return result; } else { - errorNode.addError("The correct format is " + errorName + "@__({@SomeAnnotation, @SomeOtherAnnotation}))"); + addError(errorName, errorNode); return Collections.emptyList(); } } diff --git a/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java new file mode 100644 index 00000000..bc779dab --- /dev/null +++ b/src/core/lombok/eclipse/handlers/EclipseSingularsRecipes.java @@ -0,0 +1,349 @@ +/* + * Copyright (C) 2015-2017 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.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.internal.compiler.ast.ASTNode; +import org.eclipse.jdt.internal.compiler.ast.AbstractMethodDeclaration; +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; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.ast.Wildcard; +import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; + +import lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.eclipse.EclipseNode; + +public class EclipseSingularsRecipes { + private static final EclipseSingularsRecipes INSTANCE = new EclipseSingularsRecipes(); + private final Map<String, EclipseSingularizer> singularizers = new HashMap<String, EclipseSingularizer>(); + private final TypeLibrary singularizableTypes = new TypeLibrary(); + + private EclipseSingularsRecipes() { + try { + loadAll(singularizableTypes, singularizers); + singularizableTypes.lock(); + } catch (IOException e) { + System.err.println("Lombok's @Singularizable feature is broken due to misconfigured SPI files: " + e); + } + } + + private static void loadAll(TypeLibrary library, Map<String, EclipseSingularizer> map) throws IOException { + for (EclipseSingularizer handler : SpiLoadUtil.findServices(EclipseSingularizer.class, EclipseSingularizer.class.getClassLoader())) { + for (String type : handler.getSupportedTypes()) { + EclipseSingularizer existingSingularizer = map.get(type); + if (existingSingularizer != null) { + EclipseSingularizer toKeep = existingSingularizer.getClass().getName().compareTo(handler.getClass().getName()) > 0 ? handler : existingSingularizer; + System.err.println("Multiple singularizers found for type " + type + "; the alphabetically first class is used: " + toKeep.getClass().getName()); + map.put(type, toKeep); + } else { + map.put(type, handler); + library.addType(type); + } + } + } + } + + public static EclipseSingularsRecipes get() { + return INSTANCE; + } + + public String toQualified(String typeReference) { + return singularizableTypes.toQualified(typeReference); + } + + public EclipseSingularizer getSingularizer(String fqn) { + return singularizers.get(fqn); + } + + public static final class SingularData { + private final EclipseNode annotation; + private final char[] singularName; + private final char[] pluralName; + private final List<TypeReference> typeArgs; + private final String targetFqn; + private final EclipseSingularizer singularizer; + private final ASTNode source; + + public SingularData(EclipseNode annotation, char[] singularName, char[] pluralName, List<TypeReference> typeArgs, String targetFqn, EclipseSingularizer singularizer, ASTNode source) { + this.annotation = annotation; + this.singularName = singularName; + this.pluralName = pluralName; + this.typeArgs = typeArgs; + this.targetFqn = targetFqn; + this.singularizer = singularizer; + this.source = source; + } + + public void setGeneratedByRecursive(ASTNode target) { + SetGeneratedByVisitor visitor = new SetGeneratedByVisitor(source); + + if (target instanceof AbstractMethodDeclaration) { + ((AbstractMethodDeclaration) target).traverse(visitor, (ClassScope) null); + } else if (target instanceof FieldDeclaration) { + ((FieldDeclaration) target).traverse(visitor, (MethodScope) null); + } else { + target.traverse(visitor, null); + } + } + + public ASTNode getSource() { + return source; + } + + public EclipseNode getAnnotation() { + return annotation; + } + + public char[] getSingularName() { + return singularName; + } + + public char[] getPluralName() { + return pluralName; + } + + public List<TypeReference> getTypeArgs() { + return typeArgs; + } + + public String getTargetFqn() { + return targetFqn; + } + + public EclipseSingularizer getSingularizer() { + return singularizer; + } + + public String getTargetSimpleType() { + int idx = targetFqn.lastIndexOf("."); + return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); + } + } + + public static abstract class EclipseSingularizer { + protected static final long[] NULL_POSS = {0L}; + public abstract LombokImmutableList<String> getSupportedTypes(); + + /** Checks if any of the to-be-generated nodes (fields, methods) already exist. If so, errors on these (singulars don't support manually writing some of it, and returns true). */ + public boolean checkForAlreadyExistingNodesAndGenerateError(EclipseNode builderType, SingularData data) { + for (EclipseNode child : builderType.down()) { + switch (child.getKind()) { + case FIELD: { + FieldDeclaration fd = (FieldDeclaration) child.get(); + char[] name = fd.name; + if (name == null) continue; + if (getGeneratedBy(fd) != null) continue; + for (char[] fieldToBeGenerated : listFieldsToBeGenerated(data, builderType)) { + if (!Arrays.equals(name, fieldToBeGenerated)) continue; + child.addError("Manually adding a field that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + } + case METHOD: { + AbstractMethodDeclaration method = (AbstractMethodDeclaration) child.get(); + char[] name = method.selector; + if (name == null) continue; + if (getGeneratedBy(method) != null) continue; + for (char[] methodToBeGenerated : listMethodsToBeGenerated(data, builderType)) { + if (!Arrays.equals(name, methodToBeGenerated)) continue; + child.addError("Manually adding a method that @Singular @Builder would generate is not supported. If you want to manually manage the builder aspect for this field/parameter, don't use @Singular."); + return true; + } + break; + }} + } + + return false; + } + + public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + return Collections.singletonList(data.pluralName); + } + + public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + char[] p = data.pluralName; + char[] s = data.singularName; + if (Arrays.equals(p, s)) return Collections.singletonList(p); + return Arrays.asList(p, s); + } + + public abstract List<EclipseNode> generateFields(SingularData data, EclipseNode builderType); + public abstract void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain); + public abstract void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName); + + public boolean requiresCleaning() { + try { + return !getClass().getMethod("appendCleaningCode", SingularData.class, EclipseNode.class, List.class).getDeclaringClass().equals(EclipseSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, EclipseNode builderType, List<Statement> statements) { + } + + // -- Utility methods -- + + /** + * Adds the requested number of type arguments to the provided type, copying each argument in {@code typeArgs}. If typeArgs is too long, the extra elements are ignored. + * If {@code typeArgs} is null or too short, {@code java.lang.Object} will be substituted for each missing type argument. + * + * @param count The number of type arguments requested. + * @param addExtends If {@code true}, all bounds are either '? extends X' or just '?'. If false, the reverse is applied, and '? extends Foo' is converted to Foo, '?' to Object, etc. + * @param node Some node in the same AST. Just used to obtain makers and contexts and such. + * @param type The type to add generics to. + * @param typeArgs the list of type args to clone. + * @param source The source annotation that is the root cause of this code generation. + */ + protected TypeReference addTypeArgs(int count, boolean addExtends, EclipseNode node, TypeReference type, List<TypeReference> typeArgs) { + TypeReference[] clonedAndFixedArgs = createTypeArgs(count, addExtends, node, typeArgs); + if (type instanceof SingleTypeReference) { + type = new ParameterizedSingleTypeReference(((SingleTypeReference) type).token, clonedAndFixedArgs, 0, 0L); + } else if (type instanceof QualifiedTypeReference) { + QualifiedTypeReference qtr = (QualifiedTypeReference) type; + TypeReference[][] trs = new TypeReference[qtr.tokens.length][]; + trs[qtr.tokens.length - 1] = clonedAndFixedArgs; + type = new ParameterizedQualifiedTypeReference(((QualifiedTypeReference) type).tokens, trs, 0, NULL_POSS); + } else { + node.addError("Don't know how to clone-and-parameterize type: " + type); + } + + return type; + } + + protected TypeReference[] createTypeArgs(int count, boolean addExtends, EclipseNode node, List<TypeReference> typeArgs) { + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return null; + List<TypeReference> arguments = new ArrayList<TypeReference>(); + + if (typeArgs != null) for (TypeReference orig : typeArgs) { + Wildcard wildcard = orig instanceof Wildcard ? (Wildcard) orig : null; + if (!addExtends) { + if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) { + try { + arguments.add(copyType(wildcard.bound)); + } catch (Exception e) { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } + } else { + arguments.add(copyType(orig)); + } + } else { + if (wildcard != null && (wildcard.kind == Wildcard.UNBOUND || wildcard.kind == Wildcard.SUPER)) { + Wildcard w = new Wildcard(Wildcard.UNBOUND); + arguments.add(w); + } else if (wildcard != null && wildcard.kind == Wildcard.EXTENDS) { + arguments.add(copyType(orig)); + } else { + Wildcard w = new Wildcard(Wildcard.EXTENDS); + w.bound = copyType(orig); + arguments.add(w); + } + } + if (--count == 0) break; + } + + while (count-- > 0) { + if (addExtends) { + arguments.add(new Wildcard(Wildcard.UNBOUND)); + } else { + arguments.add(new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS)); + } + } + + if (arguments.isEmpty()) return null; + return arguments.toArray(new TypeReference[arguments.size()]); + } + + private static final char[] SIZE_TEXT = new char[] {'s', 'i', 'z', 'e'}; + + /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ + protected Expression getSize(EclipseNode builderType, char[] name, boolean nullGuard) { + MessageSend invoke = new MessageSend(); + ThisReference thisRef = new ThisReference(0, 0); + FieldReference thisDotName = new FieldReference(name, 0L); + thisDotName.receiver = thisRef; + invoke.receiver = thisDotName; + invoke.selector = SIZE_TEXT; + if (!nullGuard) return invoke; + + ThisReference cdnThisRef = new ThisReference(0, 0); + FieldReference cdnThisDotName = new FieldReference(name, 0L); + cdnThisDotName.receiver = cdnThisRef; + NullLiteral nullLiteral = new NullLiteral(0, 0); + EqualExpression isNull = new EqualExpression(cdnThisDotName, nullLiteral, OperatorIds.EQUAL_EQUAL); + IntLiteral zeroLiteral = makeIntLiteral(new char[] {'0'}, null); + ConditionalExpression conditional = new ConditionalExpression(isNull, zeroLiteral, invoke); + return conditional; + } + + protected TypeReference cloneParamType(int index, List<TypeReference> typeArgs, EclipseNode builderType) { + if (typeArgs != null && typeArgs.size() > index) { + TypeReference originalType = typeArgs.get(index); + if (originalType instanceof Wildcard) { + Wildcard wOriginalType = (Wildcard) originalType; + if (wOriginalType.kind == Wildcard.EXTENDS) { + try { + return copyType(wOriginalType.bound); + } catch (Exception e) { + // fallthrough + } + } + } else { + return copyType(originalType); + } + } + + return new QualifiedTypeReference(TypeConstants.JAVA_LANG_OBJECT, NULL_POSS); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleBuilder.java b/src/core/lombok/eclipse/handlers/HandleBuilder.java index 522501f6..d4cdc654 100644 --- a/src/core/lombok/eclipse/handlers/HandleBuilder.java +++ b/src/core/lombok/eclipse/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-2018 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -32,53 +32,132 @@ 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.AbstractVariableDeclaration; 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.Assignment; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.ConditionalExpression; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; import org.eclipse.jdt.internal.compiler.ast.MessageSend; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedThisReference; 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.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; 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.ast.UnaryExpression; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; import org.eclipse.jdt.internal.compiler.lookup.ClassScope; +import org.eclipse.jdt.internal.compiler.lookup.MethodScope; import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; import org.mangosdk.spi.ProviderFor; import lombok.AccessLevel; +import lombok.Builder; +import lombok.Builder.ObtainVia; import lombok.ConfigurationKeys; +import lombok.Singular; import lombok.core.AST.Kind; +import lombok.core.handlers.HandlerUtil; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; 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) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder"); + private HandleConstructor handleConstructor = new HandleConstructor(); + + private static final char[] CLEAN_FIELD_NAME = "$lombokUnclean".toCharArray(); + private static final char[] CLEAN_METHOD_NAME = "$lombokClean".toCharArray(); + + private static final boolean toBoolean(Object expr, boolean defaultValue) { + if (expr == null) return defaultValue; + if (expr instanceof FalseLiteral) return false; + if (expr instanceof TrueLiteral) return true; + return ((Boolean) expr).booleanValue(); + } + + private static class BuilderFieldData { + TypeReference type; + char[] rawName; + char[] name; + char[] nameOfDefaultProvider; + char[] nameOfSetFlag; + SingularData singularData; + ObtainVia obtainVia; + EclipseNode obtainViaNode; + EclipseNode originalFieldNode; + List<EclipseNode> createdFields = new ArrayList<EclipseNode>(); + } + + private static boolean equals(String a, char[] b) { + if (a.length() != b.length) return false; + for (int i = 0; i < b.length; i++) { + if (a.charAt(i) != b[i]) return false; + } + return true; + } + + private static boolean equals(String a, char[][] b) { + if (a == null || a.isEmpty()) return b.length == 0; + String[] aParts = a.split("\\."); + if (aParts.length != b.length) return false; + for (int i = 0; i < b.length; i++) { + if (!equals(aParts[i], b[i])) return false; + } + return true; + } + + private static final char[] DEFAULT_PREFIX = {'$', 'd', 'e', 'f', 'a', 'u', 'l', 't', '$'}; + private static final char[] SET_PREFIX = {'$', 's', 'e', 't'}; + + private static final char[] prefixWith(char[] prefix, char[] name) { + char[] out = new char[prefix.length + name.length]; + System.arraycopy(prefix, 0, out, 0, prefix.length); + System.arraycopy(name, 0, out, prefix.length, name.length); + return out; + } + + @Override public void handle(AnnotationValues<Builder> annotation, Annotation ast, EclipseNode annotationNode) { long p = (long) ast.sourceStart << 32 | ast.sourceEnd; Builder builderInstance = annotation.getInstance(); + + // These exist just to support the 'old' lombok.experimental.Builder, which had these properties. lombok.Builder no longer has them. + boolean fluent = toBoolean(annotation.getActualExpression("fluent"), true); + boolean chain = toBoolean(annotation.getActualExpression("chain"), true); + String builderMethodName = builderInstance.builderMethodName(); String buildMethodName = builderInstance.buildMethodName(); String builderClassName = builderInstance.builderClassName(); + String toBuilderMethodName = "toBuilder"; + boolean toBuilder = builderInstance.toBuilder(); + List<char[]> typeArgsForToBuilder = null; if (builderMethodName == null) builderMethodName = "builder"; if (buildMethodName == null) builderMethodName = "build"; @@ -92,36 +171,64 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { EclipseNode parent = annotationNode.up(); - List<TypeReference> typesOfParameters = new ArrayList<TypeReference>(); - List<char[]> namesOfParameters = new ArrayList<char[]>(); + List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); TypeReference returnType; TypeParameter[] typeParams; TypeReference[] thrownExceptions; char[] nameOfStaticBuilderMethod; EclipseNode tdParent; - AbstractMethodDeclaration fillParametersFrom = null; + EclipseNode fillParametersFrom = parent.get() instanceof AbstractMethodDeclaration ? parent : null; + boolean addCleaning = false; + boolean isStatic = true; 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)) { + List<EclipseNode> allFields = new ArrayList<EclipseNode>(); + boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent)); + for (EclipseNode fieldNode : HandleConstructor.findAllFields(tdParent, true)) { 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(removePrefixFromField(fieldNode)); - typesOfParameters.add(fd.type); - fields.add(fieldNode); + EclipseNode isDefault = findAnnotation(Builder.Default.class, fieldNode); + boolean isFinal = ((fd.modifiers & ClassFileConstants.AccFinal) != 0) || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode)); + + BuilderFieldData bfd = new BuilderFieldData(); + bfd.rawName = fieldNode.getName().toCharArray(); + bfd.name = removePrefixFromField(fieldNode); + bfd.type = fd.type; + bfd.singularData = getSingularData(fieldNode, ast); + bfd.originalFieldNode = fieldNode; + + if (bfd.singularData != null && isDefault != null) { + isDefault.addError("@Builder.Default and @Singular cannot be mixed."); + isDefault = null; + } + + if (fd.initialization == null && isDefault != null) { + isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;')."); + isDefault = null; + } + + if (fd.initialization != null && isDefault == null) { + if (isFinal) continue; + fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final."); + } + + if (isDefault != null) { + bfd.nameOfDefaultProvider = prefixWith(DEFAULT_PREFIX, bfd.name); + bfd.nameOfSetFlag = prefixWith(bfd.name, SET_PREFIX); + + MethodDeclaration md = generateDefaultProvider(bfd.nameOfDefaultProvider, td.typeParameters, fieldNode, ast); + if (md != null) injectMethod(tdParent, md); + } + addObtainVia(bfd, fieldNode); + builderFields.add(bfd); + allFields.add(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, fields, null, SkipIfConstructorExists.I_AM_BUILDER, null, - Collections.<Annotation>emptyList(), annotationNode); + handleConstructor.generateConstructor(tdParent, AccessLevel.PACKAGE, allFields, false, null, SkipIfConstructorExists.I_AM_BUILDER, + Collections.<Annotation>emptyList(), annotationNode); returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; @@ -137,7 +244,6 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { tdParent = parent.up(); TypeDeclaration td = (TypeDeclaration) tdParent.get(); - fillParametersFrom = cd; returnType = namePlusTypeParamsToTypeReference(td.name, td.typeParameters, p); typeParams = td.typeParameters; thrownExceptions = cd.thrownExceptions; @@ -146,11 +252,79 @@ public class HandleBuilder extends EclipseAnnotationHandler<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; + isStatic = md.isStatic(); + + if (toBuilder) { + final String TO_BUILDER_NOT_SUPPORTED = "@Builder(toBuilder=true) is only supported if you return your own type."; + char[] token; + char[][] pkg = null; + if (md.returnType.dimensions() > 0) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (md.returnType instanceof SingleTypeReference) { + token = ((SingleTypeReference) md.returnType).token; + } else if (md.returnType instanceof QualifiedTypeReference) { + pkg = ((QualifiedTypeReference) md.returnType).tokens; + token = pkg[pkg.length]; + char[][] pkg_ = new char[pkg.length - 1][]; + System.arraycopy(pkg, 0, pkg_, 0, pkg_.length); + pkg = pkg_; + } else { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (pkg != null && !equals(parent.getPackageDeclaration(), pkg)) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + if (tdParent == null || !equals(tdParent.getName(), token)) { + annotationNode.addError(TO_BUILDER_NOT_SUPPORTED); + return; + } + + TypeParameter[] tpOnType = ((TypeDeclaration) tdParent.get()).typeParameters; + TypeParameter[] tpOnMethod = md.typeParameters; + TypeReference[][] tpOnRet_ = null; + if (md.returnType instanceof ParameterizedSingleTypeReference) { + tpOnRet_ = new TypeReference[1][]; + tpOnRet_[0] = ((ParameterizedSingleTypeReference) md.returnType).typeArguments; + } else if (md.returnType instanceof ParameterizedQualifiedTypeReference) { + tpOnRet_ = ((ParameterizedQualifiedTypeReference) md.returnType).typeArguments; + } + + if (tpOnRet_ != null) for (int i = 0; i < tpOnRet_.length - 1; i++) { + if (tpOnRet_[i] != null && tpOnRet_[i].length > 0) { + annotationNode.addError("@Builder(toBuilder=true) is not supported if returning a type with generics applied to an intermediate."); + return; + } + } + TypeReference[] tpOnRet = tpOnRet_ == null ? null : tpOnRet_[tpOnRet_.length - 1]; + typeArgsForToBuilder = new ArrayList<char[]>(); + + // Every typearg on this method needs to be found in the return type, but the reverse is not true. + // We also need to 'map' them. + + + if (tpOnMethod != null) for (TypeParameter onMethod : tpOnMethod) { + int pos = -1; + if (tpOnRet != null) for (int i = 0; i < tpOnRet.length; i++) { + if (tpOnRet[i].getClass() != SingleTypeReference.class) continue; + if (!Arrays.equals(((SingleTypeReference) tpOnRet[i]).token, onMethod.name)) continue; + pos = i; + } + if (pos == -1 || tpOnType == null || tpOnType.length <= pos) { + annotationNode.addError("@Builder(toBuilder=true) requires that each type parameter on the static method is part of the typeargs of the return value. Type parameter " + new String(onMethod.name) + " is not part of the return type."); + return; + } + + typeArgsForToBuilder.add(tpOnType[pos].name); + } } - fillParametersFrom = md; + returnType = copyType(md.returnType, ast); typeParams = md.typeParameters; thrownExceptions = md.thrownExceptions; @@ -185,159 +359,373 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { builderClassName = new String(token) + "Builder"; } } else { - annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); + annotationNode.addError("@Builder is only supported on types, constructors, and methods."); return; } if (fillParametersFrom != null) { - if (fillParametersFrom.arguments != null) for (Argument a : fillParametersFrom.arguments) { - namesOfParameters.add(a.name); - typesOfParameters.add(a.type); + for (EclipseNode param : fillParametersFrom.down()) { + if (param.getKind() != Kind.ARGUMENT) continue; + BuilderFieldData bfd = new BuilderFieldData(); + Argument arg = (Argument) param.get(); + bfd.rawName = arg.name; + bfd.name = arg.name; + bfd.type = arg.type; + bfd.singularData = getSingularData(param, ast); + bfd.originalFieldNode = param; + addObtainVia(bfd, param); + builderFields.add(bfd); } } EclipseNode builderType = findInnerClass(tdParent, builderClassName); if (builderType == null) { - builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); + builderType = makeBuilderClass(isStatic, tdParent, builderClassName, typeParams, ast); } else { + TypeDeclaration builderTypeDeclaration = (TypeDeclaration) builderType.get(); + if (isStatic && (builderTypeDeclaration.modifiers & ClassFileConstants.AccStatic) == 0) { + annotationNode.addError("Existing Builder must be a static inner class."); + return; + } else if (!isStatic && (builderTypeDeclaration.modifiers & ClassFileConstants.AccStatic) != 0) { + annotationNode.addError("Existing Builder must be a non-static inner class."); + return; + } sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); + /* generate errors for @Singular BFDs that have one already defined node. */ { + for (BuilderFieldData bfd : builderFields) { + SingularData sd = bfd.singularData; + if (sd == null) continue; + EclipseSingularizer singularizer = sd.getSingularizer(); + if (singularizer == null) continue; + if (singularizer.checkForAlreadyExistingNodesAndGenerateError(builderType, sd)) { + bfd.singularData = null; + } + } + } + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + if (bfd.singularData.getSingularizer().requiresCleaning()) { + addCleaning = true; + break; + } + } + if (bfd.obtainVia != null) { + if (bfd.obtainVia.field().isEmpty() == bfd.obtainVia.method().isEmpty()) { + bfd.obtainViaNode.addError("The syntax is either @ObtainVia(field = \"fieldName\") or @ObtainVia(method = \"methodName\")."); + return; + } + if (bfd.obtainVia.method().isEmpty() && bfd.obtainVia.isStatic()) { + bfd.obtainViaNode.addError("@ObtainVia(isStatic = true) is not valid unless 'method' has been set."); + return; + } + } } - List<EclipseNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - List<AbstractMethodDeclaration> newMethods = new ArrayList<AbstractMethodDeclaration>(); - for (EclipseNode fieldNode : fieldNodes) { - MethodDeclaration newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, builderInstance.fluent(), builderInstance.chain()); - if (newMethod != null) newMethods.add(newMethod); + + generateBuilderFields(builderType, builderFields, ast); + if (addCleaning) { + FieldDeclaration cleanDecl = new FieldDeclaration(CLEAN_FIELD_NAME, 0, -1); + cleanDecl.declarationSourceEnd = -1; + cleanDecl.modifiers = ClassFileConstants.AccPrivate; + cleanDecl.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + injectFieldAndMarkGenerated(builderType, cleanDecl); } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { ConstructorDeclaration cd = HandleConstructor.createConstructor( - AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), null, - annotationNode, Collections.<Annotation>emptyList()); + AccessLevel.PACKAGE, builderType, Collections.<EclipseNode>emptyList(), false, + annotationNode, Collections.<Annotation>emptyList()); if (cd != null) injectMethod(builderType, cd); } - for (AbstractMethodDeclaration newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData bfd : builderFields) { + makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain); + } + if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, ast, thrownExceptions); + MethodDeclaration md = generateBuildMethod(tdParent, isStatic, buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, addCleaning, ast); if (md != null) injectMethod(builderType, md); } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + List<EclipseNode> fieldNodes = new ArrayList<EclipseNode>(); + for (BuilderFieldData bfd : builderFields) { + fieldNodes.addAll(bfd.createdFields); + } MethodDeclaration md = HandleToString.createToString(builderType, fieldNodes, true, false, ast, FieldAccess.ALWAYS_FIELD); if (md != null) injectMethod(builderType, md); } + if (addCleaning) { + MethodDeclaration cleanMethod = generateCleanMethod(builderFields, builderType, ast); + if (cleanMethod != null) injectMethod(builderType, cleanMethod); + } + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { - MethodDeclaration md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams, ast); + MethodDeclaration md = generateBuilderMethod(isStatic, builderMethodName, builderClassName, tdParent, typeParams, ast); + if (md != null) injectMethod(tdParent, md); + } + + if (toBuilder) switch (methodExists(toBuilderMethodName, tdParent, 0)) { + case EXISTS_BY_USER: + annotationNode.addWarning("Not generating toBuilder() as it already exists."); + break; + case NOT_EXISTS: + TypeParameter[] tps = typeParams; + if (typeArgsForToBuilder != null) { + tps = new TypeParameter[typeArgsForToBuilder.size()]; + for (int i = 0; i < tps.length; i++) { + tps[i] = new TypeParameter(); + tps[i].name = typeArgsForToBuilder.get(i); + } + } + MethodDeclaration md = generateToBuilderMethod(toBuilderMethodName, builderClassName, tdParent, tps, builderFields, fluent, ast); + if (md != null) injectMethod(tdParent, md); } } - public MethodDeclaration generateBuilderMethod(String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + private MethodDeclaration generateToBuilderMethod(String methodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, List<BuilderFieldData> builderFields, boolean fluent, ASTNode source) { + // return new ThingieBuilder<A, B>().setA(this.a).setB(this.b); + 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.selector = methodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic; 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)}; + + Expression receiver = invoke; + for (BuilderFieldData bfd : builderFields) { + char[] setterName = fluent ? bfd.name : HandlerUtil.buildAccessorName("set", new String(bfd.name)).toCharArray(); + MessageSend ms = new MessageSend(); + if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) { + char[] fieldName = bfd.obtainVia == null ? bfd.rawName : bfd.obtainVia.field().toCharArray(); + FieldReference fr = new FieldReference(fieldName, 0); + fr.receiver = new ThisReference(0, 0); + ms.arguments = new Expression[] {fr}; + } else { + String obtainName = bfd.obtainVia.method(); + boolean obtainIsStatic = bfd.obtainVia.isStatic(); + MessageSend obtainExpr = new MessageSend(); + obtainExpr.receiver = obtainIsStatic ? new SingleNameReference(type.getName().toCharArray(), 0) : new ThisReference(0, 0); + obtainExpr.selector = obtainName.toCharArray(); + if (obtainIsStatic) obtainExpr.arguments = new Expression[] {new ThisReference(0, 0)}; + ms.arguments = new Expression[] {obtainExpr}; + } + ms.receiver = receiver; + ms.selector = setterName; + receiver = ms; + } + + out.statements = new Statement[] {new ReturnStatement(receiver, pS, pE)}; out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) type.get()).scope); return out; + } - public 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; + private MethodDeclaration generateCleanMethod(List<BuilderFieldData> builderFields, EclipseNode builderType, ASTNode source) { + List<Statement> statements = new ArrayList<Statement>(); - MethodDeclaration out = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, builderType, statements); + } + } - out.modifiers = ClassFileConstants.AccPublic; - TypeDeclaration typeDecl = (TypeDeclaration) type.get(); - out.selector = name.toCharArray(); - out.thrownExceptions = copyTypes(thrownExceptions, source); + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + statements.add(new Assignment(thisUnclean, new FalseLiteral(0, 0), 0)); + MethodDeclaration decl = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + decl.selector = CLEAN_METHOD_NAME; + decl.modifiers = ClassFileConstants.AccPrivate; + decl.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + decl.returnType = TypeReference.baseTypeReference(TypeIds.T_void, 0); + decl.statements = statements.toArray(new Statement[0]); + decl.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return decl; + } + + public MethodDeclaration generateBuildMethod(EclipseNode tdParent, boolean isStatic, String name, char[] staticName, TypeReference returnType, List<BuilderFieldData> builderFields, EclipseNode type, TypeReference[] thrownExceptions, boolean addCleaning, ASTNode source) { + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - out.returnType = returnType; + List<Statement> statements = new ArrayList<Statement>(); + + if (addCleaning) { + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + Expression notClean = new UnaryExpression(thisUnclean, OperatorIds.NOT); + MessageSend invokeClean = new MessageSend(); + invokeClean.selector = CLEAN_METHOD_NAME; + statements.add(new IfStatement(notClean, invokeClean, 0, 0)); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, statements, bfd.name); + } + } + + List<Expression> args = new ArrayList<Expression>(); + for (BuilderFieldData bfd : builderFields) { + if (bfd.nameOfSetFlag != null) { + MessageSend inv = new MessageSend(); + inv.sourceStart = source.sourceStart; + inv.sourceEnd = source.sourceEnd; + inv.receiver = new SingleNameReference(((TypeDeclaration) tdParent.get()).name, 0L); + inv.selector = bfd.nameOfDefaultProvider; + inv.typeArguments = typeParameterNames(((TypeDeclaration) type.get()).typeParameters); + + args.add(new ConditionalExpression( + new SingleNameReference(bfd.nameOfSetFlag, 0L), + new SingleNameReference(bfd.name, 0L), + inv)); + } else { + args.add(new SingleNameReference(bfd.name, 0L)); + } + } - List<Expression> assigns = new ArrayList<Expression>(); - for (char[] fieldName : fieldNames) { - SingleNameReference nameRef = new SingleNameReference(fieldName, p); - assigns.add(nameRef); + if (addCleaning) { + FieldReference thisUnclean = new FieldReference(CLEAN_FIELD_NAME, 0); + thisUnclean.receiver = new ThisReference(0, 0); + statements.add(new Assignment(thisUnclean, new TrueLiteral(0, 0), 0)); } - Statement statement; + out.modifiers = ClassFileConstants.AccPublic; + out.selector = name.toCharArray(); + out.thrownExceptions = copyTypes(thrownExceptions); + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + out.returnType = returnType; 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); + allocationStatement.type = copyType(out.returnType); + allocationStatement.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); + statements.add(new ReturnStatement(allocationStatement, 0, 0)); } 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 (isStatic) + invoke.receiver = new SingleNameReference(type.up().getName().toCharArray(), 0); + else + invoke.receiver = new QualifiedThisReference(new SingleTypeReference(type.up().getName().toCharArray(), 0) , 0, 0); + + invoke.typeArguments = typeParameterNames(((TypeDeclaration) type.get()).typeParameters); + invoke.arguments = args.isEmpty() ? null : args.toArray(new Expression[args.size()]); if (returnType instanceof SingleTypeReference && Arrays.equals(TypeConstants.VOID, ((SingleTypeReference) returnType).token)) { - statement = invoke; + statements.add(invoke); } else { - statement = new ReturnStatement(invoke, (int)(p >> 32), (int)p); + statements.add(new ReturnStatement(invoke, 0, 0)); } } + out.statements = statements.isEmpty() ? null : statements.toArray(new Statement[statements.size()]); + out.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); + return out; + } + + private TypeReference[] typeParameterNames(TypeParameter[] typeParameters) { + if (typeParameters == null) return null; - out.statements = new Statement[] { statement }; + TypeReference[] trs = new TypeReference[typeParameters.length]; + for (int i = 0; i < trs.length; i++) { + trs[i] = new SingleTypeReference(typeParameters[i].name, 0); + } + return trs; + } + + public MethodDeclaration generateDefaultProvider(char[] methodName, TypeParameter[] typeParameters, EclipseNode fieldNode, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) fieldNode.top().get()).compilationResult); + out.typeParameters = copyTypeParams(typeParameters, source); + out.selector = methodName; + out.modifiers = ClassFileConstants.AccPrivate | ClassFileConstants.AccStatic; + out.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + FieldDeclaration fd = (FieldDeclaration) fieldNode.get(); + out.returnType = copyType(fd.type, source); + out.statements = new Statement[] {new ReturnStatement(fd.initialization, pS, pE)}; + fd.initialization = null; - out.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); + out.traverse(new SetGeneratedByVisitor(source), ((TypeDeclaration) fieldNode.up().get()).scope); return out; } - public 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)); - } + public MethodDeclaration generateBuilderMethod(boolean isStatic, String builderMethodName, String builderClassName, EclipseNode type, TypeParameter[] typeParams, ASTNode source) { + int pS = source.sourceStart, pE = source.sourceEnd; + long p = (long) pS << 32 | pE; - Collections.reverse(out); + MethodDeclaration out = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); + out.selector = builderMethodName.toCharArray(); + out.modifiers = ClassFileConstants.AccPublic; + if (isStatic) out.modifiers |= 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; } + public void generateBuilderFields(EclipseNode builderType, List<BuilderFieldData> builderFields, ASTNode source) { + List<EclipseNode> existing = new ArrayList<EclipseNode>(); + for (EclipseNode child : builderType.down()) { + if (child.getKind() == Kind.FIELD) existing.add(child); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType)); + } else { + EclipseNode field = null, setFlag = null; + for (EclipseNode exists : existing) { + char[] n = ((FieldDeclaration) exists.get()).name; + if (Arrays.equals(n, bfd.name)) field = exists; + if (bfd.nameOfSetFlag != null && Arrays.equals(n, bfd.nameOfSetFlag)) setFlag = exists; + } + + if (field == null) { + FieldDeclaration fd = new FieldDeclaration(bfd.name, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = copyType(bfd.type); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + field = injectFieldAndMarkGenerated(builderType, fd); + } + if (setFlag == null && bfd.nameOfSetFlag != null) { + FieldDeclaration fd = new FieldDeclaration(bfd.nameOfSetFlag, 0, 0); + fd.bits |= Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; + fd.modifiers = ClassFileConstants.AccPrivate; + fd.type = TypeReference.baseTypeReference(TypeIds.T_boolean, 0); + fd.traverse(new SetGeneratedByVisitor(source), (MethodScope) null); + injectFieldAndMarkGenerated(builderType, fd); + } + bfd.createdFields.add(field); + } + } + } + private static final AbstractMethodDeclaration[] EMPTY = {}; - public MethodDeclaration makeSetterMethodForBuilder(EclipseNode builderType, EclipseNode fieldNode, EclipseNode sourceNode, boolean fluent, boolean chain) { + public void makeSetterMethodsForBuilder(EclipseNode builderType, BuilderFieldData bfd, EclipseNode sourceNode, boolean fluent, boolean chain) { + boolean deprecate = isFieldDeprecated(bfd.originalFieldNode); + if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) { + makeSimpleSetterMethodForBuilder(builderType, deprecate, bfd.createdFields.get(0), bfd.nameOfSetFlag, sourceNode, fluent, chain); + } else { + bfd.singularData.getSingularizer().generateMethods(bfd.singularData, deprecate, builderType, fluent, chain); + } + } + + private void makeSimpleSetterMethodForBuilder(EclipseNode builderType, boolean deprecate, EclipseNode fieldNode, char[] nameOfSetFlag, EclipseNode sourceNode, boolean fluent, boolean chain) { TypeDeclaration td = (TypeDeclaration) builderType.get(); AbstractMethodDeclaration[] existing = td.methods; if (existing == null) existing = EMPTY; @@ -348,14 +736,14 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { 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; + if (Arrays.equals(name, existingName) && !isTolerate(fieldNode, existing[i])) return; } - boolean isBoolean = isBoolean(fd.type); - String setterName = fluent ? fieldNode.getName() : toSetterName(builderType.getAst(), null, fieldNode.getName(), isBoolean); + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - return HandleSetter.createSetter(td, fieldNode, setterName, chain, ClassFileConstants.AccPublic, - sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + MethodDeclaration setter = HandleSetter.createSetter(td, deprecate, fieldNode, setterName, nameOfSetFlag, chain, ClassFileConstants.AccPublic, + sourceNode, Collections.<Annotation>emptyList(), Collections.<Annotation>emptyList()); + injectMethod(builderType, setter); } public EclipseNode findInnerClass(EclipseNode parent, String name) { @@ -368,14 +756,84 @@ public class HandleBuilder extends EclipseAnnotationHandler<Builder> { return null; } - public EclipseNode makeBuilderClass(EclipseNode tdParent, String builderClassName, TypeParameter[] typeParams, ASTNode source) { + public EclipseNode makeBuilderClass(boolean isStatic, 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.modifiers |= ClassFileConstants.AccPublic; + if (isStatic) builder.modifiers |= ClassFileConstants.AccStatic; builder.typeParameters = copyTypeParams(typeParams, source); builder.name = builderClassName.toCharArray(); builder.traverse(new SetGeneratedByVisitor(source), (ClassScope) null); return injectType(tdParent, builder); } + + private void addObtainVia(BuilderFieldData bfd, EclipseNode node) { + for (EclipseNode child : node.down()) { + if (!annotationTypeMatches(ObtainVia.class, child)) continue; + AnnotationValues<ObtainVia> ann = createAnnotation(ObtainVia.class, child); + bfd.obtainVia = ann.getInstance(); + bfd.obtainViaNode = child; + return; + } + } + + /** + * Returns the explicitly requested singular annotation on this node (field + * or parameter), or null if there's no {@code @Singular} annotation on it. + * + * @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation. + */ + private SingularData getSingularData(EclipseNode node, ASTNode source) { + for (EclipseNode child : node.down()) { + if (!annotationTypeMatches(Singular.class, child)) continue; + char[] pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((AbstractVariableDeclaration) node.get()).name; + AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); + String explicitSingular = ann.getInstance().value(); + if (explicitSingular.isEmpty()) { + if (Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_AUTO))) { + node.addError("The singular must be specified explicitly (e.g. @Singular(\"task\")) because auto singularization is disabled."); + explicitSingular = new String(pluralName); + } else { + explicitSingular = autoSingularize(new String(pluralName)); + if (explicitSingular == null) { + node.addError("Can't singularize this name; please specify the singular explicitly (i.e. @Singular(\"sheep\"))"); + explicitSingular = new String(pluralName); + } + } + } + char[] singularName = explicitSingular.toCharArray(); + + TypeReference type = ((AbstractVariableDeclaration) node.get()).type; + TypeReference[] typeArgs = null; + String typeName; + if (type instanceof ParameterizedSingleTypeReference) { + typeArgs = ((ParameterizedSingleTypeReference) type).typeArguments; + typeName = new String(((ParameterizedSingleTypeReference) type).token); + } else if (type instanceof ParameterizedQualifiedTypeReference) { + TypeReference[][] tr = ((ParameterizedQualifiedTypeReference) type).typeArguments; + if (tr != null) typeArgs = tr[tr.length - 1]; + char[][] tokens = ((ParameterizedQualifiedTypeReference) type).tokens; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < tokens.length; i++) { + if (i > 0) sb.append("."); + sb.append(tokens[i]); + } + typeName = sb.toString(); + } else { + typeName = type.toString(); + } + + String targetFqn = EclipseSingularsRecipes.get().toQualified(typeName); + EclipseSingularizer singularizer = EclipseSingularsRecipes.get().getSingularizer(targetFqn); + if (singularizer == null) { + node.addError("Lombok does not know how to create the singular-form builder methods for type '" + typeName + "'; they won't be generated."); + return null; + } + + return new SingularData(child, singularName, pluralName, typeArgs == null ? Collections.<TypeReference>emptyList() : Arrays.asList(typeArgs), targetFqn, singularizer, source); + } + + return null; + } } diff --git a/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java new file mode 100644 index 00000000..be2b986d --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleBuilderDefault.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2017-2018 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 org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.mangosdk.spi.ProviderFor; + +import lombok.Builder; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; + +@ProviderFor(EclipseAnnotationHandler.class) +@HandlerPriority(-1025) //HandleBuilder's level, minus one. +public class HandleBuilderDefault extends EclipseAnnotationHandler<Builder.Default> { + @Override public void handle(AnnotationValues<Builder.Default> annotation, Annotation ast, EclipseNode annotationNode) { + EclipseNode annotatedField = annotationNode.up(); + if (annotatedField.getKind() != Kind.FIELD) return; + EclipseNode classWithAnnotatedField = annotatedField.up(); + if (!hasAnnotation(Builder.class, classWithAnnotatedField) && !hasAnnotation("lombok.experimental.Builder", classWithAnnotatedField)) { + annotationNode.addWarning("@Builder.Default requires @Builder on the class for it to mean anything."); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleConstructor.java b/src/core/lombok/eclipse/handlers/HandleConstructor.java index b72d000f..62e2c18c 100644 --- a/src/core/lombok/eclipse/handlers/HandleConstructor.java +++ b/src/core/lombok/eclipse/handlers/HandleConstructor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2017 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,10 +29,12 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.ConfigurationKeys; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; @@ -40,21 +42,28 @@ import lombok.core.AST.Kind; import lombok.core.AnnotationValues; 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; 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.ArrayTypeReference; import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.CharLiteral; import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; import org.eclipse.jdt.internal.compiler.ast.ConstructorDeclaration; +import org.eclipse.jdt.internal.compiler.ast.DoubleLiteral; import org.eclipse.jdt.internal.compiler.ast.ExplicitConstructorCall; import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FalseLiteral; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.FloatLiteral; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LongLiteral; import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleMemberAnnotation; @@ -63,12 +72,16 @@ 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.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; import org.mangosdk.spi.ProviderFor; public class HandleConstructor { @ProviderFor(EclipseAnnotationHandler.class) public static class HandleNoArgsConstructor extends EclipseAnnotationHandler<NoArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<NoArgsConstructor> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.NO_ARGS_CONSTRUCTOR_FLAG_USAGE, "@NoArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -78,16 +91,20 @@ public class HandleConstructor { AccessLevel level = ann.access(); String staticName = ann.staticName(); if (level == AccessLevel.NONE) return; - List<EclipseNode> fields = new ArrayList<EclipseNode>(); - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor=", annotationNode); + boolean force = ann.force(); + + List<EclipseNode> fields = force ? findFinalFields(typeNode) : Collections.<EclipseNode>emptyList(); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@NoArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor(typeNode, level, fields, staticName, SkipIfConstructorExists.NO, null, onConstructor, annotationNode); + handleConstructor.generateConstructor(typeNode, level, fields, force, staticName, SkipIfConstructorExists.NO, onConstructor, annotationNode); } } @ProviderFor(EclipseAnnotationHandler.class) public static class HandleRequiredArgsConstructor extends EclipseAnnotationHandler<RequiredArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<RequiredArgsConstructor> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.REQUIRED_ARGS_CONSTRUCTOR_FLAG_USAGE, "@RequiredArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -97,43 +114,51 @@ public class HandleConstructor { AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - Boolean suppressConstructorProperties = null; if (annotation.isExplicit("suppressConstructorProperties")) { - @SuppressWarnings("deprecation") - boolean suppress = ann.suppressConstructorProperties(); - suppressConstructorProperties = suppress; + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); } - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor=", annotationNode); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@RequiredArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor( - typeNode, level, findRequiredFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + handleConstructor.generateConstructor( + typeNode, level, findRequiredFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + onConstructor, annotationNode); } } private static List<EclipseNode> findRequiredFields(EclipseNode typeNode) { + return findFields(typeNode, true); + } + + private static List<EclipseNode> findFinalFields(EclipseNode typeNode) { + return findFields(typeNode, false); + } + + private static List<EclipseNode> findFields(EclipseNode typeNode, boolean nullMarked) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); if (!filterField(fieldDecl)) continue; boolean isFinal = (fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0; - boolean isNonNull = findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; + boolean isNonNull = nullMarked && findAnnotations(fieldDecl, NON_NULL_PATTERN).length != 0; if ((isFinal || isNonNull) && fieldDecl.initialization == null) fields.add(child); } return fields; } static List<EclipseNode> findAllFields(EclipseNode typeNode) { + return findAllFields(typeNode, false); + } + + static List<EclipseNode> findAllFields(EclipseNode typeNode, boolean evenFinalInitialized) { List<EclipseNode> fields = new ArrayList<EclipseNode>(); for (EclipseNode child : typeNode.down()) { if (child.getKind() != Kind.FIELD) continue; FieldDeclaration fieldDecl = (FieldDeclaration) child.get(); if (!filterField(fieldDecl)) continue; - // Skip initialized final fields. - if (((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) && fieldDecl.initialization != null) continue; + if (!evenFinalInitialized && ((fieldDecl.modifiers & ClassFileConstants.AccFinal) != 0) && fieldDecl.initialization != null) continue; fields.add(child); } @@ -142,6 +167,8 @@ public class HandleConstructor { @ProviderFor(EclipseAnnotationHandler.class) public static class HandleAllArgsConstructor extends EclipseAnnotationHandler<AllArgsConstructor> { + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<AllArgsConstructor> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.ALL_ARGS_CONSTRUCTOR_FLAG_USAGE, "@AllArgsConstructor", ConfigurationKeys.ANY_CONSTRUCTOR_FLAG_USAGE, "any @xArgsConstructor"); @@ -151,18 +178,15 @@ public class HandleConstructor { AccessLevel level = ann.access(); if (level == AccessLevel.NONE) return; String staticName = ann.staticName(); - Boolean suppressConstructorProperties = null; if (annotation.isExplicit("suppressConstructorProperties")) { - @SuppressWarnings("deprecation") - boolean suppress = ann.suppressConstructorProperties(); - suppressConstructorProperties = suppress; + annotationNode.addError("This deprecated feature is no longer supported. Remove it; you can create a lombok.config file with 'lombok.anyConstructor.suppressConstructorProperties = true'."); } - List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor=", annotationNode); + List<Annotation> onConstructor = unboxAndRemoveAnnotationParameter(ast, "onConstructor", "@AllArgsConstructor(onConstructor", annotationNode); - new HandleConstructor().generateConstructor( - typeNode, level, findAllFields(typeNode), staticName, SkipIfConstructorExists.NO, - suppressConstructorProperties, onConstructor, annotationNode); + handleConstructor.generateConstructor( + typeNode, level, findAllFields(typeNode), false, staticName, SkipIfConstructorExists.NO, + onConstructor, annotationNode); } } @@ -184,14 +208,14 @@ public class HandleConstructor { EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findRequiredFields(typeNode), staticName, skipIfConstructorExists, null, onConstructor, sourceNode); + generateConstructor(typeNode, level, findRequiredFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode); } public void generateAllArgsConstructor( EclipseNode typeNode, AccessLevel level, String staticName, SkipIfConstructorExists skipIfConstructorExists, List<Annotation> onConstructor, EclipseNode sourceNode) { - generateConstructor(typeNode, level, findAllFields(typeNode), staticName, skipIfConstructorExists, null, onConstructor, sourceNode); + generateConstructor(typeNode, level, findAllFields(typeNode), false, staticName, skipIfConstructorExists, onConstructor, sourceNode); } public enum SkipIfConstructorExists { @@ -199,8 +223,8 @@ public class HandleConstructor { } public void generateConstructor( - EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, String staticName, SkipIfConstructorExists skipIfConstructorExists, - Boolean suppressConstructorProperties, List<Annotation> onConstructor, EclipseNode sourceNode) { + EclipseNode typeNode, AccessLevel level, List<EclipseNode> fields, boolean allToDefault, String staticName, SkipIfConstructorExists skipIfConstructorExists, + List<Annotation> onConstructor, EclipseNode sourceNode) { ASTNode source = sourceNode.get(); boolean staticConstrRequired = staticName != null && !staticName.equals(""); @@ -210,8 +234,8 @@ public class HandleConstructor { for (EclipseNode child : typeNode.down()) { if (child.getKind() == Kind.ANNOTATION) { boolean skipGeneration = (annotationTypeMatches(NoArgsConstructor.class, child) || - annotationTypeMatches(AllArgsConstructor.class, child) || - annotationTypeMatches(RequiredArgsConstructor.class, child)); + annotationTypeMatches(AllArgsConstructor.class, child) || + annotationTypeMatches(RequiredArgsConstructor.class, child)); if (!skipGeneration && skipIfConstructorExists == SkipIfConstructorExists.YES) { skipGeneration = annotationTypeMatches(Builder.class, child); @@ -224,8 +248,8 @@ public class HandleConstructor { // the 'staticName' parameter of the @XArgsConstructor you've stuck on your type. // We should warn that we're ignoring @Data's 'staticConstructor' param. typeNode.addWarning( - "Ignoring static constructor name: explicit @XxxArgsConstructor annotation present; its `staticName` parameter will be used.", - source.sourceStart, source.sourceEnd); + "Ignoring static constructor name: explicit @XxxArgsConstructor annotation present; its `staticName` parameter will be used.", + source.sourceStart, source.sourceEnd); } return; } @@ -234,11 +258,11 @@ public class HandleConstructor { } ConstructorDeclaration constr = createConstructor( - staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, - suppressConstructorProperties, sourceNode, onConstructor); + staticConstrRequired ? AccessLevel.PRIVATE : level, typeNode, fields, allToDefault, + sourceNode, onConstructor); injectMethod(typeNode, constr); if (staticConstrRequired) { - MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, fields, source); + MethodDeclaration staticConstr = createStaticConstructor(level, staticName, typeNode, allToDefault ? Collections.<EclipseNode>emptyList() : fields, source); injectMethod(typeNode, staticConstr); } } @@ -248,7 +272,7 @@ public class HandleConstructor { if (fields.isEmpty()) return null; int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; long[] poss = new long[3]; Arrays.fill(poss, p); QualifiedTypeReference constructorPropertiesType = new QualifiedTypeReference(JAVA_BEANS_CONSTRUCTORPROPERTIES, poss); @@ -275,28 +299,28 @@ public class HandleConstructor { return new Annotation[] { ann }; } - public static ConstructorDeclaration createConstructor( - AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, - Boolean suppressConstructorProperties, EclipseNode sourceNode, List<Annotation> onConstructor) { + @SuppressWarnings("deprecation") public static ConstructorDeclaration createConstructor( + AccessLevel level, EclipseNode type, Collection<EclipseNode> fields, boolean allToDefault, + EclipseNode sourceNode, List<Annotation> onConstructor) { ASTNode source = sourceNode.get(); - TypeDeclaration typeDeclaration = ((TypeDeclaration)type.get()); - long p = (long)source.sourceStart << 32 | source.sourceEnd; + TypeDeclaration typeDeclaration = ((TypeDeclaration) type.get()); + long p = (long) source.sourceStart << 32 | source.sourceEnd; - boolean isEnum = (((TypeDeclaration)type.get()).modifiers & ClassFileConstants.AccEnum) != 0; + boolean isEnum = (((TypeDeclaration) type.get()).modifiers & ClassFileConstants.AccEnum) != 0; if (isEnum) level = AccessLevel.PRIVATE; - if (suppressConstructorProperties == null) { - if (fields.isEmpty()) { - suppressConstructorProperties = false; - } else { - suppressConstructorProperties = Boolean.TRUE.equals(type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); - } + boolean addConstructorProperties; + if (fields.isEmpty()) { + addConstructorProperties = false; + } else { + Boolean v = type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_ADD_CONSTRUCTOR_PROPERTIES); + addConstructorProperties = v != null ? v.booleanValue() : + Boolean.FALSE.equals(type.getAst().readConfiguration(ConfigurationKeys.ANY_CONSTRUCTOR_SUPPRESS_CONSTRUCTOR_PROPERTIES)); } - ConstructorDeclaration constructor = new ConstructorDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level); constructor.selector = typeDeclaration.name; @@ -319,23 +343,27 @@ public class HandleConstructor { char[] rawName = field.name; char[] fieldName = removePrefixFromField(fieldNode); FieldReference thisX = new FieldReference(rawName, p); - thisX.receiver = new ThisReference((int)(p >> 32), (int)p); + int s = (int) (p >> 32); + int e = (int) p; + thisX.receiver = new ThisReference(s, e); + + Expression assignmentExpr = allToDefault ? getDefaultExpr(field.type, s, e) : new SingleNameReference(fieldName, p); - SingleNameReference assignmentNameRef = new SingleNameReference(fieldName, p); - Assignment assignment = new Assignment(thisX, assignmentNameRef, (int)p); - assignment.sourceStart = (int)(p >> 32); assignment.sourceEnd = assignment.statementEnd = (int)(p >> 32); + Assignment assignment = new Assignment(thisX, assignmentExpr, (int) p); + assignment.sourceStart = (int) (p >> 32); assignment.sourceEnd = assignment.statementEnd = (int) (p >> 32); assigns.add(assignment); - long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; - Argument parameter = new Argument(fieldName, fieldPos, copyType(field.type, source), Modifier.FINAL); - Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); - Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); - if (nonNulls.length != 0) { - Statement nullCheck = generateNullCheck(field, sourceNode); - if (nullCheck != null) nullChecks.add(nullCheck); + if (!allToDefault) { + long fieldPos = (((long) field.sourceStart) << 32) | field.sourceEnd; + Argument parameter = new Argument(fieldName, fieldPos, copyType(field.type, source), Modifier.FINAL); + Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); + Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); + if (nonNulls.length != 0) { + Statement nullCheck = generateNullCheck(parameter, sourceNode); + if (nullCheck != null) nullChecks.add(nullCheck); + } + parameter.annotations = copyAnnotations(source, nonNulls, nullables); + params.add(parameter); } - Annotation[] copiedAnnotations = copyAnnotations(source, nonNulls, nullables); - if (copiedAnnotations.length != 0) parameter.annotations = copiedAnnotations; - params.add(parameter); } nullChecks.addAll(assigns); @@ -344,20 +372,34 @@ public class HandleConstructor { /* Generate annotations that must be put on the generated method, and attach them. */ { Annotation[] constructorProperties = null; - if (!suppressConstructorProperties && level != AccessLevel.PRIVATE && level != AccessLevel.PACKAGE && !isLocalType(type)) { + if (!allToDefault && addConstructorProperties && !isLocalType(type)) { constructorProperties = createConstructorProperties(source, fields); } - Annotation[] copiedAnnotations = copyAnnotations(source, - onConstructor.toArray(new Annotation[0]), - constructorProperties); - if (copiedAnnotations.length != 0) constructor.annotations = copiedAnnotations; + constructor.annotations = copyAnnotations(source, + onConstructor.toArray(new Annotation[0]), + constructorProperties); } constructor.traverse(new SetGeneratedByVisitor(source), typeDeclaration.scope); return constructor; } + private static Expression getDefaultExpr(TypeReference type, int s, int e) { + boolean array = type instanceof ArrayTypeReference; + if (array) return new NullLiteral(s, e); + char[] lastToken = type.getLastToken(); + if (Arrays.equals(TypeConstants.BOOLEAN, lastToken)) return new FalseLiteral(s, e); + if (Arrays.equals(TypeConstants.CHAR, lastToken)) return new CharLiteral(new char[] {'\'', '\\', '0', '\''}, s, e); + if (Arrays.equals(TypeConstants.BYTE, lastToken) || Arrays.equals(TypeConstants.SHORT, lastToken) || + Arrays.equals(TypeConstants.INT, lastToken)) return IntLiteral.buildIntLiteral(new char[] {'0'}, s, e); + if (Arrays.equals(TypeConstants.LONG, lastToken)) return LongLiteral.buildLongLiteral(new char[] {'0', 'L'}, s, e); + if (Arrays.equals(TypeConstants.FLOAT, lastToken)) return new FloatLiteral(new char[] {'0', 'F'}, s, e); + if (Arrays.equals(TypeConstants.DOUBLE, lastToken)) return new DoubleLiteral(new char[] {'0', 'D'}, s, e); + + return new NullLiteral(s, e); + } + public static boolean isLocalType(EclipseNode type) { Kind kind = type.up().getKind(); if (kind == Kind.COMPILATION_UNIT) return false; @@ -367,10 +409,9 @@ public class HandleConstructor { public MethodDeclaration createStaticConstructor(AccessLevel level, String name, EclipseNode type, Collection<EclipseNode> fields, ASTNode source) { int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; - MethodDeclaration constructor = new MethodDeclaration( - ((CompilationUnitDeclaration) type.top().get()).compilationResult); + MethodDeclaration constructor = new MethodDeclaration(((CompilationUnitDeclaration) type.top().get()).compilationResult); constructor.modifiers = toEclipseModifier(level) | ClassFileConstants.AccStatic; TypeDeclaration typeDecl = (TypeDeclaration) type.get(); @@ -378,7 +419,7 @@ public class HandleConstructor { constructor.annotations = null; constructor.selector = name.toCharArray(); constructor.thrownExceptions = null; - constructor.typeParameters = copyTypeParams(((TypeDeclaration)type.get()).typeParameters, source); + constructor.typeParameters = copyTypeParams(((TypeDeclaration) type.get()).typeParameters, source); constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; @@ -391,20 +432,18 @@ public class HandleConstructor { for (EclipseNode fieldNode : fields) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); - long fieldPos = (((long)field.sourceStart) << 32) | field.sourceEnd; + long fieldPos = (((long) field.sourceStart) << 32) | field.sourceEnd; SingleNameReference nameRef = new SingleNameReference(field.name, fieldPos); assigns.add(nameRef); Argument parameter = new Argument(field.name, fieldPos, copyType(field.type, source), Modifier.FINAL); - - Annotation[] copiedAnnotations = copyAnnotations(source, findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); - if (copiedAnnotations.length != 0) parameter.annotations = copiedAnnotations; + parameter.annotations = copyAnnotations(source, findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN)); params.add(parameter); } statement.arguments = assigns.isEmpty() ? null : assigns.toArray(new Expression[assigns.size()]); constructor.arguments = params.isEmpty() ? null : params.toArray(new Argument[params.size()]); - constructor.statements = new Statement[] { new ReturnStatement(statement, (int)(p >> 32), (int)p) }; + constructor.statements = new Statement[] { new ReturnStatement(statement, (int) (p >> 32), (int)p) }; constructor.traverse(new SetGeneratedByVisitor(source), typeDecl.scope); return constructor; diff --git a/src/core/lombok/eclipse/handlers/HandleData.java b/src/core/lombok/eclipse/handlers/HandleData.java index 0ff65a47..025ceefd 100644 --- a/src/core/lombok/eclipse/handlers/HandleData.java +++ b/src/core/lombok/eclipse/handlers/HandleData.java @@ -43,6 +43,12 @@ import org.mangosdk.spi.ProviderFor; */ @ProviderFor(EclipseAnnotationHandler.class) public class HandleData extends EclipseAnnotationHandler<Data> { + private HandleGetter handleGetter = new HandleGetter(); + private HandleSetter handleSetter = new HandleSetter(); + private HandleEqualsAndHashCode handleEqualsAndHashCode = new HandleEqualsAndHashCode(); + private HandleToString handleToString = new HandleToString(); + private HandleConstructor handleConstructor = new HandleConstructor(); + @Override public void handle(AnnotationValues<Data> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.DATA_FLAG_USAGE, "@Data"); @@ -66,11 +72,11 @@ public class HandleData extends EclipseAnnotationHandler<Data> { //for whatever reason, though you can find callers of that one by focusing on the class name itself //and hitting 'find callers'. - new HandleGetter().generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleSetter().generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); - new HandleEqualsAndHashCode().generateEqualsAndHashCodeForType(typeNode, annotationNode); - new HandleToString().generateToStringForType(typeNode, annotationNode); - new HandleConstructor().generateRequiredArgsConstructor( + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + handleSetter.generateSetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); + handleConstructor.generateRequiredArgsConstructor( typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), annotationNode); } diff --git a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java index e1b02051..75339f7c 100644 --- a/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java +++ b/src/core/lombok/eclipse/handlers/HandleEqualsAndHashCode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2015 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -39,6 +39,7 @@ import lombok.EqualsAndHashCode; import lombok.core.AST.Kind; import lombok.core.handlers.HandlerUtil; import lombok.core.AnnotationValues; +import lombok.core.configuration.CallSuperType; import lombok.eclipse.Eclipse; import lombok.eclipse.EclipseAnnotationHandler; import lombok.eclipse.EclipseNode; @@ -66,6 +67,7 @@ import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; import org.eclipse.jdt.internal.compiler.ast.NameReference; import org.eclipse.jdt.internal.compiler.ast.NullLiteral; import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.ParameterizedQualifiedTypeReference; import org.eclipse.jdt.internal.compiler.ast.ParameterizedSingleTypeReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; @@ -116,7 +118,10 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return; } - generateMethods(typeNode, errorNode, null, null, null, false, FieldAccess.GETTER, new ArrayList<Annotation>()); + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + generateMethods(typeNode, errorNode, null, null, null, false, access, new ArrayList<Annotation>()); } @Override public void handle(AnnotationValues<EqualsAndHashCode> annotation, Annotation ast, EclipseNode annotationNode) { @@ -127,7 +132,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH List<String> includes = Arrays.asList(ann.of()); EclipseNode typeNode = annotationNode.up(); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam=", annotationNode); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@EqualsAndHashCode(onParam", annotationNode); checkForBogusFieldNames(typeNode, annotation); Boolean callSuper = ann.callSuper(); @@ -185,8 +190,23 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return; } - if (!isDirectDescendantOfObject && !callSuper && implicitCallSuper) { - errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + if (implicitCallSuper && !isDirectDescendantOfObject) { + CallSuperType cst = typeNode.getAst().readConfiguration(ConfigurationKeys.EQUALS_AND_HASH_CODE_CALL_SUPER); + if (cst == null) cst = CallSuperType.WARN; + + switch (cst) { + default: + case WARN: + errorNode.addWarning("Generating equals/hashCode implementation but without a call to superclass, even though this class does not extend java.lang.Object. If this is intentional, add '@EqualsAndHashCode(callSuper=false)' to your type."); + callSuper = false; + break; + case SKIP: + callSuper = false; + break; + case CALL: + callSuper = true; + break; + } } List<EclipseNode> nodesForEquality = new ArrayList<EclipseNode>(); @@ -279,7 +299,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH /* final int PRIME = X; */ { /* Without fields, PRIME isn't used, and that would trigger a 'local variable not used' warning. */ - if (!isEmpty || callSuper) { + if (!isEmpty) { LocalDeclaration primeDecl = new LocalDeclaration(PRIME, pS, pE); setGeneratedBy(primeDecl, source); primeDecl.modifiers |= Modifier.FINAL; @@ -291,26 +311,30 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } } - /* int result = 1; */ { - LocalDeclaration resultDecl = new LocalDeclaration(RESULT, pS, pE); + /*int result = ... */{ + LocalDeclaration resultDecl = new LocalDeclaration(RESULT, pS, pE); setGeneratedBy(resultDecl, source); - resultDecl.initialization = makeIntLiteral("1".toCharArray(), source); + final Expression init; + if (callSuper) { + /* ... super.hashCode(); */ + MessageSend callToSuper = new MessageSend(); + setGeneratedBy(callToSuper, source); + callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; + callToSuper.receiver = new SuperReference(pS, pE); + setGeneratedBy(callToSuper.receiver, source); + callToSuper.selector = "hashCode".toCharArray(); + init = callToSuper; + } else { + /* ... 1; */ + init = makeIntLiteral("1".toCharArray(), source); + } + resultDecl.initialization = init; resultDecl.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); resultDecl.type.sourceStart = pS; resultDecl.type.sourceEnd = pE; setGeneratedBy(resultDecl.type, source); statements.add(resultDecl); } - if (callSuper) { - MessageSend callToSuper = new MessageSend(); - setGeneratedBy(callToSuper, source); - callToSuper.sourceStart = pS; callToSuper.sourceEnd = pE; - callToSuper.receiver = new SuperReference(pS, pE); - setGeneratedBy(callToSuper.receiver, source); - callToSuper.selector = "hashCode".toCharArray(); - statements.add(createResultCalculation(source, callToSuper)); - } - for (EclipseNode field : fields) { TypeReference fType = getFieldType(field, fieldAccess); char[] dollarFieldName = ("$" + field.getName()).toCharArray(); @@ -358,7 +382,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH statements.add(createResultCalculation(source, fieldAccessor)); } else /* objects */ { /* final java.lang.Object $fieldName = this.fieldName; */ - /* $fieldName == null ? 0 : $fieldName.hashCode() */ + /* $fieldName == null ? NULL_PRIME : $fieldName.hashCode() */ statements.add(createLocalDeclaration(source, dollarFieldName, generateQualifiedTypeRef(source, TypeConstants.JAVA_LANG_OBJECT), fieldAccessor)); SingleNameReference copy1 = new SingleNameReference(dollarFieldName, p); @@ -375,8 +399,8 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH setGeneratedBy(nullLiteral, source); EqualExpression objIsNull = new EqualExpression(copy2, nullLiteral, OperatorIds.EQUAL_EQUAL); setGeneratedBy(objIsNull, source); - IntLiteral int0 = makeIntLiteral("0".toCharArray(), source); - ConditionalExpression nullOrHashCode = new ConditionalExpression(objIsNull, int0, hashCodeCall); + IntLiteral intMagic = makeIntLiteral(String.valueOf(HandlerUtil.primeForNull()).toCharArray(), source); + ConditionalExpression nullOrHashCode = new ConditionalExpression(objIsNull, intMagic, hashCodeCall); nullOrHashCode.sourceStart = pS; nullOrHashCode.sourceEnd = pE; setGeneratedBy(nullOrHashCode, source); statements.add(createResultCalculation(source, nullOrHashCode)); @@ -442,17 +466,44 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH return assignment; } - public TypeReference createTypeReference(EclipseNode type, long p) { + /** + * @param type Type to 'copy' into a typeref + * @param p position + * @param addWildcards If false, all generics are cut off. If true, replaces all genericparams with a ?. + * @return + */ + public TypeReference createTypeReference(EclipseNode type, long p, ASTNode source, boolean addWildcards) { + int pS = source.sourceStart; int pE = source.sourceEnd; List<String> list = new ArrayList<String>(); + List<Integer> genericsCount = addWildcards ? new ArrayList<Integer>() : null; + list.add(type.getName()); + if (addWildcards) genericsCount.add(arraySizeOf(((TypeDeclaration) type.get()).typeParameters)); + boolean staticContext = (((TypeDeclaration) type.get()).modifiers & Modifier.STATIC) != 0; EclipseNode tNode = type.up(); + while (tNode != null && tNode.getKind() == Kind.TYPE) { list.add(tNode.getName()); + if (addWildcards) genericsCount.add(staticContext ? 0 : arraySizeOf(((TypeDeclaration) tNode.get()).typeParameters)); + if (!staticContext) staticContext = (((TypeDeclaration) tNode.get()).modifiers & Modifier.STATIC) != 0; tNode = tNode.up(); } Collections.reverse(list); + if (addWildcards) Collections.reverse(genericsCount); + + if (list.size() == 1) { + if (!addWildcards || genericsCount.get(0) == 0) { + return new SingleTypeReference(list.get(0).toCharArray(), p); + } else { + return new ParameterizedSingleTypeReference(list.get(0).toCharArray(), wildcardify(pS, pE, source, genericsCount.get(0)), 0, p); + } + } + + if (addWildcards) { + addWildcards = false; + for (int i : genericsCount) if (i > 0) addWildcards = true; + } - if (list.size() == 1) return new SingleTypeReference(list.get(0).toCharArray(), p); long[] ps = new long[list.size()]; char[][] tokens = new char[list.size()][]; for (int i = 0; i < list.size(); i++) { @@ -460,13 +511,31 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH tokens[i] = list.get(i).toCharArray(); } - return new QualifiedTypeReference(tokens, ps); + if (!addWildcards) return new QualifiedTypeReference(tokens, ps); + TypeReference[][] typeArgs2 = new TypeReference[tokens.length][]; + for (int i = 0; i < tokens.length; i++) typeArgs2[i] = wildcardify(pS, pE, source, genericsCount.get(i)); + return new ParameterizedQualifiedTypeReference(tokens, typeArgs2, 0, ps); + } + + private TypeReference[] wildcardify(int pS, int pE, ASTNode source, int count) { + if (count == 0) return null; + TypeReference[] typeArgs = new TypeReference[count]; + for (int i = 0; i < count; i++) { + typeArgs[i] = new Wildcard(Wildcard.UNBOUND); + typeArgs[i].sourceStart = pS; typeArgs[i].sourceEnd = pE; + setGeneratedBy(typeArgs[i], source); + } + + return typeArgs; + } + + private int arraySizeOf(Object[] arr) { + return arr == null ? 0 : arr.length; } public MethodDeclaration createEquals(EclipseNode type, Collection<EclipseNode> fields, boolean callSuper, ASTNode source, FieldAccess fieldAccess, boolean needsCanEqual, List<Annotation> onParam) { int pS = source.sourceStart; int pE = source.sourceEnd; long p = (long)pS << 32 | pE; - TypeDeclaration typeDecl = (TypeDeclaration)type.get(); MethodDeclaration method = new MethodDeclaration( ((CompilationUnitDeclaration) type.top().get()).compilationResult); @@ -512,7 +581,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH SingleNameReference oRef = new SingleNameReference(new char[] { 'o' }, p); setGeneratedBy(oRef, source); - TypeReference typeReference = createTypeReference(type, p); + TypeReference typeReference = createTypeReference(type, p, source, false); setGeneratedBy(typeReference, source); InstanceOfExpression instanceOf = new InstanceOfExpression(oRef, typeReference); @@ -535,33 +604,17 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH char[] otherName = "other".toCharArray(); - /* MyType<?> other = (MyType<?>) o; */ { + /* Outer.Inner.MyType<?> other = (Outer.Inner.MyType<?>) o; */ { if (!fields.isEmpty() || needsCanEqual) { LocalDeclaration other = new LocalDeclaration(otherName, pS, pE); other.modifiers |= ClassFileConstants.AccFinal; setGeneratedBy(other, source); - char[] typeName = typeDecl.name; - TypeReference targetType; - if (typeDecl.typeParameters == null || typeDecl.typeParameters.length == 0) { - targetType = new SingleTypeReference(typeName, p); - setGeneratedBy(targetType, source); - other.type = new SingleTypeReference(typeName, p); - setGeneratedBy(other.type, source); - } else { - TypeReference[] typeArgs = new TypeReference[typeDecl.typeParameters.length]; - for (int i = 0; i < typeArgs.length; i++) { - typeArgs[i] = new Wildcard(Wildcard.UNBOUND); - typeArgs[i].sourceStart = pS; typeArgs[i].sourceEnd = pE; - setGeneratedBy(typeArgs[i], source); - } - targetType = new ParameterizedSingleTypeReference(typeName, typeArgs, 0, p); - setGeneratedBy(targetType, source); - other.type = new ParameterizedSingleTypeReference(typeName, copyTypes(typeArgs, source), 0, p); - setGeneratedBy(other.type, source); - } + TypeReference targetType = createTypeReference(type, p, source, true); + setGeneratedBy(targetType, source); + other.type = createTypeReference(type, p, source, true); + setGeneratedBy(other.type, source); NameReference oRef = new SingleNameReference(new char[] { 'o' }, p); setGeneratedBy(oRef, source); - other.annotations = createSuppressWarningsAll(source, null); other.initialization = makeCastExpression(oRef, targetType, source); statements.add(other); } @@ -645,7 +698,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH } else /* objects */ { /* final java.lang.Object this$fieldName = this.fieldName; */ /* final java.lang.Object other$fieldName = other.fieldName; */ - /* if (this$fieldName == null ? other$fieldName != null : !this$fieldName.equals(other$fieldName)) return false;; */ + /* if (this$fieldName == null ? other$fieldName != null : !this$fieldName.equals(other$fieldName)) return false; */ char[] thisDollarFieldName = ("this$" + field.getName()).toCharArray(); char[] otherDollarFieldName = ("other$" + field.getName()).toCharArray(); @@ -725,7 +778,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH public MethodDeclaration createCanEqual(EclipseNode type, ASTNode source, List<Annotation> onParam) { - /* public boolean canEqual(final java.lang.Object other) { + /* protected boolean canEqual(final java.lang.Object other) { * return other instanceof Outer.Inner.MyType; * } */ @@ -757,7 +810,7 @@ public class HandleEqualsAndHashCode extends EclipseAnnotationHandler<EqualsAndH SingleNameReference otherRef = new SingleNameReference(otherName, p); setGeneratedBy(otherRef, source); - TypeReference typeReference = createTypeReference(type, p); + TypeReference typeReference = createTypeReference(type, p, source, false); setGeneratedBy(typeReference, source); InstanceOfExpression instanceOf = new InstanceOfExpression(otherRef, typeReference); diff --git a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java index 7d0702db..702713fe 100644 --- a/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java +++ b/src/core/lombok/eclipse/handlers/HandleFieldDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2014 The Project Lombok Authors. + * Copyright (C) 2012-2016 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 @@ -23,12 +23,17 @@ package lombok.eclipse.handlers; import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.Arrays; + import lombok.AccessLevel; import lombok.ConfigurationKeys; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseASTAdapter; +import lombok.eclipse.EclipseASTVisitor; import lombok.eclipse.EclipseNode; import lombok.experimental.FieldDefaults; import lombok.experimental.NonFinal; @@ -37,16 +42,19 @@ import lombok.experimental.PackagePrivate; import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.Annotation; import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleTypeReference; 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.mangosdk.spi.ProviderFor; /** * Handles the {@code lombok.FieldDefaults} annotation for eclipse. */ -@ProviderFor(EclipseAnnotationHandler.class) +@ProviderFor(EclipseASTVisitor.class) @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 class HandleFieldDefaults extends EclipseASTAdapter { public boolean generateFieldDefaultsForType(EclipseNode typeNode, EclipseNode pos, AccessLevel level, boolean makeFinal, boolean checkForTypeLevelFieldDefaults) { if (checkForTypeLevelFieldDefaults) { if (hasAnnotation(FieldDefaults.class, typeNode)) { @@ -89,43 +97,78 @@ public class HandleFieldDefaults extends EclipseAnnotationHandler<FieldDefaults> if (level != null && level != AccessLevel.NONE) { if ((field.modifiers & (ClassFileConstants.AccPublic | ClassFileConstants.AccPrivate | ClassFileConstants.AccProtected)) == 0) { if (!hasAnnotation(PackagePrivate.class, fieldNode)) { - field.modifiers |= EclipseHandlerUtil.toEclipseModifier(level); + if ((field.modifiers & ClassFileConstants.AccStatic) == 0) { + field.modifiers |= EclipseHandlerUtil.toEclipseModifier(level); + } } } } if (makeFinal && (field.modifiers & ClassFileConstants.AccFinal) == 0) { if (!hasAnnotation(NonFinal.class, fieldNode)) { - field.modifiers |= ClassFileConstants.AccFinal; + if ((field.modifiers & ClassFileConstants.AccStatic) == 0) { + field.modifiers |= ClassFileConstants.AccFinal; + } } } fieldNode.rebuild(); } - public void handle(AnnotationValues<FieldDefaults> annotation, Annotation ast, EclipseNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults"); - - EclipseNode node = annotationNode.up(); - FieldDefaults instance = annotation.getInstance(); - AccessLevel level = instance.level(); - boolean makeFinal = instance.makeFinal(); + private static final char[] FIELD_DEFAULTS = "FieldDefaults".toCharArray(); + + @Override public void visitType(EclipseNode typeNode, TypeDeclaration type) { + AnnotationValues<FieldDefaults> fieldDefaults = null; + EclipseNode source = typeNode; - if (level == AccessLevel.NONE && !makeFinal) { - annotationNode.addError("This does nothing; provide either level or makeFinal or both."); - return; + boolean levelIsExplicit = false; + boolean makeFinalIsExplicit = false; + FieldDefaults fd = null; + for (EclipseNode jn : typeNode.down()) { + if (jn.getKind() != Kind.ANNOTATION) continue; + Annotation ann = (Annotation) jn.get(); + TypeReference typeTree = ann.type; + if (typeTree == null) continue; + if (typeTree instanceof SingleTypeReference) { + char[] t = ((SingleTypeReference) typeTree).token; + if (!Arrays.equals(t, FIELD_DEFAULTS)) continue; + } else if (typeTree instanceof QualifiedTypeReference) { + char[][] t = ((QualifiedTypeReference) typeTree).tokens; + if (!Eclipse.nameEquals(t, "lombok.experimental.FieldDefaults")) continue; + } else { + continue; + } + + if (!typeMatches(FieldDefaults.class, jn, typeTree)) continue; + + source = jn; + fieldDefaults = createAnnotation(FieldDefaults.class, jn); + levelIsExplicit = fieldDefaults.isExplicit("level"); + makeFinalIsExplicit = fieldDefaults.isExplicit("makeFinal"); + + handleExperimentalFlagUsage(jn, ConfigurationKeys.FIELD_DEFAULTS_FLAG_USAGE, "@FieldDefaults"); + + fd = fieldDefaults.getInstance(); + if (!levelIsExplicit && !makeFinalIsExplicit) { + jn.addError("This does nothing; provide either level or makeFinal or both."); + } + + if (levelIsExplicit && fd.level() == AccessLevel.NONE) { + jn.addError("AccessLevel.NONE doesn't mean anything here. Pick another value."); + levelIsExplicit = false; + } + break; } - 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 (fd == null && (type.modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation)) != 0) return; - 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."); - } + boolean defaultToPrivate = levelIsExplicit ? false : Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_PRIVATE_EVERYWHERE)); + boolean defaultToFinal = makeFinalIsExplicit ? false : Boolean.TRUE.equals(typeNode.getAst().readConfiguration(ConfigurationKeys.FIELD_DEFAULTS_FINAL_EVERYWHERE)); - if (node == null) return; + if (!defaultToPrivate && !defaultToFinal && fieldDefaults == null) return; + AccessLevel fdAccessLevel = (fieldDefaults != null && levelIsExplicit) ? fd.level() : defaultToPrivate ? AccessLevel.PRIVATE : null; + boolean fdToFinal = (fieldDefaults != null && makeFinalIsExplicit) ? fd.makeFinal() : defaultToFinal; - generateFieldDefaultsForType(node, annotationNode, level, makeFinal, false); + generateFieldDefaultsForType(typeNode, source, fdAccessLevel, fdToFinal, false); } } diff --git a/src/core/lombok/eclipse/handlers/HandleGetter.java b/src/core/lombok/eclipse/handlers/HandleGetter.java index 031fff82..f417aca5 100644 --- a/src/core/lombok/eclipse/handlers/HandleGetter.java +++ b/src/core/lombok/eclipse/handlers/HandleGetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2016 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 @@ -105,7 +105,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { return true; } - public boolean fieldQualifiesForGetterGeneration(EclipseNode field) { + public static boolean fieldQualifiesForGetterGeneration(EclipseNode field) { if (field.getKind() != Kind.FIELD) return false; FieldDeclaration fieldDecl = (FieldDeclaration) field.get(); return filterField(fieldDecl); @@ -148,7 +148,7 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { if (node == null) return; - List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod=", annotationNode); + List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Getter(onMethod", annotationNode); switch (node.getKind()) { case FIELD: @@ -183,6 +183,10 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { errorNode.addError("'lazy' requires the field to be private and final."); return; } + if ((field.modifiers & ClassFileConstants.AccTransient) != 0) { + errorNode.addError("'lazy' is not supported on transient fields."); + return; + } if (field.initialization == null) { errorNode.addError("'lazy' requires field initialization."); return; @@ -270,14 +274,12 @@ public class HandleGetter extends EclipseAnnotationHandler<Getter> { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), findAnnotations(field, NON_NULL_PATTERN), findAnnotations(field, NULLABLE_PATTERN), findDelegatesAndMarkAsHandled(fieldNode), deprecated); - - if (copiedAnnotations.length != 0) method.annotations = copiedAnnotations; } method.traverse(new SetGeneratedByVisitor(source), parent.scope); diff --git a/src/core/lombok/eclipse/handlers/HandleHelper.java b/src/core/lombok/eclipse/handlers/HandleHelper.java new file mode 100644 index 00000000..4f06bdfd --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleHelper.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015-2016 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.core.handlers.HandlerUtil.handleExperimentalFlagUsage; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.internal.compiler.ASTVisitor; +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.Block; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +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.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.BlockScope; +import org.mangosdk.spi.ProviderFor; + +import lombok.ConfigurationKeys; +import lombok.core.AST.Kind; +import lombok.core.AnnotationValues; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.Helper; + +/** + * Handles the {@code lombok.Cleanup} annotation for eclipse. + */ +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleHelper extends EclipseAnnotationHandler<Helper> { + private Statement[] getStatementsFromAstNode(ASTNode node) { + if (node instanceof Block) return ((Block) node).statements; + if (node instanceof AbstractMethodDeclaration) return ((AbstractMethodDeclaration) node).statements; + if (node instanceof SwitchStatement) return ((SwitchStatement) node).statements; + return null; + } + + private void setStatementsOfAstNode(ASTNode node, Statement[] statements) { + if (node instanceof Block) ((Block) node).statements = statements; + else if (node instanceof AbstractMethodDeclaration) ((AbstractMethodDeclaration) node).statements = statements; + else if (node instanceof SwitchStatement) ((SwitchStatement) node).statements = statements; + else throw new IllegalArgumentException("Can't set statements on node type: " + node.getClass()); + } + + @Override public void handle(AnnotationValues<Helper> annotation, Annotation ast, EclipseNode annotationNode) { + handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.HELPER_FLAG_USAGE, "@Helper"); + + EclipseNode annotatedType = annotationNode.up(); + EclipseNode containingBlock = annotatedType == null ? null : annotatedType.directUp(); + Statement[] origStatements = getStatementsFromAstNode(containingBlock == null ? null : containingBlock.get()); + + if (annotatedType == null || annotatedType.getKind() != Kind.TYPE || origStatements == null) { + annotationNode.addError("@Helper is legal only on method-local classes."); + return; + } + + TypeDeclaration annotatedType_ = (TypeDeclaration) annotatedType.get(); + int indexOfType = -1; + for (int i = 0; i < origStatements.length; i++) { + if (origStatements[i] == annotatedType_) { + indexOfType = i; + break; + } + } + + final List<String> knownMethodNames = new ArrayList<String>(); + + for (AbstractMethodDeclaration methodOfHelper : annotatedType_.methods) { + if (!(methodOfHelper instanceof MethodDeclaration)) continue; + char[] name = methodOfHelper.selector; + if (name != null && name.length > 0 && name[0] != '<') knownMethodNames.add(new String(name)); + } + + Collections.sort(knownMethodNames); + final String[] knownMethodNames_ = knownMethodNames.toArray(new String[knownMethodNames.size()]); + + final char[] helperName = new char[annotatedType_.name.length + 1]; + final boolean[] helperUsed = new boolean[1]; + helperName[0] = '$'; + System.arraycopy(annotatedType_.name, 0, helperName, 1, helperName.length - 1); + + ASTVisitor visitor = new ASTVisitor() { + @Override public boolean visit(MessageSend messageSend, BlockScope scope) { + if (messageSend.receiver instanceof ThisReference) { + if ((((ThisReference) messageSend.receiver).bits & ASTNode.IsImplicitThis) == 0) return true; + } else if (messageSend.receiver != null) return true; + + char[] name = messageSend.selector; + if (name == null || name.length == 0 || name[0] == '<') return true; + String n = new String(name); + if (Arrays.binarySearch(knownMethodNames_, n) < 0) return true; + messageSend.receiver = new SingleNameReference(helperName, messageSend.nameSourcePosition); + helperUsed[0] = true; + return true; + } + }; + + for (int i = indexOfType + 1; i < origStatements.length; i++) { + origStatements[i].traverse(visitor, null); + } + + if (!helperUsed[0]) { + annotationNode.addWarning("No methods of this helper class are ever used."); + return; + } + + Statement[] newStatements = new Statement[origStatements.length + 1]; + System.arraycopy(origStatements, 0, newStatements, 0, indexOfType + 1); + System.arraycopy(origStatements, indexOfType + 1, newStatements, indexOfType + 2, origStatements.length - indexOfType - 1); + LocalDeclaration decl = new LocalDeclaration(helperName, 0, 0); + decl.modifiers |= ClassFileConstants.AccFinal; + AllocationExpression alloc = new AllocationExpression(); + alloc.type = new SingleTypeReference(annotatedType_.name, 0L); + decl.initialization = alloc; + decl.type = new SingleTypeReference(annotatedType_.name, 0L); + SetGeneratedByVisitor sgbvVisitor = new SetGeneratedByVisitor(annotationNode.get()); + decl.traverse(sgbvVisitor, null); + newStatements[indexOfType + 1] = decl; + setStatementsOfAstNode(containingBlock.get(), newStatements); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleLog.java b/src/core/lombok/eclipse/handlers/HandleLog.java index 830190a2..c49030d8 100644 --- a/src/core/lombok/eclipse/handlers/HandleLog.java +++ b/src/core/lombok/eclipse/handlers/HandleLog.java @@ -229,6 +229,17 @@ public class HandleLog { } } + /** + * Handles the {@link lombok.extern.jbosslog.JBossLog} annotation for Eclipse. + */ + @ProviderFor(EclipseAnnotationHandler.class) + public static class HandleJBossLog extends EclipseAnnotationHandler<lombok.extern.jbosslog.JBossLog> { + @Override public void handle(AnnotationValues<lombok.extern.jbosslog.JBossLog> annotation, Annotation source, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.LOG_JBOSSLOG_FLAG_USAGE, "@JBossLog", ConfigurationKeys.LOG_ANY_FLAG_USAGE, "any @Log"); + processAnnotation(LoggingFramework.JBOSSLOG, annotation, source, annotationNode, annotation.getInstance().topic()); + } + } + enum LoggingFramework { // private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(TargetType.class); COMMONS("org.apache.commons.logging.Log", "org.apache.commons.logging.LogFactory", "getLog", "@CommonsLog"), @@ -264,7 +275,9 @@ public class HandleLog { // private static final org.slf4j.ext.XLogger log = org.slf4j.ext.XLoggerFactory.getXLogger(TargetType.class); XSLF4J("org.slf4j.ext.XLogger", "org.slf4j.ext.XLoggerFactory", "getXLogger", "@XSlf4j"), - + + // private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(TargetType.class); + JBOSSLOG("org.jboss.logging.Logger", "org.jboss.logging.Logger", "getLogger", "@JBossLog"), ; private final String loggerTypeName; diff --git a/src/core/lombok/eclipse/handlers/HandleNonNull.java b/src/core/lombok/eclipse/handlers/HandleNonNull.java index d904de2f..d09993ed 100644 --- a/src/core/lombok/eclipse/handlers/HandleNonNull.java +++ b/src/core/lombok/eclipse/handlers/HandleNonNull.java @@ -91,7 +91,7 @@ public class HandleNonNull extends EclipseAnnotationHandler<NonNull> { if (isGenerated(declaration)) return; if (declaration.isAbstract()) { - annotationNode.addWarning("@NonNull is meaningless on a parameter of an abstract method."); + // This used to be a warning, but as @NonNull also has a documentary purpose, better to not warn about this. Since 1.16.7 return; } diff --git a/src/core/lombok/eclipse/handlers/HandlePrintAST.java b/src/core/lombok/eclipse/handlers/HandlePrintAST.java index 0b61bc4d..234e29b8 100644 --- a/src/core/lombok/eclipse/handlers/HandlePrintAST.java +++ b/src/core/lombok/eclipse/handlers/HandlePrintAST.java @@ -59,7 +59,7 @@ public class HandlePrintAST extends EclipseAnnotationHandler<PrintAST> { try { stream.close(); } catch (Exception e) { - Lombok.sneakyThrow(e); + throw Lombok.sneakyThrow(e); } } } diff --git a/src/core/lombok/eclipse/handlers/HandleSetter.java b/src/core/lombok/eclipse/handlers/HandleSetter.java index c22af676..6a9a5123 100644 --- a/src/core/lombok/eclipse/handlers/HandleSetter.java +++ b/src/core/lombok/eclipse/handlers/HandleSetter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2014 The Project Lombok Authors. + * Copyright (C) 2009-2017 The Project Lombok Authors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -52,6 +52,7 @@ import org.eclipse.jdt.internal.compiler.ast.ReturnStatement; import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; import org.eclipse.jdt.internal.compiler.ast.Statement; import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TrueLiteral; import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; @@ -125,8 +126,8 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { AccessLevel level = annotation.getInstance().value(); if (level == AccessLevel.NONE || node == null) return; - List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod=", annotationNode); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam=", annotationNode); + List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod", annotationNode); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam", annotationNode); switch (node.getKind()) { case FIELD: @@ -192,11 +193,11 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { } } - MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), fieldNode, setterName, shouldReturnThis, modifier, sourceNode, onMethod, onParam); + MethodDeclaration method = createSetter((TypeDeclaration) fieldNode.up().get(), false, fieldNode, setterName, null, shouldReturnThis, modifier, sourceNode, onMethod, onParam); injectMethod(fieldNode.up(), method); } - static MethodDeclaration createSetter(TypeDeclaration parent, EclipseNode fieldNode, String name, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { + static MethodDeclaration createSetter(TypeDeclaration parent, boolean deprecate, EclipseNode fieldNode, String name, char[] booleanFieldToSet, boolean shouldReturnThis, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { FieldDeclaration field = (FieldDeclaration) fieldNode.get(); ASTNode source = sourceNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; @@ -213,13 +214,10 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { shouldReturnThis = false; } Annotation[] deprecated = null; - if (isFieldDeprecated(fieldNode)) { + if (isFieldDeprecated(fieldNode) || deprecate) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); - if (copiedAnnotations.length != 0) { - method.annotations = copiedAnnotations; - } + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; @@ -246,15 +244,17 @@ public class HandleSetter extends EclipseAnnotationHandler<Setter> { statements.add(assignment); } + if (booleanFieldToSet != null) { + statements.add(new Assignment(new SingleNameReference(booleanFieldToSet, p), new TrueLiteral(pS, pE), pE)); + } + if (shouldReturnThis) { ThisReference thisRef = new ThisReference(pS, pE); ReturnStatement returnThis = new ReturnStatement(thisRef, pS, pE); statements.add(returnThis); } method.statements = statements.toArray(new Statement[0]); - - Annotation[] copiedAnnotationsParam = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); - if (copiedAnnotationsParam.length != 0) param.annotations = copiedAnnotationsParam; + param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; diff --git a/src/core/lombok/eclipse/handlers/HandleToString.java b/src/core/lombok/eclipse/handlers/HandleToString.java index a4ed254a..d8f4c569 100644 --- a/src/core/lombok/eclipse/handlers/HandleToString.java +++ b/src/core/lombok/eclipse/handlers/HandleToString.java @@ -94,7 +94,11 @@ public class HandleToString extends EclipseAnnotationHandler<ToString> { Boolean configuration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_INCLUDE_FIELD_NAMES); includeFieldNames = configuration != null ? configuration : ((Boolean)ToString.class.getMethod("includeFieldNames").getDefaultValue()).booleanValue(); } catch (Exception ignore) {} - generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, FieldAccess.GETTER); + + Boolean doNotUseGettersConfiguration = typeNode.getAst().readConfiguration(ConfigurationKeys.TO_STRING_DO_NOT_USE_GETTERS); + FieldAccess access = doNotUseGettersConfiguration == null || !doNotUseGettersConfiguration ? FieldAccess.GETTER : FieldAccess.PREFER_FIELD; + + generateToString(typeNode, errorNode, null, null, includeFieldNames, null, false, access); } public void handle(AnnotationValues<ToString> annotation, Annotation ast, EclipseNode annotationNode) { diff --git a/src/core/lombok/eclipse/handlers/HandleUtilityClass.java b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java new file mode 100644 index 00000000..959c1d20 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/HandleUtilityClass.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2015 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.core.handlers.HandlerUtil.*; +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +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.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Clinit; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +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.FieldDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.StringLiteral; +import org.eclipse.jdt.internal.compiler.ast.ThrowStatement; +import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.mangosdk.spi.ProviderFor; + +import lombok.ConfigurationKeys; +import lombok.core.AnnotationValues; +import lombok.core.HandlerPriority; +import lombok.core.AST.Kind; +import lombok.eclipse.EclipseAnnotationHandler; +import lombok.eclipse.EclipseNode; +import lombok.experimental.UtilityClass; + +/** + * Handles the {@code lombok.experimental.UtilityClass} annotation for eclipse. + */ +@HandlerPriority(-4096) //-2^12; to ensure @FieldDefaults picks up on the 'static' we set here. +@ProviderFor(EclipseAnnotationHandler.class) +public class HandleUtilityClass extends EclipseAnnotationHandler<UtilityClass> { + @Override public void handle(AnnotationValues<UtilityClass> annotation, Annotation ast, EclipseNode annotationNode) { + handleFlagUsage(annotationNode, ConfigurationKeys.UTILITY_CLASS_FLAG_USAGE, "@UtilityClass"); + + EclipseNode typeNode = annotationNode.up(); + if (!checkLegality(typeNode, annotationNode)) return; + changeModifiersAndGenerateConstructor(annotationNode.up(), annotationNode); + } + + private static boolean checkLegality(EclipseNode typeNode, EclipseNode errorNode) { + TypeDeclaration typeDecl = null; + if (typeNode.get() instanceof TypeDeclaration) typeDecl = (TypeDeclaration) typeNode.get(); + int modifiers = typeDecl == null ? 0 : typeDecl.modifiers; + boolean notAClass = (modifiers & (ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0; + + if (typeDecl == null || notAClass) { + errorNode.addError("@UtilityClass is only supported on a class (can't be an interface, enum, or annotation)."); + return false; + } + + // It might be an inner class. This is okay, but only if it is / can be a static inner class. Thus, all of its parents have to be static inner classes until the top-level. + EclipseNode typeWalk = typeNode; + while (true) { + typeWalk = typeWalk.up(); + switch (typeWalk.getKind()) { + case TYPE: + if ((((TypeDeclaration) typeWalk.get()).modifiers & (ClassFileConstants.AccStatic | ClassFileConstants.AccInterface | ClassFileConstants.AccAnnotation | ClassFileConstants.AccEnum)) != 0) continue; + if (typeWalk.up().getKind() == Kind.COMPILATION_UNIT) return true; + errorNode.addError("@UtilityClass automatically makes the class static, however, this class cannot be made static."); + return false; + case COMPILATION_UNIT: + return true; + default: + errorNode.addError("@UtilityClass cannot be placed on a method local or anonymous inner class, or any class nested in such a class."); + return false; + } + } + } + + private void changeModifiersAndGenerateConstructor(EclipseNode typeNode, EclipseNode annotationNode) { + TypeDeclaration classDecl = (TypeDeclaration) typeNode.get(); + + boolean makeConstructor = true; + + classDecl.modifiers |= ClassFileConstants.AccFinal; + + boolean markStatic = true; + boolean requiresClInit = false; + boolean alreadyHasClinit = false; + + if (typeNode.up().getKind() == Kind.COMPILATION_UNIT) markStatic = false; + if (markStatic && typeNode.up().getKind() == Kind.TYPE) { + TypeDeclaration typeDecl = (TypeDeclaration) typeNode.up().get(); + if ((typeDecl.modifiers & ClassFileConstants.AccInterface) != 0) markStatic = false; + } + + if (markStatic) classDecl.modifiers |= ClassFileConstants.AccStatic; + + for (EclipseNode element : typeNode.down()) { + if (element.getKind() == Kind.FIELD) { + FieldDeclaration fieldDecl = (FieldDeclaration) element.get(); + if ((fieldDecl.modifiers & ClassFileConstants.AccStatic) == 0) { + requiresClInit = true; + fieldDecl.modifiers |= ClassFileConstants.AccStatic; + } + } else if (element.getKind() == Kind.METHOD) { + AbstractMethodDeclaration amd = (AbstractMethodDeclaration) element.get(); + if (amd instanceof ConstructorDeclaration) { + ConstructorDeclaration constrDecl = (ConstructorDeclaration) element.get(); + if (getGeneratedBy(constrDecl) == null && (constrDecl.bits & ASTNode.IsDefaultConstructor) == 0) { + element.addError("@UtilityClasses cannot have declared constructors."); + makeConstructor = false; + continue; + } + } else if (amd instanceof MethodDeclaration) { + amd.modifiers |= ClassFileConstants.AccStatic; + } else if (amd instanceof Clinit) { + alreadyHasClinit = true; + } + } else if (element.getKind() == Kind.TYPE) { + ((TypeDeclaration) element.get()).modifiers |= ClassFileConstants.AccStatic; + } + } + + if (makeConstructor) createPrivateDefaultConstructor(typeNode, annotationNode); + if (requiresClInit && !alreadyHasClinit) classDecl.addClinit(); + } + + private static final char[][] JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION = new char[][] { + TypeConstants.JAVA, TypeConstants.LANG, "UnsupportedOperationException".toCharArray() + }; + + private static final char[] UNSUPPORTED_MESSAGE = "This is a utility class and cannot be instantiated".toCharArray(); + + private void createPrivateDefaultConstructor(EclipseNode typeNode, EclipseNode sourceNode) { + ASTNode source = sourceNode.get(); + + TypeDeclaration typeDeclaration = ((TypeDeclaration) typeNode.get()); + long p = (long) source.sourceStart << 32 | source.sourceEnd; + + ConstructorDeclaration constructor = new ConstructorDeclaration(((CompilationUnitDeclaration) typeNode.top().get()).compilationResult); + + constructor.modifiers = ClassFileConstants.AccPrivate; + constructor.selector = typeDeclaration.name; + constructor.constructorCall = new ExplicitConstructorCall(ExplicitConstructorCall.ImplicitSuper); + constructor.constructorCall.sourceStart = source.sourceStart; + constructor.constructorCall.sourceEnd = source.sourceEnd; + constructor.thrownExceptions = null; + constructor.typeParameters = null; + constructor.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + constructor.bodyStart = constructor.declarationSourceStart = constructor.sourceStart = source.sourceStart; + constructor.bodyEnd = constructor.declarationSourceEnd = constructor.sourceEnd = source.sourceEnd; + constructor.arguments = null; + + AllocationExpression exception = new AllocationExpression(); + setGeneratedBy(exception, source); + long[] ps = new long[JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION.length]; + Arrays.fill(ps, p); + exception.type = new QualifiedTypeReference(JAVA_LANG_UNSUPPORTED_OPERATION_EXCEPTION, ps); + setGeneratedBy(exception.type, source); + exception.arguments = new Expression[] { + new StringLiteral(UNSUPPORTED_MESSAGE, source.sourceStart, source.sourceEnd, 0) + }; + setGeneratedBy(exception.arguments[0], source); + ThrowStatement throwStatement = new ThrowStatement(exception, source.sourceStart, source.sourceEnd); + setGeneratedBy(throwStatement, source); + + constructor.statements = new Statement[] {throwStatement}; + + injectMethod(typeNode, constructor); + } +} diff --git a/src/core/lombok/eclipse/handlers/HandleVal.java b/src/core/lombok/eclipse/handlers/HandleVal.java index d4ae417c..3742ac00 100644 --- a/src/core/lombok/eclipse/handlers/HandleVal.java +++ b/src/core/lombok/eclipse/handlers/HandleVal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-2018 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,19 +21,24 @@ */ package lombok.eclipse.handlers; -import static lombok.core.handlers.HandlerUtil.*; +import static lombok.core.handlers.HandlerUtil.handleFlagUsage; +import static lombok.eclipse.handlers.EclipseHandlerUtil.typeMatches; import lombok.ConfigurationKeys; import lombok.val; +import lombok.var; import lombok.core.HandlerPriority; import lombok.eclipse.DeferUntilPostDiet; import lombok.eclipse.EclipseASTAdapter; import lombok.eclipse.EclipseASTVisitor; import lombok.eclipse.EclipseNode; +import org.eclipse.jdt.internal.compiler.ast.ASTNode; import org.eclipse.jdt.internal.compiler.ast.ArrayInitializer; import org.eclipse.jdt.internal.compiler.ast.ForStatement; import org.eclipse.jdt.internal.compiler.ast.ForeachStatement; import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; import org.mangosdk.spi.ProviderFor; /* @@ -44,8 +49,13 @@ import org.mangosdk.spi.ProviderFor; @HandlerPriority(65536) // 2^16; resolution needs to work, so if the RHS expression is i.e. a call to a generated getter, we have to run after that getter has been generated. public class HandleVal extends EclipseASTAdapter { @Override public void visitLocal(EclipseNode localNode, LocalDeclaration local) { - if (!EclipseHandlerUtil.typeMatches(val.class, localNode, local.type)) return; - handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); + TypeReference type = local.type; + boolean isVal = typeMatches(val.class, localNode, type); + boolean isVar = typeMatches(var.class, localNode, type); + if (!(isVal || isVar)) return; + + if (isVal) handleFlagUsage(localNode, ConfigurationKeys.VAL_FLAG_USAGE, "val"); + if (isVar) handleFlagUsage(localNode, ConfigurationKeys.VAR_FLAG_USAGE, "var"); boolean variableOfForEach = false; @@ -54,23 +64,37 @@ public class HandleVal extends EclipseASTAdapter { variableOfForEach = fs.elementVariable == local; } + String annotation = isVal ? "val" : "var"; if (local.initialization == null && !variableOfForEach) { - localNode.addError("'val' on a local variable requires an initializer expression"); + localNode.addError("'" + annotation + "' on a local variable requires an initializer expression"); return; } if (local.initialization instanceof ArrayInitializer) { - localNode.addError("'val' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); + localNode.addError("'" + annotation + "' is not compatible with array initializer expressions. Use the full form (new int[] { ... } instead of just { ... })"); return; } - if (localNode.directUp().get() instanceof ForStatement) { + ASTNode parentRaw = localNode.directUp().get(); + + if (isVal && parentRaw instanceof ForStatement) { localNode.addError("'val' is not allowed in old-style for loops"); return; } + if (parentRaw instanceof ForStatement && ((ForStatement) parentRaw).initializations != null && ((ForStatement) parentRaw).initializations.length > 1) { + localNode.addError("'var' is not allowed in old-style for loops if there is more than 1 initializer"); + return; + } + if (local.initialization != null && local.initialization.getClass().getName().equals("org.eclipse.jdt.internal.compiler.ast.LambdaExpression")) { - localNode.addError("'val' is not allowed with lambda expressions."); + localNode.addError("'" + annotation + "' is not allowed with lambda expressions."); + return; + } + + if(isVar && local.initialization instanceof NullLiteral) { + localNode.addError("variable initializer is 'null'"); + return; } } } diff --git a/src/core/lombok/eclipse/handlers/HandleValue.java b/src/core/lombok/eclipse/handlers/HandleValue.java index 79c11771..a61ca6c3 100644 --- a/src/core/lombok/eclipse/handlers/HandleValue.java +++ b/src/core/lombok/eclipse/handlers/HandleValue.java @@ -47,6 +47,12 @@ import org.mangosdk.spi.ProviderFor; @ProviderFor(EclipseAnnotationHandler.class) @HandlerPriority(-512) //-2^9; to ensure @EqualsAndHashCode and such pick up on this handler making the class final and messing with the fields' access levels, run earlier. public class HandleValue extends EclipseAnnotationHandler<Value> { + private HandleFieldDefaults handleFieldDefaults = new HandleFieldDefaults(); + private HandleGetter handleGetter = new HandleGetter(); + private HandleEqualsAndHashCode handleEqualsAndHashCode = new HandleEqualsAndHashCode(); + private HandleToString handleToString = new HandleToString(); + private HandleConstructor handleConstructor = new HandleConstructor(); + public void handle(AnnotationValues<Value> annotation, Annotation ast, EclipseNode annotationNode) { handleFlagUsage(annotationNode, ConfigurationKeys.VALUE_FLAG_USAGE, "@Value"); @@ -72,7 +78,7 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { } } - new HandleFieldDefaults().generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); + handleFieldDefaults.generateFieldDefaultsForType(typeNode, annotationNode, AccessLevel.PRIVATE, true, true); //Careful: Generate the public static constructor (if there is one) LAST, so that any attempt to //'find callers' on the annotation node will find callers of the constructor, which is by far the @@ -80,10 +86,10 @@ public class HandleValue extends EclipseAnnotationHandler<Value> { //for whatever reason, though you can find callers of that one by focusing on the class name itself //and hitting 'find callers'. - 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(), SkipIfConstructorExists.YES, + handleGetter.generateGetterForType(typeNode, annotationNode, AccessLevel.PUBLIC, true); + handleEqualsAndHashCode.generateEqualsAndHashCodeForType(typeNode, annotationNode); + handleToString.generateToStringForType(typeNode, annotationNode); + handleConstructor.generateAllArgsConstructor(typeNode, AccessLevel.PUBLIC, ann.staticConstructor(), SkipIfConstructorExists.YES, Collections.<Annotation>emptyList(), annotationNode); } } diff --git a/src/core/lombok/eclipse/handlers/HandleWither.java b/src/core/lombok/eclipse/handlers/HandleWither.java index 8b038676..200ebde7 100644 --- a/src/core/lombok/eclipse/handlers/HandleWither.java +++ b/src/core/lombok/eclipse/handlers/HandleWither.java @@ -25,7 +25,6 @@ import static lombok.core.handlers.HandlerUtil.*; import static lombok.eclipse.Eclipse.*; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; -import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -57,6 +56,7 @@ import org.eclipse.jdt.internal.compiler.ast.ThisReference; 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.ExtraCompilerModifiers; import org.mangosdk.spi.ProviderFor; @ProviderFor(EclipseAnnotationHandler.class) @@ -127,8 +127,8 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { AccessLevel level = annotation.getInstance().value(); if (level == AccessLevel.NONE || node == null) return; - List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod=", annotationNode); - List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam=", annotationNode); + List<Annotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Wither(onMethod", annotationNode); + List<Annotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam", annotationNode); switch (node.getKind()) { case FIELD: @@ -163,6 +163,9 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { return; } + EclipseNode typeNode = fieldNode.up(); + boolean makeAbstract = typeNode != null && typeNode.getKind() == Kind.TYPE && (((TypeDeclaration) typeNode.get()).modifiers & ClassFileConstants.AccAbstract) != 0; + FieldDeclaration field = (FieldDeclaration) fieldNode.get(); TypeReference fieldType = copyType(field.type, source); boolean isBoolean = isBoolean(fieldType); @@ -208,17 +211,18 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { int modifier = toEclipseModifier(level); - MethodDeclaration method = createWither((TypeDeclaration) fieldNode.up().get(), fieldNode, witherName, modifier, sourceNode, onMethod, onParam); + MethodDeclaration method = createWither((TypeDeclaration) fieldNode.up().get(), fieldNode, witherName, modifier, sourceNode, onMethod, onParam, makeAbstract); injectMethod(fieldNode.up(), method); } - public MethodDeclaration createWither(TypeDeclaration parent, EclipseNode fieldNode, String name, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam) { + public MethodDeclaration createWither(TypeDeclaration parent, EclipseNode fieldNode, String name, int modifier, EclipseNode sourceNode, List<Annotation> onMethod, List<Annotation> onParam, boolean makeAbstract ) { ASTNode source = sourceNode.get(); if (name == null) return null; FieldDeclaration field = (FieldDeclaration) fieldNode.get(); int pS = source.sourceStart, pE = source.sourceEnd; - long p = (long)pS << 32 | pE; + long p = (long) pS << 32 | pE; MethodDeclaration method = new MethodDeclaration(parent.compilationResult); + if (makeAbstract) modifier = modifier | ClassFileConstants.AccAbstract | ExtraCompilerModifiers.AccSemicolonBody; method.modifiers = modifier; method.returnType = cloneSelfType(fieldNode, source); if (method.returnType == null) return null; @@ -227,11 +231,8 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { if (isFieldDeprecated(fieldNode)) { deprecated = new Annotation[] { generateDeprecatedAnnotation(source) }; } - Annotation[] copiedAnnotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); - if (copiedAnnotations.length != 0) { - method.annotations = copiedAnnotations; - } - Argument param = new Argument(field.name, p, copyType(field.type, source), Modifier.FINAL); + method.annotations = copyAnnotations(source, onMethod.toArray(new Annotation[0]), deprecated); + Argument param = new Argument(field.name, p, copyType(field.type, source), ClassFileConstants.AccFinal); param.sourceStart = pS; param.sourceEnd = pE; method.arguments = new Argument[] { param }; method.selector = name.toCharArray(); @@ -240,51 +241,52 @@ public class HandleWither extends EclipseAnnotationHandler<Wither> { method.typeParameters = null; method.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; - List<Expression> args = new ArrayList<Expression>(); - for (EclipseNode child : fieldNode.up().down()) { - if (child.getKind() != Kind.FIELD) continue; - FieldDeclaration childDecl = (FieldDeclaration) child.get(); - // Skip fields that start with $ - if (childDecl.name != null && childDecl.name.length > 0 && childDecl.name[0] == '$') continue; - long fieldFlags = childDecl.modifiers; - // Skip static fields. - if ((fieldFlags & ClassFileConstants.AccStatic) != 0) continue; - // Skip initialized final fields. - if (((fieldFlags & ClassFileConstants.AccFinal) != 0) && childDecl.initialization != null) continue; - if (child.get() == fieldNode.get()) { - args.add(new SingleNameReference(field.name, p)); - } else { - args.add(createFieldAccessor(child, FieldAccess.ALWAYS_FIELD, source)); - } - } - - AllocationExpression constructorCall = new AllocationExpression(); - constructorCall.arguments = args.toArray(new Expression[0]); - constructorCall.type = cloneSelfType(fieldNode, source); - - Expression identityCheck = new EqualExpression( - createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), - new SingleNameReference(field.name, p), - OperatorIds.EQUAL_EQUAL); - ThisReference thisRef = new ThisReference(pS, pE); - Expression conditional = new ConditionalExpression(identityCheck, thisRef, constructorCall); - Statement returnStatement = new ReturnStatement(conditional, pS, pE); - method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; - method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; - Annotation[] nonNulls = findAnnotations(field, NON_NULL_PATTERN); Annotation[] nullables = findAnnotations(field, NULLABLE_PATTERN); - List<Statement> statements = new ArrayList<Statement>(5); - if (nonNulls.length > 0) { - Statement nullCheck = generateNullCheck(field, sourceNode); - if (nullCheck != null) statements.add(nullCheck); - } - statements.add(returnStatement); - - method.statements = statements.toArray(new Statement[0]); - Annotation[] copiedAnnotationsParam = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); - if (copiedAnnotationsParam.length != 0) param.annotations = copiedAnnotationsParam; + if (!makeAbstract) { + List<Expression> args = new ArrayList<Expression>(); + for (EclipseNode child : fieldNode.up().down()) { + if (child.getKind() != Kind.FIELD) continue; + FieldDeclaration childDecl = (FieldDeclaration) child.get(); + // Skip fields that start with $ + if (childDecl.name != null && childDecl.name.length > 0 && childDecl.name[0] == '$') continue; + long fieldFlags = childDecl.modifiers; + // Skip static fields. + if ((fieldFlags & ClassFileConstants.AccStatic) != 0) continue; + // Skip initialized final fields. + if (((fieldFlags & ClassFileConstants.AccFinal) != 0) && childDecl.initialization != null) continue; + if (child.get() == fieldNode.get()) { + args.add(new SingleNameReference(field.name, p)); + } else { + args.add(createFieldAccessor(child, FieldAccess.ALWAYS_FIELD, source)); + } + } + + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.arguments = args.toArray(new Expression[0]); + constructorCall.type = cloneSelfType(fieldNode, source); + + Expression identityCheck = new EqualExpression( + createFieldAccessor(fieldNode, FieldAccess.ALWAYS_FIELD, source), + new SingleNameReference(field.name, p), + OperatorIds.EQUAL_EQUAL); + ThisReference thisRef = new ThisReference(pS, pE); + Expression conditional = new ConditionalExpression(identityCheck, thisRef, constructorCall); + Statement returnStatement = new ReturnStatement(conditional, pS, pE); + method.bodyStart = method.declarationSourceStart = method.sourceStart = source.sourceStart; + method.bodyEnd = method.declarationSourceEnd = method.sourceEnd = source.sourceEnd; + + List<Statement> statements = new ArrayList<Statement>(5); + if (nonNulls.length > 0) { + Statement nullCheck = generateNullCheck(field, sourceNode); + if (nullCheck != null) statements.add(nullCheck); + } + statements.add(returnStatement); + + method.statements = statements.toArray(new Statement[0]); + } + param.annotations = copyAnnotations(source, nonNulls, nullables, onParam.toArray(new Annotation[0])); method.traverse(new SetGeneratedByVisitor(source), parent.scope); return method; diff --git a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java index 7217a396..89964914 100644 --- a/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java +++ b/src/core/lombok/eclipse/handlers/SetGeneratedByVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2013 The Project Lombok Authors. + * Copyright (C) 2011-2015 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 @@ -23,6 +23,8 @@ package lombok.eclipse.handlers; import static lombok.eclipse.handlers.EclipseHandlerUtil.*; +import java.util.Arrays; + import org.eclipse.jdt.internal.compiler.ASTVisitor; import org.eclipse.jdt.internal.compiler.ast.AND_AND_Expression; import org.eclipse.jdt.internal.compiler.ast.ASTNode; @@ -128,881 +130,816 @@ public final class SetGeneratedByVisitor extends ASTVisitor { private static final long INT_TO_LONG_MASK = 0x00000000FFFFFFFFL; private final ASTNode source; - private final int newSourceStart; - private final int newSourceEnd; + private final int sourceStart; + private final int sourceEnd; + private final long sourcePos; public SetGeneratedByVisitor(ASTNode source) { this.source = source; - this.newSourceStart = this.source.sourceStart; - this.newSourceEnd = this.source.sourceEnd; + this.sourceStart = this.source.sourceStart; + this.sourceEnd = this.source.sourceEnd; + this.sourcePos = (long)sourceStart << 32 | (sourceEnd & INT_TO_LONG_MASK); } - private void applyOffset(JavadocAllocationExpression node) { - applyOffsetExpression(node); - node.memberStart = newSourceStart; - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocAllocationExpression node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.memberStart = sourceStart; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocMessageSend node) { - applyOffsetMessageSend(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocMessageSend node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocSingleNameReference node) { - applyOffsetExpression(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocSingleNameReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocSingleTypeReference node) { - applyOffsetExpression(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocSingleTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; } - private void applyOffset(JavadocFieldReference node) { - applyOffsetFieldReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(JavadocFieldReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(JavadocArrayQualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(JavadocQualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); + node.tagSourceEnd = sourceEnd; + node.tagSourceStart = sourceStart; + } + + private void fixPositions(Annotation node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; } - - private void applyOffset(JavadocArrayQualifiedTypeReference node) { - applyOffsetQualifiedTypeReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + + private void fixPositions(ArrayTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.originalSourceEnd = sourceEnd; } - private void applyOffset(JavadocQualifiedTypeReference node) { - applyOffsetQualifiedTypeReference(node); - node.tagSourceEnd = newSourceEnd; - node.tagSourceStart = newSourceStart; + private void fixPositions(AbstractMethodDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.bodyEnd = sourceEnd; + node.bodyStart = sourceStart; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffset(Annotation node) { - applyOffsetExpression(node); - node.declarationSourceEnd = newSourceEnd; + private void fixPositions(Javadoc node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.valuePositions = sourceStart; } - private void applyOffset(ArrayTypeReference node) { - applyOffsetExpression(node); - node.originalSourceEnd = newSourceEnd; + private void fixPositions(Initializer node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; + node.endPart1Position = sourceEnd; + node.endPart2Position = sourceEnd; + node.bodyStart = sourceStart; + node.bodyEnd = sourceEnd; } - - private void applyOffset(AbstractMethodDeclaration node) { - applyOffsetASTNode(node); - node.bodyEnd = newSourceEnd; - node.bodyStart = newSourceStart; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; + + private void fixPositions(TypeDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.bodyEnd = sourceEnd; + node.bodyStart = sourceStart; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffset(Javadoc node) { - applyOffsetASTNode(node); - node.valuePositions = newSourceStart; - for (int i = 0; i < node.inheritedPositions.length; i++) { - node.inheritedPositions[i] = recalcSourcePosition(node.inheritedPositions[i]); - } + private void fixPositions(ImportReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } - - private void applyOffset(Initializer node) { - applyOffsetFieldDeclaration(node); - node.bodyStart = newSourceStart; - node.bodyEnd = newSourceEnd; - } - - private void applyOffset(TypeDeclaration node) { - applyOffsetASTNode(node); - node.bodyEnd = newSourceEnd; - node.bodyStart = newSourceStart; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; - } - - private void applyOffset(ImportReference node) { - applyOffsetASTNode(node); - node.declarationEnd = newSourceEnd; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } + + private void fixPositions(ASTNode node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; } - - private void applyOffsetASTNode(ASTNode node) { - node.sourceEnd = newSourceEnd; - node.sourceStart = newSourceStart; + + private void fixPositions(SwitchStatement node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.blockStart = sourceStart; } - - private void applyOffsetExpression(Expression node) { - applyOffsetASTNode(node); -// if (node.statementEnd != -1) { - node.statementEnd = newSourceEnd; -// } + + private void fixPositions(Expression node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; } - - private void applyOffsetVariable(AbstractVariableDeclaration node) { - applyOffsetASTNode(node); - node.declarationEnd = newSourceEnd; - node.declarationSourceEnd = newSourceEnd; - node.declarationSourceStart = newSourceStart; - node.modifiersSourceStart = newSourceStart; + + private void fixPositions(AbstractVariableDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; } - private void applyOffsetFieldDeclaration(FieldDeclaration node) { - applyOffsetVariable(node); - node.endPart1Position = newSourceEnd; - node.endPart2Position = newSourceEnd; + private void fixPositions(FieldDeclaration node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.declarationEnd = sourceEnd; + node.declarationSourceEnd = sourceEnd; + node.declarationSourceStart = sourceStart; + node.modifiersSourceStart = sourceStart; + node.endPart1Position = sourceEnd; + node.endPart2Position = sourceEnd; } - private void applyOffsetFieldReference(FieldReference node) { - applyOffsetExpression(node); - node.nameSourcePosition = recalcSourcePosition(node.nameSourcePosition); + private void fixPositions(FieldReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; } - private void applyOffsetMessageSend(MessageSend node) { - applyOffsetExpression(node); - node.nameSourcePosition = recalcSourcePosition(node.nameSourcePosition); + private void fixPositions(MessageSend node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + node.nameSourcePosition = sourcePos; } - private void applyOffsetQualifiedNameReference(QualifiedNameReference node) { - applyOffsetExpression(node); - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } + private void fixPositions(QualifiedNameReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } - private void applyOffsetQualifiedTypeReference(QualifiedTypeReference node) { - applyOffsetExpression(node); - for (int i = 0; i < node.sourcePositions.length; i++) { - node.sourcePositions[i] = recalcSourcePosition(node.sourcePositions[i]); - } - } - - /** See {@link FieldReference#nameSourcePosition} for explanation */ - private long recalcSourcePosition(long sourcePosition) { -// long start = (sourcePosition >>> 32); -// long end = (sourcePosition & 0x00000000FFFFFFFFL); -// start = newSourceStart; -// end = newSourceStart; -// return ((start<<32)+end); - return ((long)newSourceStart << 32) | (newSourceEnd & INT_TO_LONG_MASK); + private void fixPositions(QualifiedTypeReference node) { + node.sourceEnd = sourceEnd; + node.sourceStart = sourceStart; + node.statementEnd = sourceEnd; + if (node.sourcePositions == null || node.sourcePositions.length != node.tokens.length) node.sourcePositions = new long[node.tokens.length]; + Arrays.fill(node.sourcePositions, sourcePos); } @Override public boolean visit(AllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } - + @Override public boolean visit(AND_AND_Expression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(AnnotationMethodDeclaration node, ClassScope classScope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, classScope); } @Override public boolean visit(Argument node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Argument node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayInitializer node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ArrayTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(AssertStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Assignment node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(BinaryExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Block node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(BreakStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CaseStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CastExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CharLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ClassLiteralAccess node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Clinit node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CompilationUnitDeclaration node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(CompoundAssignment node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ConditionalExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ConstructorDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ContinueStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(DoStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(DoubleLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(EmptyStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(EqualExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ExplicitConstructorCall node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ExtendedStringLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FalseLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldDeclaration node, MethodScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldDeclaration(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FieldReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetFieldReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(FloatLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ForeachStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ForStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(IfStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ImportReference node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Initializer node, MethodScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(InstanceOfExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(IntLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Javadoc node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Javadoc node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocAllocationExpression node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArgumentExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArgumentExpression node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArrayQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArrayQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArraySingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocArraySingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocFieldReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocFieldReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocImplicitTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocImplicitTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocMessageSend node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocMessageSend node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocReturnStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocReturnStatement node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(JavadocSingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LabeledStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LocalDeclaration node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(LongLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MarkerAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MemberValuePair node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MessageSend node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetMessageSend(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(MethodDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(StringLiteralConcatenation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(NormalAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(NullLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(OR_OR_Expression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedQualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedQualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedSingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ParameterizedSingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(PostfixExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(PrefixExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedAllocationExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedNameReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedSuperReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedSuperReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedThisReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedThisReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(QualifiedTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetQualifiedTypeReference(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ReturnStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleMemberAnnotation node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleNameReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleNameReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleTypeReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SingleTypeReference node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(StringLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SuperReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SwitchStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); - node.blockStart = newSourceStart; + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(SynchronizedStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThisReference node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThisReference node, ClassScope scope) { - setGeneratedBy(node, source); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(ThrowStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TrueLiteral node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TryStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeDeclaration node, CompilationUnitScope scope) { - setGeneratedBy(node, source); - applyOffset(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeParameter node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(TypeParameter node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetVariable(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(UnaryExpression node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(WhileStatement node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetASTNode(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Wildcard node, BlockScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } @Override public boolean visit(Wildcard node, ClassScope scope) { - setGeneratedBy(node, source); - applyOffsetExpression(node); + fixPositions(setGeneratedBy(node, source)); return super.visit(node, scope); } + +// missing methods +// public boolean visit(MarkerAnnotation node, ClassScope scope){ return true;} +// public boolean visit(MemberValuePair node, ClassScope scope){ return true;} +// public boolean visit(NormalAnnotation node, ClassScope scope){ return true;} +// public boolean visit(SingleMemberAnnotation node, ClassScope scope){ return true;} + +// missing methods from later versions +// public boolean visit(UnionTypeReference node, BlockScope scope){ return true;} +// public boolean visit(UnionTypeReference node, ClassScope scope){ return true;} +// public boolean visit(LambdaExpression node, BlockScope scope){ return true;} +// public boolean visit(ReferenceExpression node, BlockScope scope){ return true;} +// public boolean visit(IntersectionCastTypeReference node, ClassScope scope){ return true;} +// public boolean visit(IntersectionCastTypeReference node, BlockScope scope){ return true;} }
\ No newline at end of file diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java new file mode 100644 index 00000000..5956c01b --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaMapSingularizer.java @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaMapSingularizer extends EclipseGuavaSingularizer { + // TODO cgcc.ImmutableMultimap, cgcc.ImmutableListMultimap, cgcc.ImmutableSetMultimap + // TODO cgcc.ImmutableClassToInstanceMap + // TODO cgcc.ImmutableRangeMap + + private static final LombokImmutableList<String> SUFFIXES = + LombokImmutableList.of("key", "value"); + private static final LombokImmutableList<String> SUPPORTED_TYPES = LombokImmutableList.of( + "com.google.common.collect.ImmutableMap", + "com.google.common.collect.ImmutableBiMap", + "com.google.common.collect.ImmutableSortedMap" + ); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "put"; + } + + @Override protected String getAddAllTypeName() { + return "java.util.Map"; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java new file mode 100644 index 00000000..326a9179 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSetListSingularizer.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaSetListSingularizer extends EclipseGuavaSingularizer { + // TODO com.google.common.collect.ImmutableRangeSet + // TODO com.google.common.collect.ImmutableMultiset and com.google.common.collect.ImmutableSortedMultiset + private static final LombokImmutableList<String> SUFFIXES = LombokImmutableList.of(""); + private static final LombokImmutableList<String> SUPPORTED_TYPES = LombokImmutableList.of( + "com.google.common.collect.ImmutableCollection", + "com.google.common.collect.ImmutableList", + "com.google.common.collect.ImmutableSet", + "com.google.common.collect.ImmutableSortedSet" + ); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "add"; + } + + @Override protected String getAddAllTypeName() { + return "java.lang.Iterable"; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java new file mode 100644 index 00000000..8e925b3f --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaSingularizer.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2015-2017 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.singulars; + +import static lombok.eclipse.Eclipse.*; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.core.GuavaTypeMap; +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +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; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +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.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +abstract class EclipseGuavaSingularizer extends EclipseSingularizer { + protected String getSimpleTargetTypeName(SingularData data) { + return GuavaTypeMap.getGuavaTypeName(data.getTargetFqn()); + } + + protected char[] getBuilderMethodName(SingularData data) { + String simpleTypeName = getSimpleTargetTypeName(data); + if ("ImmutableSortedSet".equals(simpleTypeName) || "ImmutableSortedMap".equals(simpleTypeName)) return "naturalOrder".toCharArray(); + return "builder".toCharArray(); + } + + protected char[][] makeGuavaTypeName(String simpleName, boolean addBuilder) { + char[][] tokenizedName = new char[addBuilder ? 6 : 5][]; + tokenizedName[0] = new char[] {'c', 'o', 'm'}; + tokenizedName[1] = new char[] {'g', 'o', 'o', 'g', 'l', 'e'}; + tokenizedName[2] = new char[] {'c', 'o', 'm', 'm', 'o', 'n'}; + tokenizedName[3] = new char[] {'c', 'o', 'l', 'l', 'e', 'c', 't'}; + tokenizedName[4] = simpleName.toCharArray(); + if (addBuilder) tokenizedName[5] = new char[] { 'B', 'u', 'i', 'l', 'd', 'e', 'r'}; + return tokenizedName; + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + String simpleTypeName = getSimpleTargetTypeName(data); + char[][] tokenizedName = makeGuavaTypeName(simpleTypeName, true); + TypeReference type = new QualifiedTypeReference(tokenizedName, NULL_POSS); + type = addTypeArgs(getTypeArgumentsCount(), false, builderType, type, data.getTypeArgs()); + + FieldDeclaration buildField = new FieldDeclaration(data.getPluralName(), 0, -1); + buildField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildField.modifiers = ClassFileConstants.AccPrivate; + buildField.declarationSourceEnd = -1; + buildField.type = type; + data.setGeneratedByRecursive(buildField); + return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain) { + TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateSingularMethod(deprecate, returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generatePluralMethod(deprecate, returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateClearMethod(deprecate, returnType, returnStatement, data, builderType); + } + + void generateClearMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + Assignment a = new Assignment(thisDotField, new NullLiteral(0, 0), 0); + md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray(); + md.statements = returnStatement != null ? new Statement[] {a, returnStatement} : new Statement[] {a}; + md.returnType = returnType; + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + injectMethod(builderType, md); + } + + void generateSingularMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + LombokImmutableList<String> suffixes = getArgumentSuffixes(); + char[][] names = new char[suffixes.size()][]; + for (int i = 0; i < suffixes.size(); i++) { + String s = suffixes.get(i); + char[] n = data.getSingularName(); + names[i] = s.isEmpty() ? n : s.toCharArray(); + } + + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAdd = new MessageSend(); + thisDotFieldDotAdd.arguments = new Expression[suffixes.size()]; + for (int i = 0; i < suffixes.size(); i++) { + thisDotFieldDotAdd.arguments[i] = new SingleNameReference(names[i], 0L); + } + thisDotFieldDotAdd.receiver = thisDotField; + thisDotFieldDotAdd.selector = getAddMethodName().toCharArray(); + statements.add(thisDotFieldDotAdd); + if (returnStatement != null) statements.add(returnStatement); + md.statements = statements.toArray(new Statement[statements.size()]); + md.arguments = new Argument[suffixes.size()]; + for (int i = 0; i < suffixes.size(); i++) { + TypeReference tr = cloneParamType(i, data.getTypeArgs(), builderType); + md.arguments[i] = new Argument(names[i], 0, tr, 0); + } + md.returnType = returnType; + md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName(getAddMethodName(), new String(data.getSingularName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + void generatePluralMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAddAll = new MessageSend(); + thisDotFieldDotAddAll.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L)}; + thisDotFieldDotAddAll.receiver = thisDotField; + thisDotFieldDotAddAll.selector = (getAddMethodName() + "All").toCharArray(); + statements.add(thisDotFieldDotAddAll); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType; + paramType = new QualifiedTypeReference(fromQualifiedName(getAddAllTypeName()), NULL_POSS); + paramType = addTypeArgs(getTypeArgumentsCount(), true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName(getAddMethodName() + "All", new String(data.getPluralName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + TypeReference varType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + String simpleTypeName = getSimpleTargetTypeName(data); + int agrumentsCount = getTypeArgumentsCount(); + varType = addTypeArgs(agrumentsCount, false, builderType, varType, data.getTypeArgs()); + + MessageSend emptyInvoke; { + //ImmutableX.of() + emptyInvoke = new MessageSend(); + emptyInvoke.selector = new char[] {'o', 'f'}; + emptyInvoke.receiver = new QualifiedNameReference(makeGuavaTypeName(simpleTypeName, false), NULL_POSS, 0, 0); + emptyInvoke.typeArguments = createTypeArgs(agrumentsCount, false, builderType, data.getTypeArgs()); + } + + MessageSend invokeBuild; { + //this.pluralName.build(); + invokeBuild = new MessageSend(); + invokeBuild.selector = new char[] {'b', 'u', 'i', 'l', 'd'}; + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + invokeBuild.receiver = thisDotField; + } + + Expression isNull; { + //this.pluralName == null + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + isNull = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + } + + Expression init = new ConditionalExpression(isNull, emptyInvoke, invokeBuild); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = varType; + varDefStat.initialization = init; + statements.add(varDefStat); + } + + protected Statement createConstructBuilderVarIfNeeded(SingularData data, EclipseNode builderType) { + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(data.getPluralName(), 0L); + thisDotField2.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + + MessageSend createBuilderInvoke = new MessageSend(); + char[][] tokenizedName = makeGuavaTypeName(getSimpleTargetTypeName(data), false); + createBuilderInvoke.receiver = new QualifiedNameReference(tokenizedName, NULL_POSS, 0, 0); + createBuilderInvoke.selector = getBuilderMethodName(data); + return new IfStatement(cond, new Assignment(thisDotField2, createBuilderInvoke, 0), 0, 0); + } + + protected abstract LombokImmutableList<String> getArgumentSuffixes(); + protected abstract String getAddMethodName(); + protected abstract String getAddAllTypeName(); + + protected int getTypeArgumentsCount() { + return getArgumentSuffixes().size(); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaTableSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaTableSingularizer.java new file mode 100644 index 00000000..4d25811b --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseGuavaTableSingularizer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2015 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.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseGuavaTableSingularizer extends EclipseGuavaSingularizer { + private static final LombokImmutableList<String> SUFFIXES = + LombokImmutableList.of("rowKey", "columnKey", "value"); + private static final LombokImmutableList<String> SUPPORTED_TYPES = + LombokImmutableList.of("com.google.common.collect.ImmutableTable"); + + @Override public LombokImmutableList<String> getSupportedTypes() { + return SUPPORTED_TYPES; + } + + @Override protected LombokImmutableList<String> getArgumentSuffixes() { + return SUFFIXES; + } + + @Override protected String getAddMethodName() { + return "put"; + } + + @Override protected String getAddAllTypeName() { + return "com.google.common.collect.Table"; + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java new file mode 100644 index 00000000..cfa48eaf --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSetSingularizer.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2015-2017 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.singulars; + +import static lombok.eclipse.Eclipse.ECLIPSE_DO_NOT_TOUCH_FLAG; +import static lombok.eclipse.handlers.EclipseHandlerUtil.*; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +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.EqualExpression; +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.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +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.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +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.eclipse.jdt.internal.compiler.lookup.TypeIds; + +abstract class EclipseJavaUtilListSetSingularizer extends EclipseJavaUtilSingularizer { + @Override public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listFieldsToBeGenerated(data, builderType); + } + + return super.listFieldsToBeGenerated(data, builderType); + } + + @Override public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.generateFields(data, builderType); + } + + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs()); + + FieldDeclaration buildField = new FieldDeclaration(data.getPluralName(), 0, -1); + buildField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildField.modifiers = ClassFileConstants.AccPrivate; + buildField.declarationSourceEnd = -1; + buildField.type = type; + data.setGeneratedByRecursive(buildField); + return Collections.singletonList(injectFieldAndMarkGenerated(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.generateMethods(data, deprecate, builderType, fluent, chain); + return; + } + + TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateSingularMethod(deprecate, returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generatePluralMethod(deprecate, returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateClearMethod(deprecate, returnType, returnStatement, data, builderType); + } + + private void generateClearMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(data.getPluralName(), 0L); + thisDotField2.receiver = new ThisReference(0, 0); + md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray(); + MessageSend clearMsg = new MessageSend(); + clearMsg.receiver = thisDotField2; + clearMsg.selector = "clear".toCharArray(); + Statement clearStatement = new IfStatement(new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL), clearMsg, 0, 0); + md.statements = returnStatement != null ? new Statement[] {clearStatement, returnStatement} : new Statement[] {clearStatement}; + md.returnType = returnType; + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + injectMethod(builderType, md); + } + + void generateSingularMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, false)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAdd = new MessageSend(); + thisDotFieldDotAdd.arguments = new Expression[] {new SingleNameReference(data.getSingularName(), 0L)}; + thisDotFieldDotAdd.receiver = thisDotField; + thisDotFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotFieldDotAdd); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + TypeReference paramType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument param = new Argument(data.getSingularName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName("add", new String(data.getSingularName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + void generatePluralMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, false)); + + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldDotAddAll = new MessageSend(); + thisDotFieldDotAddAll.arguments = new Expression[] {new SingleNameReference(data.getPluralName(), 0L)}; + thisDotFieldDotAddAll.receiver = thisDotField; + thisDotFieldDotAddAll.selector = "addAll".toCharArray(); + statements.add(thisDotFieldDotAddAll); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType = new QualifiedTypeReference(TypeConstants.JAVA_UTIL_COLLECTION, NULL_POSS); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName("addAll", new String(data.getPluralName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java new file mode 100644 index 00000000..576115b0 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilListSingularizer.java @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2015 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.singulars; + +import static lombok.eclipse.handlers.EclipseHandlerUtil.makeIntLiteral; + +import java.util.ArrayList; +import java.util.List; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.Eclipse; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.AllocationExpression; +import org.eclipse.jdt.internal.compiler.ast.Assignment; +import org.eclipse.jdt.internal.compiler.ast.BreakStatement; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement; +import org.eclipse.jdt.internal.compiler.ast.Expression; +import org.eclipse.jdt.internal.compiler.ast.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.mangosdk.spi.ProviderFor; + + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilListSingularizer extends EclipseJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.lang.Iterable"); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + List<Statement> switchContents = new ArrayList<Statement>(); + + /* case 0: (empty) break; */ { + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'0'}, null), 0, 0)); + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = "emptyList".toCharArray(); + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + switchContents.add(new BreakStatement(null, 0, 0)); + } + + /* case 1: (singleton) break; */ { + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); + FieldReference thisDotField = new FieldReference(data.getPluralName(), 0L); + thisDotField.receiver = new ThisReference(0, 0); + MessageSend thisDotFieldGet0 = new MessageSend(); + thisDotFieldGet0.receiver = thisDotField; + thisDotFieldGet0.selector = new char[] {'g', 'e', 't'}; + thisDotFieldGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + + Expression[] args = new Expression[] {thisDotFieldGet0}; + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = "singletonList".toCharArray(); + invoke.arguments = args; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + switchContents.add(new BreakStatement(null, 0, 0)); + } + + /* default: Create by passing builder field to constructor. */ { + switchContents.add(new CaseStatement(null, 0, 0)); + + Expression argToUnmodifiable; + /* new j.u.ArrayList<Generics>(this.pluralName); */ { + FieldReference thisDotPluralName = new FieldReference(data.getPluralName(), 0L); + thisDotPluralName.receiver = new ThisReference(0, 0); + TypeReference targetTypeExpr = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs()); + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.type = targetTypeExpr; + constructorCall.arguments = new Expression[] {thisDotPluralName}; + argToUnmodifiable = constructorCall; + } + + /* pluralname = Collections.unmodifiableList(-newlist-); */ { + MessageSend unmodInvoke = new MessageSend(); + unmodInvoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + unmodInvoke.selector = "unmodifiableList".toCharArray(); + unmodInvoke.arguments = new Expression[] {argToUnmodifiable}; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), unmodInvoke, 0)); + } + } + + SwitchStatement switchStat = new SwitchStatement(); + switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); + switchStat.expression = getSize(builderType, data.getPluralName(), true); + + TypeReference localShadowerType = new QualifiedTypeReference(Eclipse.fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = localShadowerType; + statements.add(varDefStat); + statements.add(switchStat); + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java new file mode 100644 index 00000000..9aac32a3 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilMapSingularizer.java @@ -0,0 +1,313 @@ +/* + * Copyright (C) 2015-2017 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.singulars; + +import static lombok.eclipse.Eclipse.*; +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.Annotation; +import org.eclipse.jdt.internal.compiler.ast.Argument; +import org.eclipse.jdt.internal.compiler.ast.Block; +import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; +import org.eclipse.jdt.internal.compiler.ast.EqualExpression; +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.ForeachStatement; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +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.Statement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilMapSingularizer extends EclipseJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Map", "java.util.SortedMap", "java.util.NavigableMap"); + } + + @Override public List<char[]> listFieldsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } + + char[] p = data.getPluralName(); + int len = p.length; + char[] k = new char[len + 4]; + char[] v = new char[len + 6]; + System.arraycopy(p, 0, k, 0, len); + System.arraycopy(p, 0, v, 0, len); + k[len] = '$'; + k[len + 1] = 'k'; + k[len + 2] = 'e'; + k[len + 3] = 'y'; + v[len] = '$'; + v[len + 1] = 'v'; + v[len + 2] = 'a'; + v[len + 3] = 'l'; + v[len + 4] = 'u'; + v[len + 5] = 'e'; + return Arrays.asList(k, v); + } + + @Override public List<char[]> listMethodsToBeGenerated(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } else { + return super.listMethodsToBeGenerated(data, builderType); + } + } + + @Override public List<EclipseNode> generateFields(SingularData data, EclipseNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.generateFields(data, builderType); + } + + char[] keyName = (new String(data.getPluralName()) + "$key").toCharArray(); + char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); + FieldDeclaration buildKeyField; { + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs()); + buildKeyField = new FieldDeclaration(keyName, 0, -1); + buildKeyField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildKeyField.modifiers = ClassFileConstants.AccPrivate; + buildKeyField.declarationSourceEnd = -1; + buildKeyField.type = type; + } + FieldDeclaration buildValueField; { + TypeReference type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + List<TypeReference> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = Collections.singletonList(tArgs.get(1)); + else tArgs = Collections.emptyList(); + type = addTypeArgs(1, false, builderType, type, tArgs); + buildValueField = new FieldDeclaration(valueName, 0, -1); + buildValueField.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + buildValueField.modifiers = ClassFileConstants.AccPrivate; + buildValueField.declarationSourceEnd = -1; + buildValueField.type = type; + } + data.setGeneratedByRecursive(buildKeyField); + data.setGeneratedByRecursive(buildValueField); + EclipseNode keyFieldNode = injectFieldAndMarkGenerated(builderType, buildKeyField); + EclipseNode valueFieldNode = injectFieldAndMarkGenerated(builderType, buildValueField); + return Arrays.asList(keyFieldNode, valueFieldNode); + } + + @Override public void generateMethods(SingularData data, boolean deprecate, EclipseNode builderType, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.generateMethods(data, deprecate, builderType, fluent, chain); + return; + } + + TypeReference returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + Statement returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateSingularMethod(deprecate, returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generatePluralMethod(deprecate, returnType, returnStatement, data, builderType, fluent); + + returnType = chain ? cloneSelfType(builderType) : TypeReference.baseTypeReference(TypeIds.T_void, 0); + returnStatement = chain ? new ReturnStatement(new ThisReference(0, 0), 0, 0) : null; + generateClearMethod(deprecate, returnType, returnStatement, data, builderType); + } + + private void generateClearMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + String pN = new String(data.getPluralName()); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + FieldReference thisDotField = new FieldReference(keyFieldName, 0L); + thisDotField.receiver = new ThisReference(0, 0); + FieldReference thisDotField2 = new FieldReference(keyFieldName, 0L); + thisDotField2.receiver = new ThisReference(0, 0); + FieldReference thisDotField3 = new FieldReference(valueFieldName, 0L); + thisDotField3.receiver = new ThisReference(0, 0); + md.selector = HandlerUtil.buildAccessorName("clear", new String(data.getPluralName())).toCharArray(); + MessageSend clearMsg1 = new MessageSend(); + clearMsg1.receiver = thisDotField2; + clearMsg1.selector = "clear".toCharArray(); + MessageSend clearMsg2 = new MessageSend(); + clearMsg2.receiver = thisDotField3; + clearMsg2.selector = "clear".toCharArray(); + Block clearMsgs = new Block(2); + clearMsgs.statements = new Statement[] {clearMsg1, clearMsg2}; + Statement clearStatement = new IfStatement(new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL), clearMsgs, 0, 0); + md.statements = returnStatement != null ? new Statement[] {clearStatement, returnStatement} : new Statement[] {clearStatement}; + md.returnType = returnType; + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + injectMethod(builderType, md); + } + + private void generateSingularMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, true)); + + String sN = new String(data.getSingularName()); + String pN = new String(data.getPluralName()); + char[] keyParamName = (sN + "Key").toCharArray(); + char[] valueParamName = (sN + "Value").toCharArray(); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + /* this.pluralname$key.add(singularnameKey); */ { + FieldReference thisDotKeyField = new FieldReference(keyFieldName, 0L); + thisDotKeyField.receiver = new ThisReference(0, 0); + MessageSend thisDotKeyFieldDotAdd = new MessageSend(); + thisDotKeyFieldDotAdd.arguments = new Expression[] {new SingleNameReference(keyParamName, 0L)}; + thisDotKeyFieldDotAdd.receiver = thisDotKeyField; + thisDotKeyFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotKeyFieldDotAdd); + } + + /* this.pluralname$value.add(singularnameValue); */ { + FieldReference thisDotValueField = new FieldReference(valueFieldName, 0L); + thisDotValueField.receiver = new ThisReference(0, 0); + MessageSend thisDotValueFieldDotAdd = new MessageSend(); + thisDotValueFieldDotAdd.arguments = new Expression[] {new SingleNameReference(valueParamName, 0L)}; + thisDotValueFieldDotAdd.receiver = thisDotValueField; + thisDotValueFieldDotAdd.selector = "add".toCharArray(); + statements.add(thisDotValueFieldDotAdd); + } + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + TypeReference keyParamType = cloneParamType(0, data.getTypeArgs(), builderType); + Argument keyParam = new Argument(keyParamName, 0, keyParamType, 0); + TypeReference valueParamType = cloneParamType(1, data.getTypeArgs(), builderType); + Argument valueParam = new Argument(valueParamName, 0, valueParamType, 0); + md.arguments = new Argument[] {keyParam, valueParam}; + md.returnType = returnType; + md.selector = fluent ? data.getSingularName() : HandlerUtil.buildAccessorName("put", new String(data.getSingularName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + private void generatePluralMethod(boolean deprecate, TypeReference returnType, Statement returnStatement, SingularData data, EclipseNode builderType, boolean fluent) { + MethodDeclaration md = new MethodDeclaration(((CompilationUnitDeclaration) builderType.top().get()).compilationResult); + md.bits |= ECLIPSE_DO_NOT_TOUCH_FLAG; + md.modifiers = ClassFileConstants.AccPublic; + + String pN = new String(data.getPluralName()); + char[] keyFieldName = (pN + "$key").toCharArray(); + char[] valueFieldName = (pN + "$value").toCharArray(); + + List<Statement> statements = new ArrayList<Statement>(); + statements.add(createConstructBuilderVarIfNeeded(data, builderType, true)); + + char[] entryName = "$lombokEntry".toCharArray(); + + TypeReference forEachType = new QualifiedTypeReference(JAVA_UTIL_MAP_ENTRY, NULL_POSS); + forEachType = addTypeArgs(2, true, builderType, forEachType, data.getTypeArgs()); + + MessageSend keyArg = new MessageSend(); + keyArg.receiver = new SingleNameReference(entryName, 0L); + keyArg.selector = "getKey".toCharArray(); + MessageSend addKey = new MessageSend(); + FieldReference thisDotKeyField = new FieldReference(keyFieldName, 0L); + thisDotKeyField.receiver = new ThisReference(0, 0); + addKey.receiver = thisDotKeyField; + addKey.selector = new char[] {'a', 'd', 'd'}; + addKey.arguments = new Expression[] {keyArg}; + + MessageSend valueArg = new MessageSend(); + valueArg.receiver = new SingleNameReference(entryName, 0L); + valueArg.selector = "getValue".toCharArray(); + MessageSend addValue = new MessageSend(); + FieldReference thisDotValueField = new FieldReference(valueFieldName, 0L); + thisDotValueField.receiver = new ThisReference(0, 0); + addValue.receiver = thisDotValueField; + addValue.selector = new char[] {'a', 'd', 'd'}; + addValue.arguments = new Expression[] {valueArg}; + + LocalDeclaration elementVariable = new LocalDeclaration(entryName, 0, 0); + elementVariable.type = forEachType; + ForeachStatement forEach = new ForeachStatement(elementVariable, 0); + MessageSend invokeEntrySet = new MessageSend(); + invokeEntrySet.selector = new char[] { 'e', 'n', 't', 'r', 'y', 'S', 'e', 't'}; + invokeEntrySet.receiver = new SingleNameReference(data.getPluralName(), 0L); + forEach.collection = invokeEntrySet; + Block forEachContent = new Block(0); + forEachContent.statements = new Statement[] {addKey, addValue}; + forEach.action = forEachContent; + statements.add(forEach); + if (returnStatement != null) statements.add(returnStatement); + + md.statements = statements.toArray(new Statement[statements.size()]); + + TypeReference paramType = new QualifiedTypeReference(JAVA_UTIL_MAP, NULL_POSS); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs()); + Argument param = new Argument(data.getPluralName(), 0, paramType, 0); + md.arguments = new Argument[] {param}; + md.returnType = returnType; + md.selector = fluent ? data.getPluralName() : HandlerUtil.buildAccessorName("putAll", new String(data.getPluralName())).toCharArray(); + md.annotations = deprecate ? new Annotation[] { generateDeprecatedAnnotation(data.getSource()) } : null; + + data.setGeneratedByRecursive(md); + injectMethod(builderType, md); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + if (data.getTargetFqn().equals("java.util.Map")) { + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap")); + } else { + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, true, true, false, true, "TreeMap")); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java new file mode 100644 index 00000000..2d16eae0 --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSetSingularizer.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2015 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.singulars; + +import java.util.List; + +import lombok.core.LombokImmutableList; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(EclipseSingularizer.class) +public class EclipseJavaUtilSetSingularizer extends EclipseJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); + } + + @Override public void appendBuildCode(SingularData data, EclipseNode builderType, List<Statement> statements, char[] targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, statements, targetVariableName); + return; + } + + if (data.getTargetFqn().equals("java.util.Set")) { + statements.addAll(createJavaUtilSetMapInitialCapacitySwitchStatements(data, builderType, false, "emptySet", "singleton", "LinkedHashSet")); + } else { + statements.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, false, true, false, true, "TreeSet")); + } + } +} diff --git a/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java new file mode 100644 index 00000000..6661f4af --- /dev/null +++ b/src/core/lombok/eclipse/handlers/singulars/EclipseJavaUtilSingularizer.java @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2015 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.singulars; + +import static lombok.eclipse.Eclipse.*; +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.AllocationExpression; +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.BreakStatement; +import org.eclipse.jdt.internal.compiler.ast.CaseStatement; +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.FieldReference; +import org.eclipse.jdt.internal.compiler.ast.ForStatement; +import org.eclipse.jdt.internal.compiler.ast.IfStatement; +import org.eclipse.jdt.internal.compiler.ast.IntLiteral; +import org.eclipse.jdt.internal.compiler.ast.LocalDeclaration; +import org.eclipse.jdt.internal.compiler.ast.MessageSend; +import org.eclipse.jdt.internal.compiler.ast.NullLiteral; +import org.eclipse.jdt.internal.compiler.ast.OperatorIds; +import org.eclipse.jdt.internal.compiler.ast.PostfixExpression; +import org.eclipse.jdt.internal.compiler.ast.QualifiedNameReference; +import org.eclipse.jdt.internal.compiler.ast.QualifiedTypeReference; +import org.eclipse.jdt.internal.compiler.ast.SingleNameReference; +import org.eclipse.jdt.internal.compiler.ast.Statement; +import org.eclipse.jdt.internal.compiler.ast.SwitchStatement; +import org.eclipse.jdt.internal.compiler.ast.ThisReference; +import org.eclipse.jdt.internal.compiler.ast.TypeReference; +import org.eclipse.jdt.internal.compiler.lookup.TypeConstants; +import org.eclipse.jdt.internal.compiler.lookup.TypeIds; + +import lombok.ConfigurationKeys; +import lombok.eclipse.EclipseNode; +import lombok.eclipse.handlers.EclipseSingularsRecipes.EclipseSingularizer; +import lombok.eclipse.handlers.EclipseSingularsRecipes.SingularData; + +abstract class EclipseJavaUtilSingularizer extends EclipseSingularizer { + protected static final char[][] JAVA_UTIL_ARRAYLIST = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'A', 'r', 'r', 'a', 'y', 'L', 'i', 's', 't'} + }; + + protected static final char[][] JAVA_UTIL_LIST = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'L', 'i', 's', 't'} + }; + + protected static final char[][] JAVA_UTIL_MAP = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'} + }; + + protected static final char[][] JAVA_UTIL_MAP_ENTRY = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'M', 'a', 'p'}, {'E', 'n', 't', 'r', 'y'} + }; + + protected static final char[][] JAVA_UTIL_COLLECTIONS = { + {'j', 'a', 'v', 'a'}, {'u', 't', 'i', 'l'}, {'C', 'o', 'l', 'l', 'e', 'c', 't', 'i', 'o', 'n', 's'} + }; + + protected final EclipseSingularizer guavaListSetSingularizer = new EclipseGuavaSetListSingularizer(); + protected final EclipseSingularizer guavaMapSingularizer = new EclipseGuavaMapSingularizer(); + + protected boolean useGuavaInstead(EclipseNode node) { + return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); + } + + protected List<Statement> createJavaUtilSetMapInitialCapacitySwitchStatements(SingularData data, EclipseNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType) { + List<Statement> switchContents = new ArrayList<Statement>(); + char[] keyName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); + + if (emptyCollectionMethod != null) { // case 0: (empty); break; + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'0'}, null), 0, 0)); + + /* pluralName = java.util.Collections.emptyCollectionMethod(); */ { + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = emptyCollectionMethod.toCharArray(); + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + } + + switchContents.add(new BreakStatement(null, 0, 0)); + } + + if (singletonCollectionMethod != null) { // case 1: (singleton); break; + switchContents.add(new CaseStatement(makeIntLiteral(new char[] {'1'}, null), 0, 0)); + /* !mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName.get(0)); + mapMode: pluralName = java.util.Collections.singletonCollectionMethod(this.pluralName$key.get(0), this.pluralName$value.get(0)); */ { + FieldReference thisDotKey = new FieldReference(keyName, 0L); + thisDotKey.receiver = new ThisReference(0, 0); + MessageSend thisDotKeyGet0 = new MessageSend(); + thisDotKeyGet0.receiver = thisDotKey; + thisDotKeyGet0.selector = new char[] {'g', 'e', 't'}; + thisDotKeyGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + + Expression[] args; + if (mapMode) { + char[] valueName = (new String(data.getPluralName()) + "$value").toCharArray(); + FieldReference thisDotValue = new FieldReference(valueName, 0L); + thisDotValue.receiver = new ThisReference(0, 0); + MessageSend thisDotValueGet0 = new MessageSend(); + thisDotValueGet0.receiver = thisDotValue; + thisDotValueGet0.selector = new char[] {'g', 'e', 't'}; + thisDotValueGet0.arguments = new Expression[] {makeIntLiteral(new char[] {'0'}, null)}; + args = new Expression[] {thisDotKeyGet0, thisDotValueGet0}; + } else { + args = new Expression[] {thisDotKeyGet0}; + } + + MessageSend invoke = new MessageSend(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + invoke.selector = singletonCollectionMethod.toCharArray(); + invoke.arguments = args; + switchContents.add(new Assignment(new SingleNameReference(data.getPluralName(), 0), invoke, 0)); + } + switchContents.add(new BreakStatement(null, 0, 0)); + } + + { // default: + switchContents.add(new CaseStatement(null, 0, 0)); + switchContents.addAll(createJavaUtilSimpleCreationAndFillStatements(data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType)); + } + + SwitchStatement switchStat = new SwitchStatement(); + switchStat.statements = switchContents.toArray(new Statement[switchContents.size()]); + switchStat.expression = getSize(builderType, keyName, true); + + TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration varDefStat = new LocalDeclaration(data.getPluralName(), 0, 0); + varDefStat.type = localShadowerType; + return Arrays.asList(varDefStat, switchStat); + } + + protected List<Statement> createJavaUtilSimpleCreationAndFillStatements(SingularData data, EclipseNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType) { + char[] varName = mapMode ? (new String(data.getPluralName()) + "$key").toCharArray() : data.getPluralName(); + + Statement createStat; { + // pluralName = new java.util.TargetType(initialCap); + Expression[] constructorArgs = null; + if (addInitialCapacityArg) { + // this.varName.size() < MAX_POWER_OF_2 ? 1 + this.varName.size() + (this.varName.size() - 3) / 3 : Integer.MAX_VALUE; + // lessThanCutOff = this.varName.size() < MAX_POWER_OF_2 + Expression lessThanCutoff = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral("0x40000000".toCharArray(), null), OperatorIds.LESS); + FieldReference integerMaxValue = new FieldReference("MAX_VALUE".toCharArray(), 0L); + integerMaxValue.receiver = new QualifiedNameReference(TypeConstants.JAVA_LANG_INTEGER, NULL_POSS, 0, 0); + Expression sizeFormulaLeft = new BinaryExpression(makeIntLiteral(new char[] {'1'}, null), getSize(builderType, varName, nullGuard), OperatorIds.PLUS); + Expression sizeFormulaRightLeft = new BinaryExpression(getSize(builderType, varName, nullGuard), makeIntLiteral(new char[] {'3'}, null), OperatorIds.MINUS); + Expression sizeFormulaRight = new BinaryExpression(sizeFormulaRightLeft, makeIntLiteral(new char[] {'3'}, null), OperatorIds.DIVIDE); + Expression sizeFormula = new BinaryExpression(sizeFormulaLeft, sizeFormulaRight, OperatorIds.PLUS); + Expression cond = new ConditionalExpression(lessThanCutoff, sizeFormula, integerMaxValue); + constructorArgs = new Expression[] {cond}; + } + + TypeReference targetTypeRef = new QualifiedTypeReference(new char[][] {TypeConstants.JAVA, TypeConstants.UTIL, targetType.toCharArray()}, NULL_POSS); + targetTypeRef = addTypeArgs(mapMode ? 2 : 1, false, builderType, targetTypeRef, data.getTypeArgs()); + AllocationExpression constructorCall = new AllocationExpression(); + constructorCall.type = targetTypeRef; + constructorCall.arguments = constructorArgs; + + if (defineVar) { + TypeReference localShadowerType = new QualifiedTypeReference(fromQualifiedName(data.getTargetFqn()), NULL_POSS); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs()); + LocalDeclaration localShadowerDecl = new LocalDeclaration(data.getPluralName(), 0, 0); + localShadowerDecl.type = localShadowerType; + localShadowerDecl.initialization = constructorCall; + createStat = localShadowerDecl; + } else { + createStat = new Assignment(new SingleNameReference(data.getPluralName(), 0L), constructorCall, 0); + } + } + + Statement fillStat; { + if (mapMode) { + // for (int $i = 0; $i < this.pluralname$key.size(); i++) pluralname.put(this.pluralname$key.get($i), this.pluralname$value.get($i)); + char[] iVar = new char[] {'$', 'i'}; + MessageSend pluralnameDotPut = new MessageSend(); + pluralnameDotPut.selector = new char[] {'p', 'u', 't'}; + pluralnameDotPut.receiver = new SingleNameReference(data.getPluralName(), 0L); + FieldReference thisDotKey = new FieldReference(varName, 0L); + thisDotKey.receiver = new ThisReference(0, 0); + FieldReference thisDotValue = new FieldReference((new String(data.getPluralName()) + "$value").toCharArray(), 0L); + thisDotValue.receiver = new ThisReference(0, 0); + MessageSend keyArg = new MessageSend(); + keyArg.receiver = thisDotKey; + keyArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; + keyArg.selector = new char[] {'g', 'e', 't'}; + MessageSend valueArg = new MessageSend(); + valueArg.receiver = thisDotValue; + valueArg.arguments = new Expression[] {new SingleNameReference(iVar, 0L)}; + valueArg.selector = new char[] {'g', 'e', 't'}; + pluralnameDotPut.arguments = new Expression[] {keyArg, valueArg}; + + LocalDeclaration forInit = new LocalDeclaration(iVar, 0, 0); + forInit.type = TypeReference.baseTypeReference(TypeIds.T_int, 0); + forInit.initialization = makeIntLiteral(new char[] {'0'}, null); + Expression checkExpr = new BinaryExpression(new SingleNameReference(iVar, 0L), getSize(builderType, varName, nullGuard), OperatorIds.LESS); + Expression incrementExpr = new PostfixExpression(new SingleNameReference(iVar, 0L), IntLiteral.One, OperatorIds.PLUS, 0); + fillStat = new ForStatement(new Statement[] {forInit}, checkExpr, new Statement[] {incrementExpr}, pluralnameDotPut, true, 0, 0); + } else { + // pluralname.addAll(this.pluralname); + MessageSend pluralnameDotAddAll = new MessageSend(); + pluralnameDotAddAll.selector = new char[] {'a', 'd', 'd', 'A', 'l', 'l'}; + pluralnameDotAddAll.receiver = new SingleNameReference(data.getPluralName(), 0L); + FieldReference thisDotPluralname = new FieldReference(varName, 0L); + thisDotPluralname.receiver = new ThisReference(0, 0); + pluralnameDotAddAll.arguments = new Expression[] {thisDotPluralname}; + fillStat = pluralnameDotAddAll; + } + + if (nullGuard) { + FieldReference thisDotField = new FieldReference(varName, 0L); + thisDotField.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.NOT_EQUAL); + fillStat = new IfStatement(cond, fillStat, 0, 0); + } + } + + Statement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(pluralname); + Expression arg = new SingleNameReference(data.getPluralName(), 0L); + MessageSend invoke = new MessageSend(); + invoke.arguments = new Expression[] {arg}; + invoke.selector = ("unmodifiable" + data.getTargetSimpleType()).toCharArray(); + invoke.receiver = new QualifiedNameReference(JAVA_UTIL_COLLECTIONS, NULL_POSS, 0, 0); + unmodifiableStat = new Assignment(new SingleNameReference(data.getPluralName(), 0L), invoke, 0); + } + + return Arrays.asList(createStat, fillStat, unmodifiableStat); + } + + protected Statement createConstructBuilderVarIfNeeded(SingularData data, EclipseNode builderType, boolean mapMode) { + char[] v1Name, v2Name; + if (mapMode) { + String n = new String(data.getPluralName()); + v1Name = (n + "$key").toCharArray(); + v2Name = (n + "$value").toCharArray(); + } else { + v1Name = data.getPluralName(); + v2Name = null; + } + + FieldReference thisDotField = new FieldReference(v1Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + Expression cond = new EqualExpression(thisDotField, new NullLiteral(0, 0), OperatorIds.EQUAL_EQUAL); + + thisDotField = new FieldReference(v1Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + TypeReference v1Type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + v1Type = addTypeArgs(1, false, builderType, v1Type, data.getTypeArgs()); + AllocationExpression constructArrayList = new AllocationExpression(); + constructArrayList.type = v1Type; + Assignment initV1 = new Assignment(thisDotField, constructArrayList, 0); + Statement thenPart; + if (mapMode) { + thisDotField = new FieldReference(v2Name, 0L); + thisDotField.receiver = new ThisReference(0, 0); + TypeReference v2Type = new QualifiedTypeReference(JAVA_UTIL_ARRAYLIST, NULL_POSS); + List<TypeReference> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = Collections.singletonList(tArgs.get(1)); + else tArgs = Collections.emptyList(); + v2Type = addTypeArgs(1, false, builderType, v2Type, tArgs); + constructArrayList = new AllocationExpression(); + constructArrayList.type = v2Type; + Assignment initV2 = new Assignment(thisDotField, constructArrayList, 0); + Block b = new Block(0); + b.statements = new Statement[] {initV1, initV2}; + thenPart = b; + } else { + thenPart = initV1; + } + + return new IfStatement(cond, thenPart, 0, 0); + } +} |