diff options
Diffstat (limited to 'src/core/lombok/javac')
20 files changed, 1724 insertions, 146 deletions
diff --git a/src/core/lombok/javac/JavacNode.java b/src/core/lombok/javac/JavacNode.java index 6eef36eb..727692ac 100644 --- a/src/core/lombok/javac/JavacNode.java +++ b/src/core/lombok/javac/JavacNode.java @@ -66,40 +66,40 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre public void traverse(JavacASTVisitor visitor) { switch (this.getKind()) { case COMPILATION_UNIT: - visitor.visitCompilationUnit(this, (JCCompilationUnit)get()); + visitor.visitCompilationUnit(this, (JCCompilationUnit) get()); ast.traverseChildren(visitor, this); - visitor.endVisitCompilationUnit(this, (JCCompilationUnit)get()); + visitor.endVisitCompilationUnit(this, (JCCompilationUnit) get()); break; case TYPE: - visitor.visitType(this, (JCClassDecl)get()); + visitor.visitType(this, (JCClassDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitType(this, (JCClassDecl)get()); + visitor.endVisitType(this, (JCClassDecl) get()); break; case FIELD: - visitor.visitField(this, (JCVariableDecl)get()); + visitor.visitField(this, (JCVariableDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitField(this, (JCVariableDecl)get()); + visitor.endVisitField(this, (JCVariableDecl) get()); break; case METHOD: - visitor.visitMethod(this, (JCMethodDecl)get()); + visitor.visitMethod(this, (JCMethodDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitMethod(this, (JCMethodDecl)get()); + visitor.endVisitMethod(this, (JCMethodDecl) get()); break; case INITIALIZER: - visitor.visitInitializer(this, (JCBlock)get()); + visitor.visitInitializer(this, (JCBlock) get()); ast.traverseChildren(visitor, this); - visitor.endVisitInitializer(this, (JCBlock)get()); + visitor.endVisitInitializer(this, (JCBlock) get()); break; case ARGUMENT: JCMethodDecl parentMethod = (JCMethodDecl) up().get(); - visitor.visitMethodArgument(this, (JCVariableDecl)get(), parentMethod); + visitor.visitMethodArgument(this, (JCVariableDecl) get(), parentMethod); ast.traverseChildren(visitor, this); - visitor.endVisitMethodArgument(this, (JCVariableDecl)get(), parentMethod); + visitor.endVisitMethodArgument(this, (JCVariableDecl) get(), parentMethod); break; case LOCAL: - visitor.visitLocal(this, (JCVariableDecl)get()); + visitor.visitLocal(this, (JCVariableDecl) get()); ast.traverseChildren(visitor, this); - visitor.endVisitLocal(this, (JCVariableDecl)get()); + visitor.endVisitLocal(this, (JCVariableDecl) get()); break; case STATEMENT: visitor.visitStatement(this, get()); @@ -109,21 +109,21 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre case ANNOTATION: switch (up().getKind()) { case TYPE: - visitor.visitAnnotationOnType((JCClassDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnType((JCClassDecl) up().get(), this, (JCAnnotation) get()); break; case FIELD: - visitor.visitAnnotationOnField((JCVariableDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnField((JCVariableDecl) up().get(), this, (JCAnnotation) get()); break; case METHOD: - visitor.visitAnnotationOnMethod((JCMethodDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnMethod((JCMethodDecl) up().get(), this, (JCAnnotation) get()); break; case ARGUMENT: - JCVariableDecl argument = (JCVariableDecl)up().get(); - JCMethodDecl method = (JCMethodDecl)up().up().get(); - visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation)get()); + JCVariableDecl argument = (JCVariableDecl) up().get(); + JCMethodDecl method = (JCMethodDecl) up().up().get(); + visitor.visitAnnotationOnMethodArgument(argument, method, this, (JCAnnotation) get()); break; case LOCAL: - visitor.visitAnnotationOnLocal((JCVariableDecl)up().get(), this, (JCAnnotation)get()); + visitor.visitAnnotationOnLocal((JCVariableDecl) up().get(), this, (JCAnnotation) get()); break; default: throw new AssertionError("Annotion not expected as child of a " + up().getKind()); @@ -138,9 +138,9 @@ public class JavacNode extends lombok.core.LombokNode<JavacAST, JavacNode, JCTre @Override public String getName() { final Name n; - if (node instanceof JCClassDecl) n = ((JCClassDecl)node).name; - else if (node instanceof JCMethodDecl) n = ((JCMethodDecl)node).name; - else if (node instanceof JCVariableDecl) n = ((JCVariableDecl)node).name; + if (node instanceof JCClassDecl) n = ((JCClassDecl) node).name; + else if (node instanceof JCMethodDecl) n = ((JCMethodDecl) node).name; + else if (node instanceof JCVariableDecl) n = ((JCVariableDecl) node).name; else n = null; return n == null ? null : n.toString(); diff --git a/src/core/lombok/javac/JavacResolution.java b/src/core/lombok/javac/JavacResolution.java index 23d7d482..6231735c 100644 --- a/src/core/lombok/javac/JavacResolution.java +++ b/src/core/lombok/javac/JavacResolution.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011-2014 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 @@ -24,6 +24,7 @@ package lombok.javac; import static lombok.javac.Javac.*; import static lombok.javac.JavacTreeMaker.TypeTag.typeTag; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayDeque; @@ -98,7 +99,7 @@ public class JavacResolution { @Override public void visitClassDef(JCClassDecl tree) { if (copyAt != null) return; - env = enter.getClassEnv(tree.sym); + if (tree.sym != null) env = enter.getClassEnv(tree.sym); } @Override public void visitMethodDef(JCMethodDecl tree) { @@ -138,16 +139,64 @@ public class JavacResolution { EnvFinder finder = new EnvFinder(node.getContext()); while (!stack.isEmpty()) stack.pop().accept(finder); - TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker()); + TreeMirrorMaker mirrorMaker = new TreeMirrorMaker(node.getTreeMaker(), node.getContext()); JCTree copy = mirrorMaker.copy(finder.copyAt()); - attrib(copy, finder.get()); + memberEnterAndAttribute(copy, finder.get(), node.getContext()); return mirrorMaker.getOriginalToCopyMap(); } finally { messageSuppressor.enableLoggers(); } } + private static Field memberEnterDotEnv; + + private static Field getMemberEnterDotEnv() { + if (memberEnterDotEnv != null) return memberEnterDotEnv; + try { + Field f = MemberEnter.class.getDeclaredField("env"); + f.setAccessible(true); + memberEnterDotEnv = f; + } catch (NoSuchFieldException e) { + return null; + } + + return memberEnterDotEnv; + } + + @SuppressWarnings("unchecked") + private static Env<AttrContext> getEnvOfMemberEnter(MemberEnter memberEnter) { + Field f = getMemberEnterDotEnv(); + try { + return (Env<AttrContext>) f.get(memberEnter); + } catch (Exception e) { + return null; + } + } + + private static void setEnvOfMemberEnter(MemberEnter memberEnter, Env<AttrContext> env) { + Field f = getMemberEnterDotEnv(); + try { + f.set(memberEnter, env); + } catch (Exception e) { + return; + } + } + + private void memberEnterAndAttribute(JCTree copy, Env<AttrContext> env, Context context) { + MemberEnter memberEnter = MemberEnter.instance(context); + Env<AttrContext> oldEnv = getEnvOfMemberEnter(memberEnter); + setEnvOfMemberEnter(memberEnter, env); + try { + copy.accept(memberEnter); + } catch (Exception ignore) { + // intentionally ignored; usually even if this step fails, val will work (but not for val in method local inner classes and anonymous inner classes). + } finally { + setEnvOfMemberEnter(memberEnter, oldEnv); + } + attrib(copy, env); + } + public void resolveClassMember(JavacNode node) { ArrayDeque<JCTree> stack = new ArrayDeque<JCTree>(); @@ -241,7 +290,7 @@ public class JavacResolution { Type type0 = type; while (type0 instanceof ArrayType) { dims++; - type0 = ((ArrayType)type0).elemtype; + type0 = ((ArrayType) type0).elemtype; } JCExpression result = typeToJCTree0(type0, ast, allowCompound, allowVoid); diff --git a/src/core/lombok/javac/apt/EmptyLombokFileObject.java b/src/core/lombok/javac/apt/EmptyLombokFileObject.java index 7298e920..5a3a7def 100644 --- a/src/core/lombok/javac/apt/EmptyLombokFileObject.java +++ b/src/core/lombok/javac/apt/EmptyLombokFileObject.java @@ -19,7 +19,6 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ - package lombok.javac.apt; import java.io.ByteArrayInputStream; diff --git a/src/core/lombok/javac/apt/Processor.java b/src/core/lombok/javac/apt/Processor.java index ce4d75ff..9979a066 100644 --- a/src/core/lombok/javac/apt/Processor.java +++ b/src/core/lombok/javac/apt/Processor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009-2012 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 diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 1885b8b4..4f7f79d9 100644 --- a/src/core/lombok/javac/handlers/HandleBuilder.java +++ b/src/core/lombok/javac/handlers/HandleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 The Project Lombok Authors. + * Copyright (C) 2013-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 @@ -21,8 +21,8 @@ */ package lombok.javac.handlers; +import java.lang.annotation.Annotation; import java.util.ArrayList; -import java.util.Collections; import org.mangosdk.spi.ProviderFor; @@ -34,6 +34,8 @@ import com.sun.tools.javac.tree.JCTree.JCClassDecl; import com.sun.tools.javac.tree.JCTree.JCExpression; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; +import com.sun.tools.javac.tree.JCTree.JCIf; +import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCModifiers; import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree; @@ -46,16 +48,21 @@ import com.sun.tools.javac.util.ListBuffer; import com.sun.tools.javac.util.Name; import lombok.AccessLevel; +import lombok.Builder; import lombok.ConfigurationKeys; +import lombok.Singular; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; import lombok.core.HandlerPriority; -import lombok.experimental.Builder; +import lombok.core.handlers.HandlerUtil; import lombok.experimental.NonFinal; +import lombok.javac.Javac; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; import lombok.javac.handlers.HandleConstructor.SkipIfConstructorExists; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import static lombok.javac.Javac.*; @@ -64,10 +71,27 @@ import static lombok.javac.JavacTreeMaker.TypeTag.*; @ProviderFor(JavacAnnotationHandler.class) @HandlerPriority(-1024) //-2^10; to ensure we've picked up @FieldDefault's changes (-2048) but @Value hasn't removed itself yet (-512), so that we can error on presence of it on the builder classes. public class HandleBuilder extends JavacAnnotationHandler<Builder> { - @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { - handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder"); + private static final boolean toBoolean(Object expr, boolean defaultValue) { + if (expr == null) return defaultValue; + if (expr instanceof JCLiteral) return ((Integer) ((JCLiteral) expr).value) != 0; + return ((Boolean) expr).booleanValue(); + } + + private static class BuilderFieldData { + JCExpression type; + Name name; + SingularData singularData; + java.util.List<JavacNode> createdFields = new ArrayList<JavacNode>(); + } + + @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { 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(); @@ -82,20 +106,21 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (!checkName("builderClassName", builderClassName, annotationNode)) return; } - deleteAnnotationIfNeccessary(annotationNode, Builder.class); - deleteImportFromCompilationUnit(annotationNode, "lombok.experimental.Builder"); + @SuppressWarnings("deprecation") + Class<? extends Annotation> oldExperimentalBuilder = lombok.experimental.Builder.class; + deleteAnnotationIfNeccessary(annotationNode, Builder.class, oldExperimentalBuilder); JavacNode parent = annotationNode.up(); - java.util.List<JCExpression> typesOfParameters = new ArrayList<JCExpression>(); - java.util.List<Name> namesOfParameters = new ArrayList<Name>(); + java.util.List<BuilderFieldData> builderFields = new ArrayList<BuilderFieldData>(); JCExpression returnType; List<JCTypeParameter> typeParams = List.nil(); List<JCExpression> thrownExceptions = List.nil(); Name nameOfStaticBuilderMethod; JavacNode tdParent; - JCMethodDecl fillParametersFrom = parent.get() instanceof JCMethodDecl ? ((JCMethodDecl) parent.get()) : null; + JavacNode fillParametersFrom = parent.get() instanceof JCMethodDecl ? parent : null; + boolean addCleaning = false; if (parent.get() instanceof JCClassDecl) { tdParent = parent; @@ -109,12 +134,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { // non-final fields final, but @Value's handler hasn't done this yet, so we have to do this math ourselves. // Value will only skip making a field final if it has an explicit @NonFinal annotation, so we check for that. if (fd.init != null && valuePresent && !hasAnnotation(NonFinal.class, fieldNode)) continue; - namesOfParameters.add(removePrefixFromField(fieldNode)); - typesOfParameters.add(fd.vartype); + BuilderFieldData bfd = new BuilderFieldData(); + bfd.name = removePrefixFromField(fieldNode); + bfd.type = fd.vartype; + bfd.singularData = getSingularData(fieldNode); + builderFields.add(bfd); allFields.append(fieldNode); } - new HandleConstructor().generateConstructor(tdParent, AccessLevel.PACKAGE, List.<JCAnnotation>nil(), allFields.toList(), null, SkipIfConstructorExists.I_AM_BUILDER, null, annotationNode); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); @@ -123,28 +150,31 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { nameOfStaticBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("<init>")) { - if (!fillParametersFrom.typarams.isEmpty()) { + JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + if (!jmd.typarams.isEmpty()) { annotationNode.addError("@Builder is not supported on constructors with constructor type parameters."); return; } + tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); returnType = namePlusTypeParamsToTypeReference(tdParent.getTreeMaker(), td.name, td.typarams); typeParams = td.typarams; - thrownExceptions = fillParametersFrom.thrown; + thrownExceptions = jmd.thrown; nameOfStaticBuilderMethod = null; if (builderClassName.isEmpty()) builderClassName = td.name.toString() + "Builder"; } else if (fillParametersFrom != null) { tdParent = parent.up(); JCClassDecl td = (JCClassDecl) tdParent.get(); - if ((fillParametersFrom.mods.flags & Flags.STATIC) == 0) { + JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get(); + if ((jmd.mods.flags & Flags.STATIC) == 0) { annotationNode.addError("@Builder is only supported on types, constructors, and static methods."); return; } - returnType = fillParametersFrom.restype; - typeParams = fillParametersFrom.typarams; - thrownExceptions = fillParametersFrom.thrown; - nameOfStaticBuilderMethod = fillParametersFrom.name; + returnType = jmd.restype; + typeParams = jmd.typarams; + thrownExceptions = jmd.thrown; + nameOfStaticBuilderMethod = jmd.name; if (builderClassName.isEmpty()) { if (returnType instanceof JCTypeApply) { returnType = ((JCTypeApply) returnType).clazz; @@ -179,9 +209,14 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } if (fillParametersFrom != null) { - for (JCVariableDecl param : fillParametersFrom.params) { - namesOfParameters.add(param.name); - typesOfParameters.add(param.vartype); + for (JavacNode param : fillParametersFrom.down()) { + if (param.getKind() != Kind.ARGUMENT) continue; + BuilderFieldData bfd = new BuilderFieldData(); + JCVariableDecl raw = (JCVariableDecl) param.get(); + bfd.name = raw.name; + bfd.type = raw.vartype; + bfd.singularData = getSingularData(param); + builderFields.add(bfd); } } @@ -190,12 +225,34 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { builderType = makeBuilderClass(tdParent, builderClassName, typeParams, ast); } else { 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; + JavacSingularizer 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; + } + } } - java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>(); - for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, builderInstance.fluent(), builderInstance.chain()); - if (newMethod != null) newMethods.add(newMethod); + + generateBuilderFields(builderType, builderFields, ast); + if (addCleaning) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCVariableDecl uncleanField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName("$lombokUnclean"), maker.TypeIdent(CTC_BOOLEAN), null); + injectField(builderType, uncleanField); } if (constructorExists(builderType) == MemberExistsResult.NOT_EXISTS) { @@ -203,38 +260,93 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (cd != null) injectMethod(builderType, cd); } - for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData bfd : builderFields) { + makeSetterMethodsForBuilder(builderType, bfd, annotationNode, fluent, chain); + } if (methodExists(buildMethodName, builderType, -1) == MemberExistsResult.NOT_EXISTS) { - JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, namesOfParameters, builderType, thrownExceptions); + JCMethodDecl md = generateBuildMethod(buildMethodName, nameOfStaticBuilderMethod, returnType, builderFields, builderType, thrownExceptions, ast, addCleaning); if (md != null) injectMethod(builderType, md); } if (methodExists("toString", builderType, 0) == MemberExistsResult.NOT_EXISTS) { + java.util.List<JavacNode> fieldNodes = new ArrayList<JavacNode>(); + for (BuilderFieldData bfd : builderFields) { + fieldNodes.addAll(bfd.createdFields); + } JCMethodDecl md = HandleToString.createToString(builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, ast); if (md != null) injectMethod(builderType, md); } + if (addCleaning) injectMethod(builderType, generateCleanMethod(builderFields, builderType, ast)); + if (methodExists(builderMethodName, tdParent, -1) == MemberExistsResult.NOT_EXISTS) { JCMethodDecl md = generateBuilderMethod(builderMethodName, builderClassName, tdParent, typeParams); + recursiveSetGeneratedBy(md, ast, annotationNode.getContext()); if (md != null) injectMethod(tdParent, md); } + + recursiveSetGeneratedBy(builderType.get(), ast, annotationNode.getContext()); } - public JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<Name> fieldNames, JavacNode type, List<JCExpression> thrownExceptions) { + private JCMethodDecl generateCleanMethod(java.util.List<BuilderFieldData> builderFields, JavacNode type, JCTree source) { + JavacTreeMaker maker = type.getTreeMaker(); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, type, source, statements); + } + } + + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, false)))); + JCBlock body = maker.Block(0, statements.toList()); + return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(maker, CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); + /* + * if (shouldReturnThis) { + methodType = cloneSelfType(field); + } + + if (methodType == null) { + //WARNING: Do not use field.getSymbolTable().voidType - that field has gone through non-backwards compatible API changes within javac1.6. + methodType = treeMaker.Type(Javac.createVoidType(treeMaker, CTC_VOID)); + shouldReturnThis = false; + } + + */ + } + + private JCMethodDecl generateBuildMethod(String name, Name staticName, JCExpression returnType, java.util.List<BuilderFieldData> builderFields, JavacNode type, List<JCExpression> thrownExceptions, JCTree source, boolean addCleaning) { JavacTreeMaker maker = type.getTreeMaker(); JCExpression call; - JCStatement statement; + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + + if (addCleaning) { + JCExpression notClean = maker.Unary(CTC_NOT, maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean"))); + JCStatement invokeClean = maker.Exec(maker.Apply(List.<JCExpression>nil(), maker.Ident(type.toName("$lombokClean")), List.<JCExpression>nil())); + JCIf ifUnclean = maker.If(notClean, invokeClean, null); + statements.append(ifUnclean); + } + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, type, source, statements, bfd.name); + } + } ListBuffer<JCExpression> args = new ListBuffer<JCExpression>(); - for (Name n : fieldNames) { - args.append(maker.Ident(n)); + for (BuilderFieldData bfd : builderFields) { + args.append(maker.Ident(bfd.name)); + } + + if (addCleaning) { + statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(type.toName("this")), type.toName("$lombokUnclean")), maker.Literal(CTC_BOOLEAN, true)))); } if (staticName == null) { call = maker.NewClass(null, List.<JCExpression>nil(), returnType, args.toList(), null); - statement = maker.Return(call); + statements.append(maker.Return(call)); } else { ListBuffer<JCExpression> typeParams = new ListBuffer<JCExpression>(); for (JCTypeParameter tp : ((JCClassDecl) type.get()).typarams) { @@ -244,13 +356,13 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCExpression fn = maker.Select(maker.Ident(((JCClassDecl) type.up().get()).name), staticName); call = maker.Apply(typeParams.toList(), fn, args.toList()); if (returnType instanceof JCPrimitiveTypeTree && CTC_VOID.equals(typeTag(returnType))) { - statement = maker.Exec(call); + statements.append(maker.Exec(call)); } else { - statement = maker.Return(call); + statements.append(maker.Return(call)); } } - JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); + JCBlock body = maker.Block(0, statements.toList()); return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName(name), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); } @@ -270,50 +382,56 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { return maker.MethodDef(maker.Modifiers(Flags.STATIC | Flags.PUBLIC), type.toName(builderMethodName), namePlusTypeParamsToTypeReference(maker, type.toName(builderClassName), typeParams), copyTypeParams(maker, typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } - public java.util.List<JavacNode> addFieldsToBuilder(JavacNode builderType, java.util.List<Name> namesOfParameters, java.util.List<JCExpression> typesOfParameters, JCTree source) { - int len = namesOfParameters.size(); + public void generateBuilderFields(JavacNode builderType, java.util.List<BuilderFieldData> builderFields, JCTree source) { + int len = builderFields.size(); java.util.List<JavacNode> existing = new ArrayList<JavacNode>(); for (JavacNode child : builderType.down()) { if (child.getKind() == Kind.FIELD) existing.add(child); } - java.util.List<JavacNode>out = new ArrayList<JavacNode>(); - top: for (int i = len - 1; i >= 0; i--) { - Name name = namesOfParameters.get(i); - for (JavacNode exists : existing) { - Name n = ((JCVariableDecl) exists.get()).name; - if (n.equals(name)) { - out.add(exists); - continue top; + BuilderFieldData bfd = builderFields.get(i); + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + bfd.createdFields.addAll(bfd.singularData.getSingularizer().generateFields(bfd.singularData, builderType, source)); + } else { + for (JavacNode exists : existing) { + Name n = ((JCVariableDecl) exists.get()).name; + if (n.equals(bfd.name)) { + bfd.createdFields.add(exists); + continue top; + } } + JavacTreeMaker maker = builderType.getTreeMaker(); + JCModifiers mods = maker.Modifiers(Flags.PRIVATE); + JCVariableDecl newField = maker.VarDef(mods, bfd.name, cloneType(maker, bfd.type, source, builderType.getContext()), null); + bfd.createdFields.add(injectField(builderType, newField)); } - JavacTreeMaker maker = builderType.getTreeMaker(); - JCModifiers mods = maker.Modifiers(Flags.PRIVATE); - JCVariableDecl newField = maker.VarDef(mods, name, cloneType(maker, typesOfParameters.get(i), source, builderType.getContext()), null); - out.add(injectField(builderType, newField)); } - - Collections.reverse(out); - return out; } + public void makeSetterMethodsForBuilder(JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, boolean fluent, boolean chain) { + if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { + makeSimpleSetterMethodForBuilder(builderType, fieldNode.createdFields.get(0), source, fluent, chain); + } else { + fieldNode.singularData.getSingularizer().generateMethods(fieldNode.singularData, builderType, source.get(), fluent, chain); + } + } - public JCMethodDecl makeSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JavacNode source, boolean fluent, boolean chain) { + private void makeSimpleSetterMethodForBuilder(JavacNode builderType, JavacNode fieldNode, JavacNode source, boolean fluent, boolean chain) { Name fieldName = ((JCVariableDecl) fieldNode.get()).name; for (JavacNode child : builderType.down()) { if (child.getKind() != Kind.METHOD) continue; Name existingName = ((JCMethodDecl) child.get()).name; - if (existingName.equals(fieldName)) return null; + if (existingName.equals(fieldName)) return; } - boolean isBoolean = isBoolean(fieldNode); - String setterName = fluent ? fieldNode.getName() : toSetterName(builderType.getAst(), null, fieldNode.getName(), isBoolean); + String setterName = fluent ? fieldNode.getName() : HandlerUtil.buildAccessorName("set", fieldNode.getName()); - JavacTreeMaker maker = builderType.getTreeMaker(); - return HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + JavacTreeMaker maker = fieldNode.getTreeMaker(); + JCMethodDecl newMethod = HandleSetter.createSetter(Flags.PUBLIC, fieldNode, maker, setterName, chain, source, List.<JCAnnotation>nil(), List.<JCAnnotation>nil()); + injectMethod(builderType, newMethod); } public JavacNode findInnerClass(JavacNode parent, String name) { @@ -331,4 +449,59 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { JCClassDecl builder = maker.ClassDef(mods, tdParent.toName(builderClassName), copyTypeParams(maker, typeParams), null, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(tdParent, builder); } + + /** + * 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(JavacNode node) { + for (JavacNode child : node.down()) { + if (child.getKind() == Kind.ANNOTATION && annotationTypeMatches(Singular.class, child)) { + Name pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((JCVariableDecl) node.get()).name; + AnnotationValues<Singular> ann = createAnnotation(Singular.class, child); + deleteAnnotationIfNeccessary(child, Singular.class); + 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 = pluralName.toString(); + } else { + explicitSingular = autoSingularize(node.getName()); + if (explicitSingular == null) { + node.addError("Can't singularize this name; please specify the singular explicitly (i.e. @Singular(\"sheep\"))"); + explicitSingular = pluralName.toString(); + } + } + } + Name singularName = node.toName(explicitSingular); + + JCExpression type = null; + if (node.get() instanceof JCVariableDecl) { + type = ((JCVariableDecl) node.get()).vartype; + } + + String name = null; + List<JCExpression> typeArgs = List.nil(); + if (type instanceof JCTypeApply) { + typeArgs = ((JCTypeApply) type).arguments; + type = ((JCTypeApply) type).clazz; + } + + name = type.toString(); + + String targetFqn = JavacSingularsRecipes.get().toQualified(name); + JavacSingularizer singularizer = JavacSingularsRecipes.get().getSingularizer(targetFqn); + if (singularizer == null) { + node.addError("Lombok does not know how to create the singular-form builder methods for type '" + name + "'; they won't be generated."); + return null; + } + + return new SingularData(child, singularName, pluralName, typeArgs, targetFqn, singularizer); + } + } + + return null; + } } diff --git a/src/core/lombok/javac/handlers/HandleConstructor.java b/src/core/lombok/javac/handlers/HandleConstructor.java index 6043d1cb..c5b309c2 100644 --- a/src/core/lombok/javac/handlers/HandleConstructor.java +++ b/src/core/lombok/javac/handlers/HandleConstructor.java @@ -25,13 +25,13 @@ import static lombok.core.handlers.HandlerUtil.*; import static lombok.javac.handlers.JavacHandlerUtil.*; import lombok.AccessLevel; import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.ConfigurationKeys; import lombok.NoArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.core.AnnotationValues; import lombok.core.AST.Kind; import lombok.delombok.LombokOptionsFactory; -import lombok.experimental.Builder; import lombok.javac.JavacAnnotationHandler; import lombok.javac.JavacNode; import lombok.javac.JavacTreeMaker; diff --git a/src/core/lombok/javac/handlers/HandleLog.java b/src/core/lombok/javac/handlers/HandleLog.java index ee7268f7..06b7c7ef 100644 --- a/src/core/lombok/javac/handlers/HandleLog.java +++ b/src/core/lombok/javac/handlers/HandleLog.java @@ -105,7 +105,7 @@ public class HandleLog { maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (useStatic ? Flags.STATIC : 0)), typeNode.toName(logFieldName), loggerType, factoryMethodCall), source, typeNode.getContext()); - injectFieldSuppressWarnings(typeNode, fieldDecl); + injectFieldAndMarkGenerated(typeNode, fieldDecl); return true; } diff --git a/src/core/lombok/javac/handlers/HandleSynchronized.java b/src/core/lombok/javac/handlers/HandleSynchronized.java index fb6678e6..20e85d7e 100644 --- a/src/core/lombok/javac/handlers/HandleSynchronized.java +++ b/src/core/lombok/javac/handlers/HandleSynchronized.java @@ -99,7 +99,7 @@ public class HandleSynchronized extends JavacAnnotationHandler<Synchronized> { JCVariableDecl fieldDecl = recursiveSetGeneratedBy(maker.VarDef( maker.Modifiers(Flags.PRIVATE | Flags.FINAL | (isStatic ? Flags.STATIC : 0)), methodNode.toName(lockName), objectType, newObjectArray), ast, context); - injectFieldSuppressWarnings(methodNode.up(), fieldDecl); + injectFieldAndMarkGenerated(methodNode.up(), fieldDecl); } if (method.body == null) return; diff --git a/src/core/lombok/javac/handlers/HandleVal.java b/src/core/lombok/javac/handlers/HandleVal.java index 0230e1b0..9eadd750 100644 --- a/src/core/lombok/javac/handlers/HandleVal.java +++ b/src/core/lombok/javac/handlers/HandleVal.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010-2014 The Project Lombok Authors. + * Copyright (C) 2010-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 @@ -35,6 +35,7 @@ import lombok.javac.ResolutionResetNeeded; import org.mangosdk.spi.ProviderFor; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Symtab; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.JCTree.JCAnnotation; @@ -110,6 +111,16 @@ public class HandleVal extends JavacASTAdapter { } } else { type = local.init.type; + if (type.isErroneous()) { + try { + JavacResolution resolver = new JavacResolution(localNode.getContext()); + local.type = Symtab.instance(localNode.getContext()).unknownType; + type = ((JCExpression) resolver.resolveMethodMember(localNode).get(local.init)).type; + } catch (RuntimeException e) { + System.err.println("Exception while resolving: " + localNode); + throw e; + } + } } } else { if (rhsOfEnhancedForLoop.type == null) { diff --git a/src/core/lombok/javac/handlers/HandleWither.java b/src/core/lombok/javac/handlers/HandleWither.java index 8a7b49b2..5de18686 100644 --- a/src/core/lombok/javac/handlers/HandleWither.java +++ b/src/core/lombok/javac/handlers/HandleWither.java @@ -129,8 +129,8 @@ public class HandleWither extends JavacAnnotationHandler<Wither> { if (level == AccessLevel.NONE || node == null) return; - List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Setter(onMethod=", annotationNode); - List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Setter(onParam=", annotationNode); + List<JCAnnotation> onMethod = unboxAndRemoveAnnotationParameter(ast, "onMethod", "@Wither(onMethod=", annotationNode); + List<JCAnnotation> onParam = unboxAndRemoveAnnotationParameter(ast, "onParam", "@Wither(onParam=", annotationNode); switch (node.getKind()) { case FIELD: diff --git a/src/core/lombok/javac/handlers/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 6413e8ef..a073ac0d 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.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 @@ -41,6 +41,7 @@ import lombok.Data; import lombok.Getter; import lombok.core.AST.Kind; import lombok.core.AnnotationValues; +import lombok.core.LombokImmutableList; import lombok.core.AnnotationValues.AnnotationValue; import lombok.core.TypeResolver; import lombok.core.configuration.NullCheckExceptionType; @@ -68,7 +69,6 @@ import com.sun.tools.javac.tree.JCTree.JCExpressionStatement; import com.sun.tools.javac.tree.JCTree.JCFieldAccess; import com.sun.tools.javac.tree.JCTree.JCIdent; import com.sun.tools.javac.tree.JCTree.JCImport; -import com.sun.tools.javac.tree.JCTree.JCLiteral; import com.sun.tools.javac.tree.JCTree.JCMethodDecl; import com.sun.tools.javac.tree.JCTree.JCMethodInvocation; import com.sun.tools.javac.tree.JCTree.JCModifiers; @@ -242,51 +242,45 @@ public class JavacHandlerUtil { Map<String, AnnotationValue> values = new HashMap<String, AnnotationValue>(); JCAnnotation anno = (JCAnnotation) node.get(); List<JCExpression> arguments = anno.getArguments(); - for (Method m : type.getDeclaredMethods()) { - if (!Modifier.isPublic(m.getModifiers())) continue; - String name = m.getName(); + + for (JCExpression arg : arguments) { + String mName; + JCExpression rhs; java.util.List<String> raws = new ArrayList<String>(); java.util.List<Object> guesses = new ArrayList<Object>(); java.util.List<Object> expressions = new ArrayList<Object>(); final java.util.List<DiagnosticPosition> positions = new ArrayList<DiagnosticPosition>(); - boolean isExplicit = false; - for (JCExpression arg : arguments) { - String mName; - JCExpression rhs; - - if (arg instanceof JCAssign) { - JCAssign assign = (JCAssign) arg; - mName = assign.lhs.toString(); - rhs = assign.rhs; - } else { - rhs = arg; - mName = "value"; - } - - if (!mName.equals(name)) continue; - isExplicit = true; - if (rhs instanceof JCNewArray) { - List<JCExpression> elems = ((JCNewArray)rhs).elems; - for (JCExpression inner : elems) { - raws.add(inner.toString()); - expressions.add(inner); - guesses.add(calculateGuess(inner)); - positions.add(inner.pos()); - } - } else { - raws.add(rhs.toString()); - expressions.add(rhs); - guesses.add(calculateGuess(rhs)); - positions.add(rhs.pos()); + if (arg instanceof JCAssign) { + JCAssign assign = (JCAssign) arg; + mName = assign.lhs.toString(); + rhs = assign.rhs; + } else { + rhs = arg; + mName = "value"; + } + + if (rhs instanceof JCNewArray) { + List<JCExpression> elems = ((JCNewArray)rhs).elems; + for (JCExpression inner : elems) { + raws.add(inner.toString()); + expressions.add(inner); + guesses.add(calculateGuess(inner)); + positions.add(inner.pos()); } + } else { + raws.add(rhs.toString()); + expressions.add(rhs); + guesses.add(calculateGuess(rhs)); + positions.add(rhs.pos()); } - values.put(name, new AnnotationValue(node, raws, expressions, guesses, isExplicit) { + values.put(mName, new AnnotationValue(node, raws, expressions, guesses, true) { @Override public void setError(String message, int valueIdx) { if (valueIdx < 0) node.addError(message); else node.addError(message, positions.get(valueIdx)); } + @Override public void setWarning(String message, int valueIdx) { if (valueIdx < 0) node.addWarning(message); else node.addWarning(message, positions.get(valueIdx)); @@ -294,6 +288,21 @@ public class JavacHandlerUtil { }); } + for (Method m : type.getDeclaredMethods()) { + if (!Modifier.isPublic(m.getModifiers())) continue; + String name = m.getName(); + if (!values.containsKey(name)) { + values.put(name, new AnnotationValue(node, new ArrayList<String>(), new ArrayList<Object>(), new ArrayList<Object>(), false) { + @Override public void setError(String message, int valueIdx) { + node.addError(message); + } + @Override public void setWarning(String message, int valueIdx) { + node.addWarning(message); + } + }); + } + } + return new AnnotationValues<A>(type, values, node); } @@ -445,9 +454,9 @@ public class JavacHandlerUtil { return HandlerUtil.shouldReturnThis0(accessors, field.getAst()); } - public static JCExpression cloneSelfType(JavacNode field) { - JavacNode typeNode = field; - JavacTreeMaker maker = field.getTreeMaker(); + public static JCExpression cloneSelfType(JavacNode childOfType) { + JavacNode typeNode = childOfType; + JavacTreeMaker maker = childOfType.getTreeMaker(); while (typeNode != null && typeNode.getKind() != Kind.TYPE) typeNode = typeNode.up(); if (typeNode != null && typeNode.get() instanceof JCClassDecl) { JCClassDecl type = (JCClassDecl) typeNode.get(); @@ -808,7 +817,7 @@ public class JavacHandlerUtil { * The field carries the @{@link SuppressWarnings}("all") annotation. * Also takes care of updating the JavacAST. */ - public static void injectFieldSuppressWarnings(JavacNode typeNode, JCVariableDecl field) { + public static void injectFieldAndMarkGenerated(JavacNode typeNode, JCVariableDecl field) { injectField(typeNode, field, true); } @@ -821,10 +830,13 @@ public class JavacHandlerUtil { return injectField(typeNode, field, false); } - private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addSuppressWarnings) { + private static JavacNode injectField(JavacNode typeNode, JCVariableDecl field, boolean addGenerated) { JCClassDecl type = (JCClassDecl) typeNode.get(); - if (addSuppressWarnings) addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext()); + if (addGenerated) { + addSuppressWarningsAll(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext()); + addGenerated(field.mods, typeNode, field.pos, getGeneratedBy(field), typeNode.getContext()); + } List<JCTree> insertAfter = null; List<JCTree> insertBefore = type.defs; @@ -883,6 +895,7 @@ public class JavacHandlerUtil { } addSuppressWarningsAll(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext()); + addGenerated(method.mods, typeNode, method.pos, getGeneratedBy(method), typeNode.getContext()); type.defs = type.defs.append(method); typeNode.add(method, Kind.METHOD); @@ -898,6 +911,7 @@ public class JavacHandlerUtil { public static JavacNode injectType(JavacNode typeNode, final JCClassDecl type) { JCClassDecl typeDecl = (JCClassDecl) typeNode.get(); addSuppressWarningsAll(type.mods, typeNode, type.pos, getGeneratedBy(type), typeNode.getContext()); + addGenerated(type.mods, typeNode, type.pos, getGeneratedBy(type), typeNode.getContext()); typeDecl.defs = typeDecl.defs.append(type); return typeNode.add(type, Kind.TYPE); } @@ -938,20 +952,52 @@ public class JavacHandlerUtil { public static void addSuppressWarningsAll(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context) { if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateSuppressWarnings()) return; + addAnnotation(mods, node, pos, source, context, "java.lang.SuppressWarnings", node.getTreeMaker().Literal("all")); + + if (Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_FINDBUGS_SUPPRESSWARNINGS_ANNOTATIONS))) { + JavacTreeMaker maker = node.getTreeMaker(); + JCExpression arg = maker.Assign(maker.Ident(node.toName("justification")), maker.Literal("generated code")); + addAnnotation(mods, node, pos, source, context, "edu.umd.cs.findbugs.annotations.SuppressFBWarnings", arg); + } + } + + public static void addGenerated(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context) { + if (!LombokOptionsFactory.getDelombokOptions(context).getFormatPreferences().generateGenerated()) return; + + if (!Boolean.FALSE.equals(node.getAst().readConfiguration(ConfigurationKeys.ADD_GENERATED_ANNOTATIONS))) { + addAnnotation(mods, node, pos, source, context, "javax.annotation.Generated", node.getTreeMaker().Literal("lombok")); + } + } + + private static void addAnnotation(JCModifiers mods, JavacNode node, int pos, JCTree source, Context context, String annotationTypeFqn, JCExpression arg) { + boolean isJavaLangBased; + String simpleName; { + int idx = annotationTypeFqn.lastIndexOf('.'); + simpleName = idx == -1 ? annotationTypeFqn : annotationTypeFqn.substring(idx + 1); + + isJavaLangBased = idx == 9 && annotationTypeFqn.regionMatches(0, "java.lang.", 0, 10); + } + for (JCAnnotation ann : mods.annotations) { JCTree annType = ann.getAnnotationType(); Name lastPart = null; if (annType instanceof JCIdent) lastPart = ((JCIdent) annType).name; else if (annType instanceof JCFieldAccess) lastPart = ((JCFieldAccess) annType).name; - if (lastPart != null && lastPart.contentEquals("SuppressWarnings")) return; + if (lastPart != null && lastPart.contentEquals(simpleName)) return; } JavacTreeMaker maker = node.getTreeMaker(); - JCExpression suppressWarningsType = genJavaLangTypeRef(node, "SuppressWarnings"); - JCLiteral allLiteral = maker.Literal("all"); - suppressWarningsType.pos = pos; - allLiteral.pos = pos; - JCAnnotation annotation = recursiveSetGeneratedBy(maker.Annotation(suppressWarningsType, List.<JCExpression>of(allLiteral)), source, context); + JCExpression annType = isJavaLangBased ? genJavaLangTypeRef(node, simpleName) : chainDotsString(node, annotationTypeFqn); + annType.pos = pos; + if (arg != null) { + arg.pos = pos; + if (arg instanceof JCAssign) { + ((JCAssign) arg).lhs.pos = pos; + ((JCAssign) arg).rhs.pos = pos; + } + } + List<JCExpression> argList = arg != null ? List.of(arg) : List.<JCExpression>nil(); + JCAnnotation annotation = recursiveSetGeneratedBy(maker.Annotation(annType, argList), source, context); annotation.pos = pos; mods.annotations = mods.annotations.append(annotation); } @@ -985,6 +1031,17 @@ public class JavacHandlerUtil { return chainDots(node, -1, null, null, elems); } + public static JCExpression chainDots(JavacNode node, LombokImmutableList<String> elems) { + assert elems != null; + + JavacTreeMaker maker = node.getTreeMaker(); + JCExpression e = null; + for (String elem : elems) { + if (e == null) e = maker.Ident(node.toName(elem)); + else e = maker.Select(e, node.toName(elem)); + } + return e; + } /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} * is represented by a fold-left of {@code Select} nodes with the leftmost string represented by @@ -1013,7 +1070,6 @@ public class JavacHandlerUtil { return e; } - /** * In javac, dotted access of any kind, from {@code java.lang.String} to {@code var.methodName} diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java new file mode 100644 index 00000000..7fca01ae --- /dev/null +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.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.javac.handlers; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import lombok.core.LombokImmutableList; +import lombok.core.SpiLoadUtil; +import lombok.core.TypeLibrary; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; + +import com.sun.source.tree.Tree.Kind; +import com.sun.tools.javac.code.BoundKind; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.tree.JCTree.JCWildcard; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +public class JavacSingularsRecipes { + private static final JavacSingularsRecipes INSTANCE = new JavacSingularsRecipes(); + private final Map<String, JavacSingularizer> singularizers = new HashMap<String, JavacSingularizer>(); + private final TypeLibrary singularizableTypes = new TypeLibrary(); + + private JavacSingularsRecipes() { + 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, JavacSingularizer> map) throws IOException { + for (JavacSingularizer handler : SpiLoadUtil.findServices(JavacSingularizer.class, JavacSingularizer.class.getClassLoader())) { + for (String type : handler.getSupportedTypes()) { + JavacSingularizer existingSingularizer = map.get(type); + if (existingSingularizer != null) { + JavacSingularizer 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 JavacSingularsRecipes get() { + return INSTANCE; + } + + public String toQualified(String typeReference) { + return singularizableTypes.toQualified(typeReference); + } + + public JavacSingularizer getSingularizer(String fqn) { + return singularizers.get(fqn); + } + + public static final class SingularData { + private final JavacNode annotation; + private final Name singularName; + private final Name pluralName; + private final List<JCExpression> typeArgs; + private final String targetFqn; + private final JavacSingularizer singularizer; + + public SingularData(JavacNode annotation, Name singularName, Name pluralName, List<JCExpression> typeArgs, String targetFqn, JavacSingularizer singularizer) { + this.annotation = annotation; + this.singularName = singularName; + this.pluralName = pluralName; + this.typeArgs = typeArgs; + this.targetFqn = targetFqn; + this.singularizer = singularizer; + } + + public JavacNode getAnnotation() { + return annotation; + } + + public Name getSingularName() { + return singularName; + } + + public Name getPluralName() { + return pluralName; + } + + public List<JCExpression> getTypeArgs() { + return typeArgs; + } + + public String getTargetFqn() { + return targetFqn; + } + + public JavacSingularizer getSingularizer() { + return singularizer; + } + + public String getTargetSimpleType() { + int idx = targetFqn.lastIndexOf("."); + return idx == -1 ? targetFqn : targetFqn.substring(idx + 1); + } + } + + public static abstract class JavacSingularizer { + 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(JavacNode builderType, SingularData data) { + for (JavacNode child : builderType.down()) { + switch (child.getKind()) { + case FIELD: { + JCVariableDecl field = (JCVariableDecl) child.get(); + Name name = field.name; + if (name == null) break; + if (getGeneratedBy(field) != null) continue; + for (Name fieldToBeGenerated : listFieldsToBeGenerated(data, builderType)) { + if (!fieldToBeGenerated.equals(name)) 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: { + JCMethodDecl method = (JCMethodDecl) child.get(); + Name name = method.name; + if (name == null) break; + if (getGeneratedBy(method) != null) continue; + for (Name methodToBeGenerated : listMethodsToBeGenerated(data, builderType)) { + if (!methodToBeGenerated.equals(name)) 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 java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + return Collections.singletonList(data.pluralName); + } + + public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + Name p = data.pluralName; + Name s = data.singularName; + if (p.equals(s)) return Collections.singletonList(p); + return Arrays.asList(p, s); + } + + public abstract java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source); + public abstract void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain); + public abstract void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName); + + public boolean requiresCleaning() { + try { + return !getClass().getMethod("appendCleaningCode", SingularData.class, JavacNode.class, JCTree.class, ListBuffer.class).getDeclaringClass().equals(JavacSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> 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 JCExpression addTypeArgs(int count, boolean addExtends, JavacNode node, JCExpression type, List<JCExpression> typeArgs, JCTree source) { + JavacTreeMaker maker = node.getTreeMaker(); + List<JCExpression> clonedAndFixedTypeArgs = createTypeArgs(count, addExtends, node, typeArgs, source); + + return maker.TypeApply(type, clonedAndFixedTypeArgs); + } + + protected List<JCExpression> createTypeArgs(int count, boolean addExtends, JavacNode node, List<JCExpression> typeArgs, JCTree source) { + JavacTreeMaker maker = node.getTreeMaker(); + Context context = node.getContext(); + + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return List.nil(); + ListBuffer<JCExpression> arguments = new ListBuffer<JCExpression>(); + + if (typeArgs != null) for (JCExpression orig : typeArgs) { + if (!addExtends) { + if (orig.getKind() == Kind.UNBOUNDED_WILDCARD || orig.getKind() == Kind.SUPER_WILDCARD) { + arguments.append(genJavaLangTypeRef(node, "Object")); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + JCExpression inner; + try { + inner = (JCExpression) ((JCWildcard) orig).inner; + } catch (Exception e) { + inner = genJavaLangTypeRef(node, "Object"); + } + arguments.append(cloneType(maker, inner, source, context)); + } else { + arguments.append(cloneType(maker, orig, source, context)); + } + } else { + if (orig.getKind() == Kind.UNBOUNDED_WILDCARD || orig.getKind() == Kind.SUPER_WILDCARD) { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + arguments.append(cloneType(maker, orig, source, context)); + } else { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.EXTENDS), cloneType(maker, orig, source, context))); + } + } + if (--count == 0) break; + } + + while (count-- > 0) { + if (addExtends) { + arguments.append(maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null)); + } else { + arguments.append(genJavaLangTypeRef(node, "Object")); + } + } + + return arguments.toList(); + } + + /** Generates 'this.<em>name</em>.size()' as an expression; if nullGuard is true, it's this.name == null ? 0 : this.name.size(). */ + protected JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name, boolean nullGuard) { + Name thisName = builderType.toName("this"); + JCExpression fn = maker.Select(maker.Select(maker.Ident(thisName), name), builderType.toName("size")); + JCExpression sizeInvoke = maker.Apply(List.<JCExpression>nil(), fn, List.<JCExpression>nil()); + if (nullGuard) { + JCExpression isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(thisName), name), maker.Literal(CTC_BOT, 0)); + return maker.Conditional(isNull, maker.Literal(CTC_INT, 0), sizeInvoke); + } + return sizeInvoke; + } + + protected JCExpression cloneParamType(int index, JavacTreeMaker maker, List<JCExpression> typeArgs, JavacNode builderType, JCTree source) { + if (typeArgs == null || typeArgs.size() <= index) { + return genJavaLangTypeRef(builderType, "Object"); + } else { + JCExpression originalType = typeArgs.get(index); + if (originalType.getKind() == Kind.UNBOUNDED_WILDCARD || originalType.getKind() == Kind.SUPER_WILDCARD) { + return genJavaLangTypeRef(builderType, "Object"); + } else if (originalType.getKind() == Kind.EXTENDS_WILDCARD) { + try { + return cloneType(maker, (JCExpression) ((JCWildcard) originalType).inner, source, builderType.getContext()); + } catch (Exception e) { + return genJavaLangTypeRef(builderType, "Object"); + } + } else { + return cloneType(maker, originalType, source, builderType.getContext()); + } + } + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java new file mode 100644 index 00000000..0700e2e5 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaMapSingularizer.java @@ -0,0 +1,45 @@ +/* + * 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.javac.handlers.singulars; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaMapSingularizer extends JavacGuavaSingularizer { + // TODO cgcc.ImmutableMultimap, cgcc.ImmutableListMultimap, cgcc.ImmutableSetMultimap + // TODO cgcc.ImmutableClassToInstanceMap + // TODO cgcc.ImmutableRangeMap + + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of( + "com.google.common.collect.ImmutableMap", + "com.google.common.collect.ImmutableBiMap", + "com.google.common.collect.ImmutableSortedMap"); + } + + @Override protected boolean isMap() { + return true; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java new file mode 100644 index 00000000..2e404ca8 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSetListSingularizer.java @@ -0,0 +1,45 @@ +/* + * 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.javac.handlers.singulars; + +import lombok.core.LombokImmutableList; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +import org.mangosdk.spi.ProviderFor; + +@ProviderFor(JavacSingularizer.class) +public class JavacGuavaSetListSingularizer extends JavacGuavaSingularizer { + // TODO com.google.common.collect.ImmutableTable + // TODO com.google.common.collect.ImmutableRangeSet + // TODO com.google.common.collect.ImmutableMultiset and com.google.common.collect.ImmutableSortedMultiset + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of( + "com.google.common.collect.ImmutableCollection", + "com.google.common.collect.ImmutableList", + "com.google.common.collect.ImmutableSet", + "com.google.common.collect.ImmutableSortedSet"); + } + + @Override protected boolean isMap() { + return false; + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.java new file mode 100644 index 00000000..e23837dc --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacGuavaSingularizer.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.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Collections; + +import lombok.core.GuavaTypeMap; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +abstract class JavacGuavaSingularizer extends JavacSingularizer { + protected String getSimpleTargetTypeName(SingularData data) { + return GuavaTypeMap.getGuavaTypeName(data.getTargetFqn()); + } + + protected String getBuilderMethodName(SingularData data) { + String simpleTypeName = getSimpleTargetTypeName(data); + if ("ImmutableSortedSet".equals(simpleTypeName) || "ImmutableSortedMap".equals(simpleTypeName)) return "naturalOrder"; + return "builder"; + } + + protected abstract boolean isMap(); + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression type = JavacHandlerUtil.chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), "Builder"); + type = addTypeArgs(isMap() ? 2 : 1, false, builderType, type, data.getTypeArgs(), source); + + JCVariableDecl buildField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), data.getPluralName(), type, null); + return Collections.singletonList(injectField(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + } + + void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + boolean mapMode = isMap(); + + Name keyName = !mapMode ? data.getSingularName() : builderType.toName(data.getSingularName() + "$key"); + Name valueName = !mapMode ? null : builderType.toName(data.getSingularName() + "$value"); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, mapMode, source)); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), mapMode ? "put" : "add"); + List<JCExpression> invokeAddExpr; + if (mapMode) { + invokeAddExpr = List.<JCExpression>of(maker.Ident(keyName), maker.Ident(valueName)); + } else { + invokeAddExpr = List.<JCExpression>of(maker.Ident(keyName)); + } + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, invokeAddExpr); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name methodName = data.getSingularName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) methodName = builderType.toName(HandlerUtil.buildAccessorName(mapMode ? "put" : "add", methodName.toString())); + List<JCVariableDecl> params; + if (mapMode) { + JCExpression keyType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCExpression valueType = cloneParamType(1, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl paramKey = maker.VarDef(maker.Modifiers(paramFlags), keyName, keyType, null); + JCVariableDecl paramValue = maker.VarDef(maker.Modifiers(paramFlags), valueName, valueType, null); + params = List.of(paramKey, paramValue); + } else { + JCExpression paramType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + params = List.of(maker.VarDef(maker.Modifiers(paramFlags), data.getSingularName(), paramType, null)); + } + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, params, thrown, body, null); + injectMethod(builderType, method); + } + + protected void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + boolean mapMode = isMap(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, mapMode, source)); + JCExpression thisDotFieldDotAddAll = chainDots(builderType, "this", data.getPluralName().toString(), mapMode ? "putAll" : "addAll"); + JCExpression invokeAddAll = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAddAll, List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(invokeAddAll)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name methodName = data.getPluralName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) methodName = builderType.toName(HandlerUtil.buildAccessorName(mapMode ? "putAll" : "addAll", methodName.toString())); + JCExpression paramType; + if (mapMode) { + paramType = chainDots(builderType, "java", "util", "Map"); + } else { + paramType = genJavaLangTypeRef(builderType, "Iterable"); + } + paramType = addTypeArgs(mapMode ? 2 : 1, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, methodName, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + JavacTreeMaker maker = builderType.getTreeMaker(); + List<JCExpression> jceBlank = List.nil(); + boolean mapMode = isMap(); + + JCExpression varType = chainDotsString(builderType, data.getTargetFqn()); + varType = addTypeArgs(mapMode ? 2 : 1, false, builderType, varType, data.getTypeArgs(), source); + + JCExpression empty; { + //ImmutableX.of() + JCExpression emptyMethod = chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), "of"); + List<JCExpression> invokeTypeArgs = createTypeArgs(mapMode ? 2 : 1, false, builderType, data.getTypeArgs(), source); + empty = maker.Apply(invokeTypeArgs, emptyMethod, jceBlank); + } + + JCExpression invokeBuild; { + //this.pluralName.build(); + invokeBuild = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "build"), jceBlank); + } + + JCExpression isNull; { + //this.pluralName == null + isNull = maker.Binary(CTC_EQUAL, maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()), maker.Literal(CTC_BOT, null)); + } + + JCExpression init = maker.Conditional(isNull, empty, invokeBuild); // this.pluralName == null ? ImmutableX.of() : this.pluralName.build() + + JCStatement jcs = maker.VarDef(maker.Modifiers(0), data.getPluralName(), varType, init); + statements.append(jcs); + } + + protected JCStatement createConstructBuilderVarIfNeeded(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression thisDotField2 = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + JCExpression cond = maker.Binary(CTC_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + + JCExpression create = maker.Apply(jceBlank, chainDots(builderType, "com", "google", "common", "collect", getSimpleTargetTypeName(data), getBuilderMethodName(data)), jceBlank); + JCStatement thenPart = maker.Exec(maker.Assign(thisDotField2, create)); + + return maker.If(cond, thenPart, null); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java new file mode 100644 index 00000000..6f8ff705 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSetSingularizer.java @@ -0,0 +1,138 @@ +/* + * 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.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Collections; + +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +abstract class JavacJavaUtilListSetSingularizer extends JavacJavaUtilSingularizer { + @Override public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listFieldsToBeGenerated(data, builderType); + } + + return super.listFieldsToBeGenerated(data, builderType); + } + + @Override public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + if (useGuavaInstead(builderType)) { + return guavaListSetSingularizer.generateFields(data, builderType, source); + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs(), source); + + JCVariableDecl buildField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), data.getPluralName(), type, null); + return Collections.singletonList(injectField(builderType, buildField)); + } + + @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.generateMethods(data, builderType, source, fluent, chain); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + Name thisName = builderType.toName("this"); + + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(thisName)) : null; + generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + } + + void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, false, source)); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getSingularName()))); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getSingularName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("add", name.toString())); + JCExpression paramType = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getSingularName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } + + void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, false, source)); + JCExpression thisDotFieldDotAdd = chainDots(builderType, "this", data.getPluralName().toString(), "addAll"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotFieldDotAdd, List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(invokeAdd)); + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getPluralName(); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("addAll", name.toString())); + JCExpression paramType = chainDots(builderType, "java", "util", "Collection"); + paramType = addTypeArgs(1, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), thrown, body, null); + injectMethod(builderType, method); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java new file mode 100644 index 00000000..9ec77e78 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilListSingularizer.java @@ -0,0 +1,120 @@ +/* + * 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.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilListSingularizer extends JavacJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.List", "java.util.Collection", "java.lang.Iterable"); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + List<JCExpression> jceBlank = List.nil(); + ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); + + /* case 0: (empty); break; */ { + JCStatement assignStat; { + // pluralName = java.util.Collections.emptyList(); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "emptyList"), jceBlank); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase emptyCase = maker.Case(maker.Literal(CTC_INT, 0), List.of(assignStat, breakStat)); + cases.append(emptyCase); + } + + /* case 1: (singletonList); break; */ { + JCStatement assignStat; { + // pluralName = java.util.Collections.singletonList(this.pluralName.get(0)); + JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName().toString(), "get"), List.of(zeroLiteral)); + List<JCExpression> args = List.of(arg); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "singletonList"), args); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase singletonCase = maker.Case(maker.Literal(CTC_INT, 1), List.of(assignStat, breakStat)); + cases.append(singletonCase); + } + + /* default: Create with right size, then addAll */ { + List<JCStatement> defStats = createListCopy(maker, data, builderType, source); + JCCase defaultCase = maker.Case(null, defStats); + cases.append(defaultCase); + } + + JCStatement switchStat = maker.Switch(getSize(maker, builderType, data.getPluralName(), true), cases.toList()); + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs(), source); + JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); + statements.append(varDefStat); + statements.append(switchStat); + } + + private List<JCStatement> createListCopy(JavacTreeMaker maker, SingularData data, JavacNode builderType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + Name thisName = builderType.toName("this"); + + JCExpression argToUnmodifiable; { + // new java.util.ArrayList<Generics>(this.pluralName); + List<JCExpression> constructorArgs = List.nil(); + JCExpression thisDotPluralName = maker.Select(maker.Ident(thisName), data.getPluralName()); + constructorArgs = List.<JCExpression>of(thisDotPluralName); + JCExpression targetTypeExpr = chainDots(builderType, "java", "util", "ArrayList"); + targetTypeExpr = addTypeArgs(1, false, builderType, targetTypeExpr, data.getTypeArgs(), source); + argToUnmodifiable = maker.NewClass(null, jceBlank, targetTypeExpr, constructorArgs, null); + } + + JCStatement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(-newlist-); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "unmodifiableList"), List.of(argToUnmodifiable)); + unmodifiableStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + + return List.of(unmodifiableStat); + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java new file mode 100644 index 00000000..ed91698d --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilMapSingularizer.java @@ -0,0 +1,198 @@ +/* + * 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.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import java.util.Arrays; + +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacHandlerUtil; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import org.mangosdk.spi.ProviderFor; + +import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCBlock; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCMethodDecl; +import com.sun.tools.javac.tree.JCTree.JCModifiers; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.tree.JCTree.JCTypeParameter; +import com.sun.tools.javac.tree.JCTree.JCVariableDecl; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilMapSingularizer extends JavacJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Map", "java.util.SortedMap", "java.util.NavigableMap"); + } + + @Override public java.util.List<Name> listFieldsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listFieldsToBeGenerated(data, builderType); + } + + String p = data.getPluralName().toString(); + return Arrays.asList(builderType.toName(p + "$key"), builderType.toName(p + "$value")); + } + + @Override public java.util.List<Name> listMethodsToBeGenerated(SingularData data, JavacNode builderType) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.listMethodsToBeGenerated(data, builderType); + } + + return super.listMethodsToBeGenerated(data, builderType); + } + + @Override public java.util.List<JavacNode> generateFields(SingularData data, JavacNode builderType, JCTree source) { + if (useGuavaInstead(builderType)) { + return guavaMapSingularizer.generateFields(data, builderType, source); + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + JCVariableDecl buildKeyField; { + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + type = addTypeArgs(1, false, builderType, type, data.getTypeArgs(), source); + buildKeyField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName(data.getPluralName() + "$key"), type, null); + } + + JCVariableDecl buildValueField; { + JCExpression type = JavacHandlerUtil.chainDots(builderType, "java", "util", "ArrayList"); + List<JCExpression> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.size() > 1) tArgs = tArgs.tail; + else tArgs = List.nil(); + type = addTypeArgs(1, false, builderType, type, tArgs, source); + buildValueField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), builderType.toName(data.getPluralName() + "$value"), type, null); + } + + JavacNode valueFieldNode = injectField(builderType, buildValueField); + JavacNode keyFieldNode = injectField(builderType, buildKeyField); + + return Arrays.asList(keyFieldNode, valueFieldNode); + } + + @Override public void generateMethods(SingularData data, JavacNode builderType, JCTree source, boolean fluent, boolean chain) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.generateMethods(data, builderType, source, fluent, chain); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + JCExpression returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + JCStatement returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generateSingularMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + + returnType = chain ? cloneSelfType(builderType) : maker.Type(createVoidType(maker, CTC_VOID)); + returnStatement = chain ? maker.Return(maker.Ident(builderType.toName("this"))) : null; + generatePluralMethod(maker, returnType, returnStatement, data, builderType, source, fluent); + } + + private void generateSingularMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> thrown = List.nil(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, true, source)); + Name keyName = builderType.toName(data.getSingularName().toString() + "Key"); + Name valueName = builderType.toName(data.getSingularName().toString() + "Value"); + /* this.pluralname$key.add(singularnameKey); */ { + JCExpression thisDotKeyFieldDotAdd = chainDots(builderType, "this", data.getPluralName() + "$key", "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotKeyFieldDotAdd, List.<JCExpression>of(maker.Ident(keyName))); + statements.append(maker.Exec(invokeAdd)); + } + /* this.pluralname$value.add(singularnameValue); */ { + JCExpression thisDotValueFieldDotAdd = chainDots(builderType, "this", data.getPluralName() + "$value", "add"); + JCExpression invokeAdd = maker.Apply(List.<JCExpression>nil(), thisDotValueFieldDotAdd, List.<JCExpression>of(maker.Ident(valueName))); + statements.append(maker.Exec(invokeAdd)); + } + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + + Name name = data.getSingularName(); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("put", name.toString())); + JCExpression paramTypeKey = cloneParamType(0, maker, data.getTypeArgs(), builderType, source); + JCExpression paramTypeValue = cloneParamType(1, maker, data.getTypeArgs(), builderType, source); + JCVariableDecl paramKey = maker.VarDef(maker.Modifiers(paramFlags), keyName, paramTypeKey, null); + JCVariableDecl paramValue = maker.VarDef(maker.Modifiers(paramFlags), valueName, paramTypeValue, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(paramKey, paramValue), thrown, body, null); + injectMethod(builderType, method); + } + + private void generatePluralMethod(JavacTreeMaker maker, JCExpression returnType, JCStatement returnStatement, SingularData data, JavacNode builderType, JCTree source, boolean fluent) { + List<JCTypeParameter> typeParams = List.nil(); + List<JCExpression> jceBlank = List.nil(); + JCModifiers mods = maker.Modifiers(Flags.PUBLIC); + ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); + statements.append(createConstructBuilderVarIfNeeded(maker, data, builderType, true, source)); + long paramFlags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, builderType.getContext()); + long baseFlags = JavacHandlerUtil.addFinalIfNeeded(0, builderType.getContext()); + Name entryName = builderType.toName("$lombokEntry"); + + JCExpression forEachType = chainDots(builderType, "java", "util", "Map", "Entry"); + forEachType = addTypeArgs(2, true, builderType, forEachType, data.getTypeArgs(), source); + JCExpression keyArg = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(entryName), builderType.toName("getKey")), List.<JCExpression>nil()); + JCExpression valueArg = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(entryName), builderType.toName("getValue")), List.<JCExpression>nil()); + JCExpression addKey = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "this", data.getPluralName() + "$key", "add"), List.of(keyArg)); + JCExpression addValue = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "this", data.getPluralName() + "$value", "add"), List.of(valueArg)); + JCBlock forEachBody = maker.Block(0, List.<JCStatement>of(maker.Exec(addKey), maker.Exec(addValue))); + JCExpression entrySetInvocation = maker.Apply(jceBlank, maker.Select(maker.Ident(data.getPluralName()), builderType.toName("entrySet")), jceBlank); + JCStatement forEach = maker.ForeachLoop(maker.VarDef(maker.Modifiers(baseFlags), entryName, forEachType, null), entrySetInvocation, forEachBody); + statements.append(forEach); + + if (returnStatement != null) statements.append(returnStatement); + JCBlock body = maker.Block(0, statements.toList()); + Name name = data.getPluralName(); + if (!fluent) name = builderType.toName(HandlerUtil.buildAccessorName("putAll", name.toString())); + JCExpression paramType = chainDots(builderType, "java", "util", "Map"); + paramType = addTypeArgs(2, true, builderType, paramType, data.getTypeArgs(), source); + JCVariableDecl param = maker.VarDef(maker.Modifiers(paramFlags), data.getPluralName(), paramType, null); + JCMethodDecl method = maker.MethodDef(mods, name, returnType, typeParams, List.of(param), jceBlank, body, null); + injectMethod(builderType, method); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaMapSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + if (data.getTargetFqn().equals("java.util.Map")) { + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, true, "emptyMap", "singletonMap", "LinkedHashMap", source)); + } else { + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, true, true, false, true, "TreeMap", source)); + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java new file mode 100644 index 00000000..317233cb --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java @@ -0,0 +1,57 @@ +/* + * 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.javac.handlers.singulars; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +@ProviderFor(JavacSingularizer.class) +public class JavacJavaUtilSetSingularizer extends JavacJavaUtilListSetSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + if (useGuavaInstead(builderType)) { + guavaListSetSingularizer.appendBuildCode(data, builderType, source, statements, targetVariableName); + return; + } + + JavacTreeMaker maker = builderType.getTreeMaker(); + + if (data.getTargetFqn().equals("java.util.Set")) { + statements.appendList(createJavaUtilSetMapInitialCapacitySwitchStatements(maker, data, builderType, false, "emptySet", "singleton", "LinkedHashSet", source)); + } else { + statements.appendList(createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, false, true, false, true, "TreeSet", source)); + } + } +} diff --git a/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java new file mode 100644 index 00000000..f419a014 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -0,0 +1,193 @@ +/* + * 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.javac.handlers.singulars; + +import static lombok.javac.Javac.*; +import static lombok.javac.handlers.JavacHandlerUtil.*; + +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.JCTree.JCCase; +import com.sun.tools.javac.tree.JCTree.JCExpression; +import com.sun.tools.javac.tree.JCTree.JCStatement; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; +import com.sun.tools.javac.util.Name; + +import lombok.ConfigurationKeys; +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; +import lombok.javac.handlers.JavacSingularsRecipes.SingularData; + +abstract class JavacJavaUtilSingularizer extends JavacSingularizer { + protected final JavacSingularizer guavaListSetSingularizer = new JavacGuavaSetListSingularizer(); + protected final JavacSingularizer guavaMapSingularizer = new JavacGuavaMapSingularizer(); + + protected boolean useGuavaInstead(JavacNode node) { + return Boolean.TRUE.equals(node.getAst().readConfiguration(ConfigurationKeys.SINGULAR_USE_GUAVA)); + } + + protected List<JCStatement> createJavaUtilSetMapInitialCapacitySwitchStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, String emptyCollectionMethod, String singletonCollectionMethod, String targetType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + ListBuffer<JCCase> cases = new ListBuffer<JCCase>(); + + if (emptyCollectionMethod != null) { // case 0: (empty); break; + JCStatement assignStat; { + // pluralName = java.util.Collections.emptyCollectionMethod(); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", emptyCollectionMethod), jceBlank); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase emptyCase = maker.Case(maker.Literal(CTC_INT, 0), List.of(assignStat, breakStat)); + cases.append(emptyCase); + } + + if (singletonCollectionMethod != null) { // case 1: (singleton); break; + JCStatement assignStat; { + // !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)); + JCExpression zeroLiteral = maker.Literal(CTC_INT, 0); + JCExpression arg = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$key" : ""), "get"), List.of(zeroLiteral)); + List<JCExpression> args; + if (mapMode) { + JCExpression zeroLiteralClone = maker.Literal(CTC_INT, 0); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + (mapMode ? "$value" : ""), "get"), List.of(zeroLiteralClone)); + args = List.of(arg, arg2); + } else { + args = List.of(arg); + } + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", singletonCollectionMethod), args); + assignStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + JCStatement breakStat = maker.Break(null); + JCCase singletonCase = maker.Case(maker.Literal(CTC_INT, 1), List.of(assignStat, breakStat)); + cases.append(singletonCase); + } + + { // default: + List<JCStatement> statements = createJavaUtilSimpleCreationAndFillStatements(maker, data, builderType, mapMode, false, true, emptyCollectionMethod == null, targetType, source); + JCCase defaultCase = maker.Case(null, statements); + cases.append(defaultCase); + } + + JCStatement switchStat = maker.Switch(getSize(maker, builderType, mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(), true), cases.toList()); + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); + JCStatement varDefStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, null); + return List.of(varDefStat, switchStat); + } + + protected JCStatement createConstructBuilderVarIfNeeded(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + + Name v1Name = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); + Name v2Name = mapMode ? builderType.toName(data.getPluralName() + "$value") : null; + JCExpression thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v1Name); + JCExpression cond = maker.Binary(CTC_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v1Name); + JCExpression v1Type = chainDots(builderType, "java", "util", "ArrayList"); + v1Type = addTypeArgs(1, false, builderType, v1Type, data.getTypeArgs(), source); + JCExpression constructArrayList = maker.NewClass(null, jceBlank, v1Type, jceBlank, null); + JCStatement initV1 = maker.Exec(maker.Assign(thisDotField, constructArrayList)); + JCStatement thenPart; + if (mapMode) { + thisDotField = maker.Select(maker.Ident(builderType.toName("this")), v2Name); + JCExpression v2Type = chainDots(builderType, "java", "util", "ArrayList"); + List<JCExpression> tArgs = data.getTypeArgs(); + if (tArgs != null && tArgs.tail != null) tArgs = tArgs.tail; + else tArgs = List.nil(); + v2Type = addTypeArgs(1, false, builderType, v2Type, tArgs, source); + constructArrayList = maker.NewClass(null, jceBlank, v2Type, jceBlank, null); + JCStatement initV2 = maker.Exec(maker.Assign(thisDotField, constructArrayList)); + thenPart = maker.Block(0, List.of(initV1, initV2)); + } else { + thenPart = initV1; + } + return maker.If(cond, thenPart, null); + } + + protected List<JCStatement> createJavaUtilSimpleCreationAndFillStatements(JavacTreeMaker maker, SingularData data, JavacNode builderType, boolean mapMode, boolean defineVar, boolean addInitialCapacityArg, boolean nullGuard, String targetType, JCTree source) { + List<JCExpression> jceBlank = List.nil(); + Name thisName = builderType.toName("this"); + + JCStatement createStat; { + // pluralName = new java.util.TargetType(initialCap); + List<JCExpression> constructorArgs = List.nil(); + if (addInitialCapacityArg) { + Name varName = mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName(); + // 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 + JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, varName, nullGuard), maker.Literal(CTC_INT, 0x40000000)); + JCExpression integerMaxValue = genJavaLangTypeRef(builderType, "Integer", "MAX_VALUE"); + JCExpression sizeFormulaLeft = maker.Binary(CTC_PLUS, maker.Literal(CTC_INT, 1), getSize(maker, builderType, varName, nullGuard)); + JCExpression sizeFormulaRightLeft = maker.Binary(CTC_MINUS, getSize(maker, builderType, varName, nullGuard), maker.Literal(CTC_INT, 3)); + JCExpression sizeFormulaRight = maker.Binary(CTC_DIV, sizeFormulaRightLeft, maker.Literal(CTC_INT, 3)); + JCExpression sizeFormula = maker.Binary(CTC_PLUS, sizeFormulaLeft, sizeFormulaRight); + constructorArgs = List.<JCExpression>of(maker.Conditional(lessThanCutoff, sizeFormula, integerMaxValue)); + } + + JCExpression targetTypeExpr = chainDots(builderType, "java", "util", targetType); + targetTypeExpr = addTypeArgs(mapMode ? 2 : 1, false, builderType, targetTypeExpr, data.getTypeArgs(), source); + JCExpression constructorCall = maker.NewClass(null, jceBlank, targetTypeExpr, constructorArgs, null); + if (defineVar) { + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(mapMode ? 2 : 1, false, builderType, localShadowerType, data.getTypeArgs(), source); + createStat = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, constructorCall); + } else { + createStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), constructorCall)); + } + } + + JCStatement 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)); + Name ivar = builderType.toName("$i"); + Name keyVarName = builderType.toName(data.getPluralName() + "$key"); + JCExpression pluralnameDotPut = maker.Select(maker.Ident(data.getPluralName()), builderType.toName("put")); + JCExpression arg1 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$key", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCExpression arg2 = maker.Apply(jceBlank, chainDots(builderType, "this", data.getPluralName() + "$value", "get"), List.<JCExpression>of(maker.Ident(ivar))); + JCStatement putStatement = maker.Exec(maker.Apply(jceBlank, pluralnameDotPut, List.of(arg1, arg2))); + JCStatement forInit = maker.VarDef(maker.Modifiers(0), ivar, maker.TypeIdent(CTC_INT), maker.Literal(CTC_INT, 0)); + JCExpression checkExpr = maker.Binary(CTC_LESS_THAN, maker.Ident(ivar), getSize(maker, builderType, keyVarName, nullGuard)); + JCExpression incrementExpr = maker.Unary(CTC_POSTINC, maker.Ident(ivar)); + fillStat = maker.ForLoop(List.of(forInit), checkExpr, List.of(maker.Exec(incrementExpr)), putStatement); + } else { + // pluralname.addAll(this.pluralname); + JCExpression thisDotPluralName = maker.Select(maker.Ident(thisName), data.getPluralName()); + fillStat = maker.Exec(maker.Apply(jceBlank, maker.Select(maker.Ident(data.getPluralName()), builderType.toName("addAll")), List.of(thisDotPluralName))); + } + if (nullGuard) { + JCExpression thisDotField = maker.Select(maker.Ident(thisName), mapMode ? builderType.toName(data.getPluralName() + "$key") : data.getPluralName()); + JCExpression nullCheck = maker.Binary(CTC_NOT_EQUAL, thisDotField, maker.Literal(CTC_BOT, null)); + fillStat = maker.If(nullCheck, fillStat, null); + } + } + JCStatement unmodifiableStat; { + // pluralname = Collections.unmodifiableInterfaceType(pluralname); + JCExpression arg = maker.Ident(data.getPluralName()); + JCExpression invoke = maker.Apply(jceBlank, chainDots(builderType, "java", "util", "Collections", "unmodifiable" + data.getTargetSimpleType()), List.of(arg)); + unmodifiableStat = maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), invoke)); + } + + return List.of(createStat, fillStat, unmodifiableStat); + } +} |
