diff options
Diffstat (limited to 'src/core/lombok/javac/handlers')
5 files changed, 655 insertions, 90 deletions
diff --git a/src/core/lombok/javac/handlers/HandleBuilder.java b/src/core/lombok/javac/handlers/HandleBuilder.java index 08acc268..86812193 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; @@ -48,14 +50,18 @@ 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.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.*; @@ -66,9 +72,18 @@ import static lombok.javac.JavacTreeMaker.TypeTag.*; public class HandleBuilder extends JavacAnnotationHandler<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; + + JavacNode mainCreatedField; + } + @Override public void handle(AnnotationValues<Builder> annotation, JCAnnotation ast, JavacNode annotationNode) { handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder"); @@ -92,20 +107,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; @@ -119,12 +135,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); @@ -133,7 +151,8 @@ 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; } @@ -141,20 +160,21 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { 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; @@ -189,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); } } @@ -201,11 +226,21 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { } else { sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(builderType, annotationNode); } - java.util.List<JavacNode> fieldNodes = addFieldsToBuilder(builderType, namesOfParameters, typesOfParameters, ast); - java.util.List<JCMethodDecl> newMethods = new ArrayList<JCMethodDecl>(); - for (JavacNode fieldNode : fieldNodes) { - JCMethodDecl newMethod = makeSetterMethodForBuilder(builderType, fieldNode, annotationNode, fluent, chain); - if (newMethod != null) newMethods.add(newMethod); + + for (BuilderFieldData bfd : builderFields) { + if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { + if (bfd.singularData.getSingularizer().requiresCleaning()) { + addCleaning = true; + break; + } + } + } + + 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) { @@ -213,38 +248,94 @@ public class HandleBuilder extends JavacAnnotationHandler<Builder> { if (cd != null) injectMethod(builderType, cd); } - for (JCMethodDecl newMethod : newMethods) injectMethod(builderType, newMethod); + for (BuilderFieldData builderFieldData : builderFields) { + makeSetterMethodForBuilder(builderType, builderFieldData, 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) { + JavacNode mcf = bfd.mainCreatedField; + if (mcf != null) fieldNodes.add(mcf); + } 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.Ident(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.Ident(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.Ident(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) { @@ -254,13 +345,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); } @@ -280,50 +371,57 @@ 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.mainCreatedField = 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.mainCreatedField = 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.mainCreatedField = 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 makeSetterMethodForBuilder(JavacNode builderType, BuilderFieldData fieldNode, JavacNode source, boolean fluent, boolean chain) { + if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { + makeSimpleSetterMethodForBuilder(builderType, fieldNode.mainCreatedField, 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() : toSetterName(fieldNode.getAst(), null, fieldNode.getName(), isBoolean); - 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) { @@ -341,4 +439,54 @@ 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()) { + 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/JavacHandlerUtil.java b/src/core/lombok/javac/handlers/JavacHandlerUtil.java index 615a18d9..8a8c5acd 100644 --- a/src/core/lombok/javac/handlers/JavacHandlerUtil.java +++ b/src/core/lombok/javac/handlers/JavacHandlerUtil.java @@ -243,51 +243,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)); @@ -295,6 +289,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); } diff --git a/src/core/lombok/javac/handlers/JavacSingularsRecipes.java b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java new file mode 100644 index 00000000..6a8a8f7c --- /dev/null +++ b/src/core/lombok/javac/handlers/JavacSingularsRecipes.java @@ -0,0 +1,142 @@ +/* + * 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 java.io.IOException; +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 com.sun.tools.javac.tree.JCTree; +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; + +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 static abstract class JavacSingularizer { + public abstract LombokImmutableList<String> getSupportedTypes(); + + public abstract 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", ListBuffer.class).getDeclaringClass().equals(JavacSingularizer.class); + } catch (NoSuchMethodException e) { + return false; + } + } + + public void appendCleaningCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements) { + } + } +} 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..c0f79275 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSetSingularizer.java @@ -0,0 +1,171 @@ +/* + * 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.handlers.JavacHandlerUtil.*; + +import org.mangosdk.spi.ProviderFor; + +import lombok.core.LombokImmutableList; +import lombok.core.handlers.HandlerUtil; +import static lombok.javac.Javac.*; +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.source.tree.Tree.Kind; +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.JCFieldAccess; +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.tree.JCTree.JCWildcard; +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 JavacJavaUtilSetSingularizer extends JavacJavaUtilSingularizer { + @Override public LombokImmutableList<String> getSupportedTypes() { + return LombokImmutableList.of("java.util.Set", "java.util.SortedSet", "java.util.NavigableSet"); + } + + @Override public JavacNode generateFields(SingularData data, JavacNode builderType, JCTree 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 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); + 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>(); + 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; { + if (data.getTypeArgs() == null || data.getTypeArgs().isEmpty()) { + paramType = chainDots(builderType, "java", "lang", "Object"); + } else { + JCExpression originalType = data.getTypeArgs().head; + if (originalType.getKind() == Kind.UNBOUNDED_WILDCARD || originalType.getKind() == Kind.SUPER_WILDCARD) { + paramType = chainDots(builderType, "java", "lang", "Object"); + } else if (originalType.getKind() == Kind.EXTENDS_WILDCARD) { + try { + paramType = cloneType(maker, (JCExpression) ((JCWildcard) originalType).inner, source, builderType.getContext()); + } catch (Exception e) { + paramType = chainDots(builderType, "java", "lang", "Object"); + } + } else { + paramType = cloneType(maker, originalType, source, builderType.getContext()); + } + } + } + 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); + } + + private 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>(); + 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); + } + + private JCExpression getSize(JavacTreeMaker maker, JavacNode builderType, Name name) { + JCExpression fn = maker.Select(maker.Select(maker.Ident(builderType.toName("this")), name), builderType.toName("size")); + return maker.Apply(List.<JCExpression>nil(), fn, List.<JCExpression>nil()); + } + + @Override public void appendBuildCode(SingularData data, JavacNode builderType, JCTree source, ListBuffer<JCStatement> statements, Name targetVariableName) { + JavacTreeMaker maker = builderType.getTreeMaker(); + JCExpression localShadowerType = chainDotsString(builderType, data.getTargetFqn()); + localShadowerType = addTypeArgs(1, false, builderType, localShadowerType, data.getTypeArgs(), source); + JCExpression constructTargetType; { + if (data.getTargetFqn().equals("java.util.Set")) { + JCExpression internalType = chainDots(builderType, "java", "util", "LinkedHashSet"); + internalType = addTypeArgs(1, false, builderType, internalType, data.getTypeArgs(), source); + JCExpression loadFactor = maker.Literal(CTC_FLOAT, 0.75f); + JCExpression lessThanCutoff = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, data.getPluralName()), maker.Literal(CTC_INT, 0x40000000)); + JCExpression maxInt = chainDots(builderType, "java", "lang", "Integer", "MAX_VALUE"); + JCExpression belowThree = maker.Binary(CTC_LESS_THAN, getSize(maker, builderType, data.getPluralName()), maker.Literal(CTC_INT, 3)); + JCExpression sizePlusOne = maker.Binary(CTC_PLUS, getSize(maker, builderType, data.getPluralName()), maker.Literal(CTC_INT, 1)); + JCExpression sizeDivThree = maker.Binary(CTC_DIV, getSize(maker, builderType, data.getPluralName()), maker.Literal(CTC_INT, 3)); + JCExpression sizePlusSizeDivThree = maker.Binary(CTC_PLUS, getSize(maker, builderType, data.getPluralName()), sizeDivThree); + JCExpression rest = maker.Conditional(belowThree, sizePlusOne, sizePlusSizeDivThree); + JCExpression initialCapacity = maker.Conditional(lessThanCutoff, rest, maxInt); + constructTargetType = maker.NewClass(null, List.<JCExpression>nil(), internalType, List.<JCExpression>of(initialCapacity, loadFactor), null); + } else { + JCExpression internalType = chainDots(builderType, "java", "util", "TreeSet"); + internalType = addTypeArgs(1, false, builderType, internalType, data.getTypeArgs(), source); + constructTargetType = maker.NewClass(null, List.<JCExpression>nil(), internalType, List.<JCExpression>nil(), null); + } + } + JCVariableDecl varDef = maker.VarDef(maker.Modifiers(0), data.getPluralName(), localShadowerType, constructTargetType); + statements.append(varDef); + JCFieldAccess varDotAddAll = maker.Select(maker.Ident(data.getPluralName()), builderType.toName("addAll")); + JCExpression thisDotFieldName = maker.Select(maker.Ident(builderType.toName("this")), data.getPluralName()); + statements.append(maker.Exec(maker.Apply(List.<JCExpression>nil(), varDotAddAll, List.of(thisDotFieldName)))); + String singletonMaker = "unmodifiable" + data.getTargetFqn().substring(data.getTargetFqn().lastIndexOf(".") + 1); + JCExpression javaUtilCollectionsInvoke = maker.Apply(List.<JCExpression>nil(), chainDots(builderType, "java", "util", "Collections", singletonMaker), List.<JCExpression>of(maker.Ident(data.getPluralName()))); + statements.append(maker.Exec(maker.Assign(maker.Ident(data.getPluralName()), javaUtilCollectionsInvoke))); + } +} 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..83348c28 --- /dev/null +++ b/src/core/lombok/javac/handlers/singulars/JavacJavaUtilSingularizer.java @@ -0,0 +1,95 @@ +/* + * 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 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.JCWildcard; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.ListBuffer; + +import lombok.javac.JavacNode; +import lombok.javac.JavacTreeMaker; +import static lombok.javac.handlers.JavacHandlerUtil.*; +import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer; + +public abstract class JavacJavaUtilSingularizer extends JavacSingularizer { + /** + * 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(); + Context context = node.getContext(); + + if (count < 0) throw new IllegalArgumentException("count is negative"); + if (count == 0) return type; + 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(chainDots(node, "java", "lang", "Object")); + } else if (orig.getKind() == Kind.EXTENDS_WILDCARD) { + JCExpression inner; + try { + inner = (JCExpression) ((JCWildcard) orig).inner; + } catch (Exception e) { + inner = chainDots(node, "java", "lang", "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(chainDots(node, "java", "lang", "Object")); + } + } + return maker.TypeApply(type, arguments.toList()); + } + +} |