lombok.javac.handlers.HandleBuilder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lombok Show documentation
Show all versions of lombok Show documentation
Spice up your java: Automatic Resource Management, automatic generation of getters, setters, equals, hashCode and toString, and more!
/*
* Copyright (C) 2013-2021 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.core.handlers.HandlerUtil.*;
import static lombok.javac.Javac.*;
import static lombok.javac.JavacTreeMaker.TypeTag.typeTag;
import static lombok.javac.handlers.JavacHandlerUtil.*;
import java.util.ArrayList;
import javax.lang.model.element.Modifier;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAnnotation;
import com.sun.tools.javac.tree.JCTree.JCArrayTypeTree;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.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.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCNewClass;
import com.sun.tools.javac.tree.JCTree.JCPrimitiveTypeTree;
import com.sun.tools.javac.tree.JCTree.JCStatement;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
import com.sun.tools.javac.tree.JCTree.JCTypeParameter;
import com.sun.tools.javac.tree.JCTree.JCVariableDecl;
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;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Builder.ObtainVia;
import lombok.ConfigurationKeys;
import lombok.Singular;
import lombok.ToString;
import lombok.core.AST.Kind;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.configuration.CheckerFrameworkVersion;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.HandlerUtil.FieldAccess;
import lombok.core.handlers.InclusionExclusionUtils.Included;
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.JavacHandlerUtil.CopyJavadoc;
import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult;
import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer;
import lombok.javac.handlers.JavacSingularsRecipes.SingularData;
import lombok.spi.Provides;
@Provides
@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 {
private HandleConstructor handleConstructor = new HandleConstructor();
static final String CLEAN_FIELD_NAME = "$lombokUnclean";
static final String CLEAN_METHOD_NAME = "$lombokClean";
static final String TO_BUILDER_METHOD_NAME = "toBuilder";
static final String DEFAULT_PREFIX = "$default$";
static final String SET_PREFIX = "$set";
static final String VALUE_PREFIX = "$value";
static final String BUILDER_TEMP_VAR = "builder";
static final String TO_BUILDER_NOT_SUPPORTED = "@Builder(toBuilder=true) is only supported if you return your own type.";
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();
}
static class BuilderJob {
CheckerFrameworkVersion checkerFramework;
JavacNode parentType;
String builderMethodName, buildMethodName;
boolean isStatic;
List typeParams;
List builderTypeParams;
JavacNode sourceNode;
java.util.List builderFields;
AccessLevel accessInners, accessOuters;
boolean oldFluent, oldChain, toBuilder;
JavacNode builderType;
String builderClassName;
void init(AnnotationValues annValues, Builder ann, JavacNode node) {
accessOuters = ann.access();
if (accessOuters == null) accessOuters = AccessLevel.PUBLIC;
if (accessOuters == AccessLevel.NONE) {
sourceNode.addError("AccessLevel.NONE is not valid here");
accessOuters = AccessLevel.PUBLIC;
}
accessInners = accessOuters == AccessLevel.PROTECTED ? AccessLevel.PUBLIC : accessOuters;
oldFluent = toBoolean(annValues.getActualExpression("fluent"), true);
oldChain = toBoolean(annValues.getActualExpression("chain"), true);
builderMethodName = ann.builderMethodName();
buildMethodName = ann.buildMethodName();
builderClassName = getBuilderClassNameTemplate(node, ann.builderClassName());
toBuilder = ann.toBuilder();
if (builderMethodName == null) builderMethodName = "builder";
if (buildMethodName == null) buildMethodName = "build";
if (builderClassName == null) builderClassName = "";
}
static String getBuilderClassNameTemplate(JavacNode node, String override) {
if (override != null && !override.isEmpty()) return override;
override = node.getAst().readConfiguration(ConfigurationKeys.BUILDER_CLASS_NAME);
if (override != null && !override.isEmpty()) return override;
return "*Builder";
}
String replaceBuilderClassName(Name name) {
return replaceBuilderClassName(name.toString(), builderClassName);
}
String replaceBuilderClassName(String name, String template) {
if (template.indexOf('*') == -1) return template;
return template.replace("*", name);
}
JCExpression createBuilderParentTypeReference() {
return namePlusTypeParamsToTypeReference(parentType.getTreeMaker(), parentType, typeParams);
}
Name getBuilderClassName() {
return parentType.toName(builderClassName);
}
List copyTypeParams() {
return JavacHandlerUtil.copyTypeParams(sourceNode, typeParams);
}
Name toName(String name) {
return parentType.toName(name);
}
Context getContext() {
return parentType.getContext();
}
JavacTreeMaker getTreeMaker() {
return parentType.getTreeMaker();
}
}
static class BuilderFieldData {
List annotations;
JCExpression type;
Name rawName;
Name name;
Name builderFieldName;
Name nameOfDefaultProvider;
Name nameOfSetFlag;
SingularData singularData;
ObtainVia obtainVia;
JavacNode obtainViaNode;
JavacNode originalFieldNode;
java.util.List createdFields = new ArrayList();
}
@Override public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) {
final String BUILDER_NODE_NOT_SUPPORTED_ERR = "@Builder is only supported on classes, records, constructors, and methods.";
handleFlagUsage(annotationNode, ConfigurationKeys.BUILDER_FLAG_USAGE, "@Builder");
BuilderJob job = new BuilderJob();
job.sourceNode = annotationNode;
job.checkerFramework = getCheckerFrameworkVersion(annotationNode);
job.isStatic = true;
Builder annInstance = annotation.getInstance();
job.init(annotation, annInstance, annotationNode);
java.util.List typeArgsForToBuilder = null;
boolean generateBuilderMethod;
if (job.builderMethodName.isEmpty()) {
generateBuilderMethod = false;
} else if (!checkName("builderMethodName", job.builderMethodName, annotationNode)) {
return;
} else {
generateBuilderMethod = true;
}
if (!checkName("buildMethodName", job.buildMethodName, annotationNode)) return;
// Do not delete the Builder annotation yet, we need it for @Jacksonized.
JavacNode parent = annotationNode.up();
job.builderFields = new ArrayList();
JCExpression buildMethodReturnType;
job.typeParams = List.nil();
List buildMethodThrownExceptions;
Name nameOfBuilderMethod;
JavacNode fillParametersFrom = parent.get() instanceof JCMethodDecl ? parent : null;
boolean addCleaning = false;
ArrayList nonFinalNonDefaultedFields = null;
if (parent.get() instanceof JCClassDecl) {
if (!isClass(parent) && !isRecord(parent)) {
annotationNode.addError(BUILDER_NODE_NOT_SUPPORTED_ERR);
return;
}
job.parentType = parent;
JCClassDecl td = (JCClassDecl) parent.get();
ListBuffer allFields = new ListBuffer();
boolean valuePresent = (hasAnnotation(lombok.Value.class, parent) || hasAnnotation("lombok.experimental.Value", parent));
for (JavacNode fieldNode : HandleConstructor.findAllFields(parent, true)) {
JCVariableDecl fd = (JCVariableDecl) fieldNode.get();
JavacNode isDefault = findAnnotation(Builder.Default.class, fieldNode, false);
boolean isFinal = (fd.mods.flags & Flags.FINAL) != 0 || (valuePresent && !hasAnnotation(NonFinal.class, fieldNode));
BuilderFieldData bfd = new BuilderFieldData();
bfd.rawName = fd.name;
bfd.name = removePrefixFromField(fieldNode);
bfd.builderFieldName = bfd.name;
bfd.annotations = findCopyableAnnotations(fieldNode);
bfd.type = fd.vartype;
bfd.singularData = getSingularData(fieldNode, annInstance.setterPrefix());
bfd.originalFieldNode = fieldNode;
if (bfd.singularData != null && isDefault != null) {
isDefault.addError("@Builder.Default and @Singular cannot be mixed.");
findAnnotation(Builder.Default.class, fieldNode, true);
isDefault = null;
}
if (fd.init == null && isDefault != null) {
isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;').");
findAnnotation(Builder.Default.class, fieldNode, true);
isDefault = null;
}
if (fd.init != null && isDefault == null) {
if (isFinal) continue;
if (nonFinalNonDefaultedFields == null) nonFinalNonDefaultedFields = new ArrayList();
nonFinalNonDefaultedFields.add(fieldNode);
}
if (isDefault != null) {
bfd.nameOfDefaultProvider = parent.toName(DEFAULT_PREFIX + bfd.name);
bfd.nameOfSetFlag = parent.toName(bfd.name + SET_PREFIX);
bfd.builderFieldName = parent.toName(bfd.name + VALUE_PREFIX);
JCMethodDecl md = generateDefaultProvider(bfd.nameOfDefaultProvider, fieldNode, td.typarams);
recursiveSetGeneratedBy(md, annotationNode);
if (md != null) injectMethod(parent, md);
}
addObtainVia(bfd, fieldNode);
job.builderFields.add(bfd);
allFields.append(fieldNode);
}
if (!isRecord(parent)) {
// Records ship with a canonical constructor that acts as @AllArgsConstructor - just use that one.
handleConstructor.generateConstructor(parent, AccessLevel.PACKAGE, List.nil(), allFields.toList(), false, null, SkipIfConstructorExists.I_AM_BUILDER, annotationNode);
}
buildMethodReturnType = namePlusTypeParamsToTypeReference(parent.getTreeMaker(), parent, td.typarams);
job.typeParams = job.builderTypeParams = td.typarams;
buildMethodThrownExceptions = List.nil();
nameOfBuilderMethod = null;
job.builderClassName = job.replaceBuilderClassName(td.name);
if (!checkName("builderClassName", job.builderClassName, annotationNode)) return;
} else if (fillParametersFrom != null && fillParametersFrom.getName().toString().equals("")) {
JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
if (!jmd.typarams.isEmpty()) {
annotationNode.addError("@Builder is not supported on constructors with constructor type parameters.");
return;
}
job.parentType = parent.up();
JCClassDecl td = (JCClassDecl) job.parentType.get();
job.typeParams = job.builderTypeParams = td.typarams;
buildMethodReturnType = job.createBuilderParentTypeReference();
buildMethodThrownExceptions = jmd.thrown;
nameOfBuilderMethod = null;
job.builderClassName = job.replaceBuilderClassName(td.name);
if (!checkName("builderClassName", job.builderClassName, annotationNode)) return;
} else if (fillParametersFrom != null) {
job.parentType = parent.up();
JCClassDecl td = (JCClassDecl) job.parentType.get();
JCMethodDecl jmd = (JCMethodDecl) fillParametersFrom.get();
job.isStatic = (jmd.mods.flags & Flags.STATIC) != 0;
JCExpression fullReturnType = jmd.restype;
buildMethodReturnType = fullReturnType;
job.typeParams = job.builderTypeParams = jmd.typarams;
buildMethodThrownExceptions = jmd.thrown;
nameOfBuilderMethod = jmd.name;
if (buildMethodReturnType instanceof JCTypeApply) {
buildMethodReturnType = cloneType(job.getTreeMaker(), buildMethodReturnType, annotationNode);
}
if (job.builderClassName.indexOf('*') > -1) {
String replStr = returnTypeToBuilderClassName(annotationNode, td, buildMethodReturnType, job.typeParams);
if (replStr == null) return; // shuold not happen
job.builderClassName = job.builderClassName.replace("*", replStr);
}
if (job.toBuilder) {
if (fullReturnType instanceof JCArrayTypeTree) {
annotationNode.addError(TO_BUILDER_NOT_SUPPORTED);
return;
}
Name simpleName;
String pkg;
List tpOnRet = List.nil();
if (fullReturnType instanceof JCTypeApply) {
tpOnRet = ((JCTypeApply) fullReturnType).arguments;
}
JCExpression namingType = fullReturnType;
if (buildMethodReturnType instanceof JCTypeApply) namingType = ((JCTypeApply) buildMethodReturnType).clazz;
if (namingType instanceof JCIdent) {
simpleName = ((JCIdent) namingType).name;
pkg = null;
} else if (namingType instanceof JCFieldAccess) {
JCFieldAccess jcfa = (JCFieldAccess) namingType;
simpleName = jcfa.name;
pkg = unpack(jcfa.selected);
if (pkg.startsWith("ERR:")) {
String err = pkg.substring(4, pkg.indexOf("__ERR__"));
annotationNode.addError(err);
return;
}
} else {
annotationNode.addError("Expected a (parameterized) type here instead of a " + namingType.getClass().getName());
return;
}
if (pkg != null && !parent.getPackageDeclaration().equals(pkg)) {
annotationNode.addError(TO_BUILDER_NOT_SUPPORTED);
return;
}
if (!job.parentType.getName().contentEquals(simpleName)) {
annotationNode.addError(TO_BUILDER_NOT_SUPPORTED);
return;
}
List tpOnMethod = jmd.typarams;
List tpOnType = ((JCClassDecl) job.parentType.get()).typarams;
typeArgsForToBuilder = new ArrayList();
for (JCTypeParameter tp : tpOnMethod) {
int pos = -1;
int idx = -1;
for (JCExpression tOnRet : tpOnRet) {
idx++;
if (!(tOnRet instanceof JCIdent)) continue;
if (((JCIdent) tOnRet).name != tp.name) continue;
pos = idx;
}
if (pos == -1 || tpOnType.size() <= pos) {
annotationNode.addError("@Builder(toBuilder=true) requires that each type parameter on the static method is part of the typeargs of the return value. Type parameter " + tp.name + " is not part of the return type.");
return;
}
typeArgsForToBuilder.add(tpOnType.get(pos).name);
}
}
} else {
annotationNode.addError(BUILDER_NODE_NOT_SUPPORTED_ERR);
return;
}
if (fillParametersFrom != null) {
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.builderFieldName = bfd.name;
bfd.rawName = raw.name;
bfd.annotations = findCopyableAnnotations(param);
bfd.type = raw.vartype;
bfd.singularData = getSingularData(param, annInstance.setterPrefix());
bfd.originalFieldNode = param;
addObtainVia(bfd, param);
job.builderFields.add(bfd);
}
}
job.builderType = findInnerClass(job.parentType, job.builderClassName);
if (job.builderType == null) {
job.builderType = makeBuilderClass(job);
recursiveSetGeneratedBy(job.builderType.get(), annotationNode);
} else {
JCClassDecl builderTypeDeclaration = (JCClassDecl) job.builderType.get();
if (job.isStatic && !builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) {
annotationNode.addError("Existing Builder must be a static inner class.");
return;
} else if (!job.isStatic && builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)) {
annotationNode.addError("Existing Builder must be a non-static inner class.");
return;
}
sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(job.builderType, annotationNode);
/* generate errors for @Singular BFDs that have one already defined node. */ {
for (BuilderFieldData bfd : job.builderFields) {
SingularData sd = bfd.singularData;
if (sd == null) continue;
JavacSingularizer singularizer = sd.getSingularizer();
if (singularizer == null) continue;
if (singularizer.checkForAlreadyExistingNodesAndGenerateError(job.builderType, sd)) {
bfd.singularData = null;
}
}
}
}
for (BuilderFieldData bfd : job.builderFields) {
if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
if (bfd.singularData.getSingularizer().requiresCleaning()) {
addCleaning = true;
break;
}
}
if (bfd.obtainVia != null) {
if (bfd.obtainVia.field().isEmpty() == bfd.obtainVia.method().isEmpty()) {
bfd.obtainViaNode.addError("The syntax is either @ObtainVia(field = \"fieldName\") or @ObtainVia(method = \"methodName\").");
return;
}
if (bfd.obtainVia.method().isEmpty() && bfd.obtainVia.isStatic()) {
bfd.obtainViaNode.addError("@ObtainVia(isStatic = true) is not valid unless 'method' has been set.");
return;
}
}
}
generateBuilderFields(job);
if (addCleaning) {
JavacTreeMaker maker = job.getTreeMaker();
JCVariableDecl uncleanField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), job.builderType.toName(CLEAN_FIELD_NAME), maker.TypeIdent(CTC_BOOLEAN), null);
injectFieldAndMarkGenerated(job.builderType, uncleanField);
recursiveSetGeneratedBy(uncleanField, annotationNode);
}
if (constructorExists(job.builderType) == MemberExistsResult.NOT_EXISTS) {
JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PACKAGE, List.nil(), job.builderType, List.nil(), false, annotationNode);
if (cd != null) injectMethod(job.builderType, cd);
}
for (BuilderFieldData bfd : job.builderFields) {
makePrefixedSetterMethodsForBuilder(job, bfd, annInstance.setterPrefix());
}
{
MemberExistsResult methodExists = methodExists(job.buildMethodName, job.builderType, -1);
if (methodExists == MemberExistsResult.EXISTS_BY_LOMBOK) methodExists = methodExists(job.buildMethodName, job.builderType, 0);
if (methodExists == MemberExistsResult.NOT_EXISTS) {
JCMethodDecl md = generateBuildMethod(job, nameOfBuilderMethod, buildMethodReturnType, buildMethodThrownExceptions, addCleaning);
if (md != null) {
recursiveSetGeneratedBy(md, annotationNode);
injectMethod(job.builderType, md);
}
}
}
if (methodExists("toString", job.builderType, 0) == MemberExistsResult.NOT_EXISTS) {
java.util.List> fieldNodes = new ArrayList>();
for (BuilderFieldData bfd : job.builderFields) {
for (JavacNode f : bfd.createdFields) {
fieldNodes.add(new Included(f, null, true, false));
}
}
JCMethodDecl md = HandleToString.createToString(job.builderType, fieldNodes, true, false, FieldAccess.ALWAYS_FIELD, job.sourceNode);
if (md != null) injectMethod(job.builderType, md);
}
if (addCleaning) injectMethod(job.builderType, generateCleanMethod(job));
if (generateBuilderMethod && methodExists(job.builderMethodName, job.parentType, -1) != MemberExistsResult.NOT_EXISTS) generateBuilderMethod = false;
if (generateBuilderMethod) {
JCMethodDecl md = generateBuilderMethod(job);
recursiveSetGeneratedBy(md, annotationNode);
if (md != null) injectMethod(job.parentType, md);
}
if (job.toBuilder) {
switch (methodExists(TO_BUILDER_METHOD_NAME, job.parentType, 0)) {
case EXISTS_BY_USER:
annotationNode.addWarning("Not generating toBuilder() as it already exists.");
return;
case NOT_EXISTS:
List tps = job.typeParams;
if (typeArgsForToBuilder != null) {
ListBuffer lb = new ListBuffer();
JavacTreeMaker maker = job.getTreeMaker();
for (Name n : typeArgsForToBuilder) {
lb.append(maker.TypeParameter(n, List.nil()));
}
tps = lb.toList();
}
JCMethodDecl md = generateToBuilderMethod(job, tps, annInstance.setterPrefix());
if (md != null) {
recursiveSetGeneratedBy(md, annotationNode);
injectMethod(job.parentType, md);
}
}
}
if (nonFinalNonDefaultedFields != null && generateBuilderMethod) {
for (JavacNode fieldNode : nonFinalNonDefaultedFields) {
fieldNode.addWarning("@Builder will ignore the initializing expression entirely. If you want the initializing expression to serve as default, add @Builder.Default. If it is not supposed to be settable during building, make the field final.");
}
}
}
static String returnTypeToBuilderClassName(JavacNode annotationNode, JCClassDecl td, JCExpression returnType, List typeParams) {
String replStr = null;
if (returnType instanceof JCFieldAccess) {
replStr = ((JCFieldAccess) returnType).name.toString();
} else if (returnType instanceof JCIdent) {
Name n = ((JCIdent) returnType).name;
for (JCTypeParameter tp : typeParams) {
if (tp.name.equals(n)) {
annotationNode.addError("@Builder requires specifying 'builderClassName' if used on methods with a type parameter as return type.");
return null;
}
}
replStr = n.toString();
} else if (returnType instanceof JCPrimitiveTypeTree) {
replStr = returnType.toString();
if (Character.isLowerCase(replStr.charAt(0))) {
replStr = Character.toTitleCase(replStr.charAt(0)) + replStr.substring(1);
}
} else if (returnType instanceof JCTypeApply) {
JCExpression clazz = ((JCTypeApply) returnType).clazz;
if (clazz instanceof JCFieldAccess) {
replStr = ((JCFieldAccess) clazz).name.toString();
} else if (clazz instanceof JCIdent) {
replStr = ((JCIdent) clazz).name.toString();
}
}
if (replStr == null || replStr.isEmpty()) {
// This shouldn't happen.
System.err.println("Lombok bug ID#20140614-1651: javac HandleBuilder: return type to name conversion failed: " + returnType.getClass());
replStr = td.name.toString();
}
return replStr;
}
private static String unpack(JCExpression expr) {
StringBuilder sb = new StringBuilder();
unpack(sb, expr);
return sb.toString();
}
private static void unpack(StringBuilder sb, JCExpression expr) {
if (expr instanceof JCIdent) {
sb.append(((JCIdent) expr).name.toString());
return;
}
if (expr instanceof JCFieldAccess) {
JCFieldAccess jcfa = (JCFieldAccess) expr;
unpack(sb, jcfa.selected);
sb.append(".").append(jcfa.name.toString());
return;
}
if (expr instanceof JCTypeApply) {
sb.setLength(0);
sb.append("ERR:");
sb.append("@Builder(toBuilder=true) is not supported if returning a type with generics applied to an intermediate.");
sb.append("__ERR__");
return;
}
sb.setLength(0);
sb.append("ERR:");
sb.append("Expected a type of some sort, not a " + expr.getClass().getName());
sb.append("__ERR__");
}
private JCMethodDecl generateToBuilderMethod(BuilderJob job, List typeParameters, String prefix) {
// return new ThingieBuilder().setA(this.a).setB(this.b);
JavacTreeMaker maker = job.getTreeMaker();
JCExpression call = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderClassName), !job.isStatic, job.builderTypeParams), List.nil(), null);
JCExpression invoke = call;
ListBuffer preStatements = null;
ListBuffer statements = new ListBuffer();
for (BuilderFieldData bfd : job.builderFields) {
String setterPrefix = !prefix.isEmpty() ? prefix : job.oldFluent ? "" : "set";
String prefixedSetterName = bfd.name.toString();
if (!setterPrefix.isEmpty()) prefixedSetterName = HandlerUtil.buildAccessorName(setterPrefix, prefixedSetterName);
Name setterName = job.toName(prefixedSetterName);
JCExpression[] tgt = new JCExpression[bfd.singularData == null ? 1 : 2];
if (bfd.obtainVia == null || !bfd.obtainVia.field().isEmpty()) {
for (int i = 0; i < tgt.length; i++) {
tgt[i] = maker.Select(maker.Ident(job.toName("this")), bfd.obtainVia == null ? bfd.rawName : job.toName(bfd.obtainVia.field()));
}
} else {
String name = bfd.obtainVia.method();
JCMethodInvocation inv;
if (bfd.obtainVia.isStatic()) {
JCExpression c = maker.Select(maker.Ident(job.toName(job.parentType.getName())), job.toName(name));
inv = maker.Apply(typeParameterNames(maker, typeParameters), c, List.of(maker.Ident(job.toName("this"))));
} else {
JCExpression c = maker.Select(maker.Ident(job.toName("this")), job.toName(name));
inv = maker.Apply(List.nil(), c, List.nil());
}
for (int i = 0; i < tgt.length; i++) tgt[i] = maker.Ident(bfd.name);
// javac appears to cache the type of JCMethodInvocation expressions based on position, meaning, if you have 2 ObtainVia-based method invokes on different types, you get bizarre type mismatch errors.
// going via a local variable declaration solves the problem.
JCExpression varType = JavacHandlerUtil.cloneType(maker, bfd.type, job.sourceNode);
if (preStatements == null) preStatements = new ListBuffer();
preStatements.append(maker.VarDef(maker.Modifiers(Flags.FINAL), bfd.name, varType, inv));
}
JCExpression arg;
if (bfd.singularData == null) {
arg = tgt[0];
invoke = maker.Apply(List.nil(), maker.Select(invoke, setterName), List.of(arg));
} else {
JCExpression isNotNull = maker.Binary(CTC_NOT_EQUAL, tgt[0], maker.Literal(CTC_BOT, null));
JCExpression invokeBuilder = maker.Apply(List.nil(), maker.Select(maker.Ident(job.toName(BUILDER_TEMP_VAR)), setterName), List.of(tgt[1]));
statements.append(maker.If(isNotNull, maker.Exec(invokeBuilder), null));
}
}
if (!statements.isEmpty()) {
JCExpression tempVarType = namePlusTypeParamsToTypeReference(maker, job.parentType, job.getBuilderClassName(), !job.isStatic, typeParameters);
statements.prepend(maker.VarDef(maker.Modifiers(Flags.FINAL), job.toName(BUILDER_TEMP_VAR), tempVarType, invoke));
statements.append(maker.Return(maker.Ident(job.toName(BUILDER_TEMP_VAR))));
} else {
statements.append(maker.Return(invoke));
}
if (preStatements != null) {
preStatements.appendList(statements);
statements = preStatements;
}
JCBlock body = maker.Block(0, statements.toList());
List annsOnParamType = List.nil();
if (job.checkerFramework.generateUnique()) annsOnParamType = List.of(maker.Annotation(genTypeRef(job.parentType, CheckerFrameworkVersion.NAME__UNIQUE), List.nil()));
JCMethodDecl methodDef = maker.MethodDef(maker.Modifiers(toJavacModifier(job.accessOuters)), job.toName(TO_BUILDER_METHOD_NAME), namePlusTypeParamsToTypeReference(maker, job.parentType, job.getBuilderClassName(), !job.isStatic, typeParameters, annsOnParamType), List.nil(), List.nil(), List.nil(), body, null);
createRelevantNonNullAnnotation(job.parentType, methodDef);
return methodDef;
}
private JCMethodDecl generateCleanMethod(BuilderJob job) {
JavacTreeMaker maker = job.getTreeMaker();
ListBuffer statements = new ListBuffer();
for (BuilderFieldData bfd : job.builderFields) {
if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
bfd.singularData.getSingularizer().appendCleaningCode(bfd.singularData, job.builderType, job.sourceNode, statements);
}
}
statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(job.toName("this")), job.toName(CLEAN_FIELD_NAME)), maker.Literal(CTC_BOOLEAN, 0))));
JCBlock body = maker.Block(0, statements.toList());
JCMethodDecl method = maker.MethodDef(maker.Modifiers(toJavacModifier(AccessLevel.PRIVATE)), job.toName(CLEAN_METHOD_NAME), maker.Type(Javac.createVoidType(job.builderType.getSymbolTable(), CTC_VOID)), List.nil(), List.nil(), List.nil(), body, null);
recursiveSetGeneratedBy(method, job.sourceNode);
return method;
}
static JCVariableDecl generateReceiver(BuilderJob job) {
if (!job.checkerFramework.generateCalledMethods()) return null;
ArrayList mandatories = new ArrayList();
for (BuilderFieldData bfd : job.builderFields) {
if (bfd.singularData == null && bfd.nameOfSetFlag == null) mandatories.add(bfd.name.toString());
}
JCExpression arg;
JavacTreeMaker maker = job.getTreeMaker();
if (mandatories.size() == 0) return null;
if (mandatories.size() == 1) arg = maker.Literal(mandatories.get(0));
else {
List elems = List.nil();
for (int i = mandatories.size() - 1; i >= 0; i--) elems = elems.prepend(maker.Literal(mandatories.get(i)));
arg = maker.NewArray(null, List.nil(), elems);
}
JCAnnotation recvAnno = maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__CALLED), List.of(arg));
JCClassDecl builderTypeNode = (JCClassDecl) job.builderType.get();
JCVariableDecl recv = maker.VarDef(maker.Modifiers(Flags.PARAMETER, List.nil()), job.toName("this"), namePlusTypeParamsToTypeReference(maker, job.builderType, builderTypeNode.typarams, List.of(recvAnno)), null);
return recv;
}
private JCMethodDecl generateBuildMethod(BuilderJob job, Name staticName, JCExpression returnType, List thrownExceptions, boolean addCleaning) {
JavacTreeMaker maker = job.getTreeMaker();
JCExpression call;
ListBuffer statements = new ListBuffer();
if (addCleaning) {
JCExpression notClean = maker.Unary(CTC_NOT, maker.Select(maker.Ident(job.toName("this")), job.toName(CLEAN_FIELD_NAME)));
JCStatement invokeClean = maker.Exec(maker.Apply(List.nil(), maker.Ident(job.toName(CLEAN_METHOD_NAME)), List.nil()));
JCIf ifUnclean = maker.If(notClean, invokeClean, null);
statements.append(ifUnclean);
}
for (BuilderFieldData bfd : job.builderFields) {
if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, job.builderType, job.sourceNode, statements, bfd.builderFieldName, "this");
}
}
ListBuffer args = new ListBuffer();
Name thisName = job.toName("this");
for (BuilderFieldData bfd : job.builderFields) {
if (bfd.nameOfSetFlag != null) {
statements.append(maker.VarDef(maker.Modifiers(0L), bfd.builderFieldName, cloneType(maker, bfd.type, job.sourceNode), maker.Select(maker.Ident(thisName), bfd.builderFieldName)));
statements.append(maker.If(maker.Unary(CTC_NOT, maker.Select(maker.Ident(thisName), bfd.nameOfSetFlag)), maker.Exec(maker.Assign(maker.Ident(bfd.builderFieldName), maker.Apply(typeParameterNames(maker, ((JCClassDecl) job.parentType.get()).typarams), maker.Select(maker.Ident(((JCClassDecl) job.parentType.get()).name), bfd.nameOfDefaultProvider), List.nil()))), null));
}
if (bfd.nameOfSetFlag != null || (bfd.singularData != null && bfd.singularData.getSingularizer().shadowedDuringBuild())) {
args.append(maker.Ident(bfd.builderFieldName));
} else {
args.append(maker.Select(maker.Ident(thisName), bfd.builderFieldName));
}
}
if (addCleaning) {
statements.append(maker.Exec(maker.Assign(maker.Select(maker.Ident(job.toName("this")), job.toName(CLEAN_FIELD_NAME)), maker.Literal(CTC_BOOLEAN, 1))));
}
if (staticName == null) {
call = maker.NewClass(null, List.nil(), returnType, args.toList(), null);
statements.append(maker.Return(call));
} else {
ListBuffer typeParams = new ListBuffer();
for (JCTypeParameter tp : ((JCClassDecl) job.builderType.get()).typarams) {
typeParams.append(maker.Ident(tp.name));
}
JCExpression callee = maker.Ident(((JCClassDecl) job.parentType.get()).name);
if (!job.isStatic) callee = maker.Select(callee, job.toName("this"));
JCExpression fn = maker.Select(callee, staticName);
call = maker.Apply(typeParams.toList(), fn, args.toList());
if (returnType instanceof JCPrimitiveTypeTree && CTC_VOID.equals(typeTag(returnType))) {
statements.append(maker.Exec(call));
} else {
statements.append(maker.Return(call));
}
}
JCBlock body = maker.Block(0, statements.toList());
List annsOnMethod = job.checkerFramework.generateSideEffectFree() ? List.of(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil())) : List.nil();
JCVariableDecl recv = generateReceiver(job);
JCMethodDecl methodDef;
if (recv != null && maker.hasMethodDefWithRecvParam()) {
methodDef = maker.MethodDefWithRecvParam(maker.Modifiers(toJavacModifier(job.accessInners), annsOnMethod), job.toName(job.buildMethodName), returnType, List.nil(), recv, List.nil(), thrownExceptions, body, null);
} else {
methodDef = maker.MethodDef(maker.Modifiers(toJavacModifier(job.accessInners), annsOnMethod), job.toName(job.buildMethodName), returnType, List.nil(), List.nil(), thrownExceptions, body, null);
}
if (staticName == null) createRelevantNonNullAnnotation(job.builderType, methodDef);
return methodDef;
}
public static JCMethodDecl generateDefaultProvider(Name methodName, JavacNode fieldNode, List params) {
JavacTreeMaker maker = fieldNode.getTreeMaker();
JCVariableDecl field = (JCVariableDecl) fieldNode.get();
JCStatement statement = maker.Return(field.init);
field.init = null;
JCBlock body = maker.Block(0, List.of(statement));
int modifiers = Flags.PRIVATE | Flags.STATIC;
return maker.MethodDef(maker.Modifiers(modifiers), methodName, cloneType(maker, field.vartype, fieldNode), copyTypeParams(fieldNode, params), List.nil(), List.nil(), body, null);
}
public JCMethodDecl generateBuilderMethod(BuilderJob job) {
//String builderClassName, JavacNode source, JavacNode type, List typeParams, AccessLevel access) {
//builderClassName, annotationNode, tdParent, typeParams, accessForOuters);
JavacTreeMaker maker = job.getTreeMaker();
JCExpression call;
if (job.isStatic) {
call = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderClassName), false, job.typeParams), List.nil(), null);
} else {
call = maker.NewClass(null, List.nil(), namePlusTypeParamsToTypeReference(maker, null, job.toName(job.builderClassName), false, job.typeParams), List.nil(), null);
((JCNewClass) call).encl = maker.Ident(job.toName("this"));
}
JCStatement statement = maker.Return(call);
JCBlock body = maker.Block(0, List.of(statement));
int modifiers = toJavacModifier(job.accessOuters);
if (job.isStatic) modifiers |= Flags.STATIC;
List annsOnMethod = List.nil();
if (job.checkerFramework.generateSideEffectFree()) annsOnMethod = List.of(maker.Annotation(genTypeRef(job.parentType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.nil()));
List annsOnParamType = List.nil();
if (job.checkerFramework.generateUnique()) annsOnParamType = List.of(maker.Annotation(genTypeRef(job.parentType, CheckerFrameworkVersion.NAME__UNIQUE), List.nil()));
JCExpression returnType = namePlusTypeParamsToTypeReference(maker, job.parentType, job.getBuilderClassName(), !job.isStatic, job.builderTypeParams, annsOnParamType);
JCMethodDecl methodDef = maker.MethodDef(maker.Modifiers(modifiers, annsOnMethod), job.toName(job.builderMethodName), returnType, job.copyTypeParams(), List.nil(), List.nil(), body, null);
createRelevantNonNullAnnotation(job.parentType, methodDef);
return methodDef;
}
public void generateBuilderFields(BuilderJob job) {
int len = job.builderFields.size();
java.util.List existing = new ArrayList();
for (JavacNode child : job.builderType.down()) {
if (child.getKind() == Kind.FIELD) existing.add(child);
}
java.util.List generated = new ArrayList();
for (int i = len - 1; i >= 0; i--) {
BuilderFieldData bfd = job.builderFields.get(i);
if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) {
java.util.List generateSingularFields = bfd.singularData.getSingularizer().generateFields(bfd.singularData, job.builderType, job.sourceNode);
for (JavacNode field : generateSingularFields) {
generated.add((JCVariableDecl) field.get());
}
bfd.createdFields.addAll(generateSingularFields);
} else {
JavacNode field = null, setFlag = null;
for (JavacNode exists : existing) {
Name n = ((JCVariableDecl) exists.get()).name;
if (n.equals(bfd.builderFieldName)) field = exists;
if (n.equals(bfd.nameOfSetFlag)) setFlag = exists;
}
JavacTreeMaker maker = job.getTreeMaker();
if (field == null) {
JCModifiers mods = maker.Modifiers(Flags.PRIVATE);
JCVariableDecl newField = maker.VarDef(mods, bfd.builderFieldName, cloneType(maker, bfd.type, job.sourceNode), null);
field = injectFieldAndMarkGenerated(job.builderType, newField);
generated.add(newField);
}
if (setFlag == null && bfd.nameOfSetFlag != null) {
JCModifiers mods = maker.Modifiers(Flags.PRIVATE);
JCVariableDecl newField = maker.VarDef(mods, bfd.nameOfSetFlag, maker.TypeIdent(CTC_BOOLEAN), null);
injectFieldAndMarkGenerated(job.builderType, newField);
generated.add(newField);
}
bfd.createdFields.add(field);
}
}
for (JCVariableDecl gen : generated) recursiveSetGeneratedBy(gen, job.sourceNode);
}
public void makePrefixedSetterMethodsForBuilder(BuilderJob job, BuilderFieldData bfd, String prefix) {
boolean deprecate = isFieldDeprecated(bfd.originalFieldNode);
if (bfd.singularData == null || bfd.singularData.getSingularizer() == null) {
makePrefixedSetterMethodForBuilder(job, bfd, deprecate, prefix);
} else {
bfd.singularData.getSingularizer().generateMethods(job, bfd.singularData, deprecate);
}
}
private void makePrefixedSetterMethodForBuilder(BuilderJob job, BuilderFieldData bfd, boolean deprecate, String prefix) {
JavacNode fieldNode = bfd.createdFields.get(0);
String setterPrefix = !prefix.isEmpty() ? prefix : job.oldFluent ? "" : "set";
String setterName = HandlerUtil.buildAccessorName(setterPrefix, bfd.name.toString());
Name setterName_ = job.builderType.toName(setterName);
for (JavacNode child : job.builderType.down()) {
if (child.getKind() != Kind.METHOD) continue;
JCMethodDecl methodDecl = (JCMethodDecl) child.get();
Name existingName = methodDecl.name;
if (existingName.equals(setterName_) && !isTolerate(fieldNode, methodDecl)) return;
}
JavacTreeMaker maker = fieldNode.getTreeMaker();
List methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(bfd.originalFieldNode);
JCMethodDecl newMethod = null;
if (job.checkerFramework.generateCalledMethods() && maker.hasMethodDefWithRecvParam()) {
JCAnnotation ncAnno = maker.Annotation(genTypeRef(job.sourceNode, CheckerFrameworkVersion.NAME__NOT_CALLED), List.of(maker.Literal(setterName.toString())));
JCClassDecl builderTypeNode = (JCClassDecl) job.builderType.get();
JCExpression selfType = namePlusTypeParamsToTypeReference(maker, job.builderType, builderTypeNode.typarams, List.of(ncAnno));
JCVariableDecl recv = maker.VarDef(maker.Modifiers(Flags.PARAMETER, List.nil()), job.builderType.toName("this"), selfType, null);
newMethod = HandleSetter.createSetterWithRecv(toJavacModifier(job.accessInners), deprecate, fieldNode, maker, setterName, bfd.name, bfd.nameOfSetFlag, job.oldChain, job.sourceNode, methodAnns, bfd.annotations, recv);
}
if (newMethod == null) newMethod = HandleSetter.createSetter(toJavacModifier(job.accessInners), deprecate, fieldNode, maker, setterName, bfd.name, bfd.nameOfSetFlag, job.oldChain, job.sourceNode, methodAnns, bfd.annotations);
recursiveSetGeneratedBy(newMethod, job.sourceNode);
if (job.sourceNode.up().getKind() == Kind.METHOD) {
copyJavadocFromParam(bfd.originalFieldNode.up(), newMethod, bfd.name.toString());
} else {
copyJavadoc(bfd.originalFieldNode, newMethod, CopyJavadoc.SETTER, true);
}
injectMethod(job.builderType, newMethod);
}
private void copyJavadocFromParam(JavacNode from, JCMethodDecl to, String param) {
try {
JCCompilationUnit cu = ((JCCompilationUnit) from.top().get());
String methodComment = Javac.getDocComment(cu, from.get());
String newJavadoc = addReturnsThisIfNeeded(getParamJavadoc(methodComment, param));
Javac.setDocComment(cu, to, newJavadoc);
} catch (Exception ignore) {}
}
public JavacNode makeBuilderClass(BuilderJob job) {
//boolean isStatic, JavacNode source, JavacNode tdParent, String builderClassName, List typeParams, JCAnnotation ast, AccessLevel access) {
//isStatic, annotationNode, tdParent, builderClassName, typeParams, ast, accessForOuters
JavacTreeMaker maker = job.getTreeMaker();
int modifiers = toJavacModifier(job.accessOuters);
if (job.isStatic) modifiers |= Flags.STATIC;
JCModifiers mods = maker.Modifiers(modifiers);
JCClassDecl builder = maker.ClassDef(mods, job.getBuilderClassName(), job.copyTypeParams(), null, List.nil(), List.nil());
recursiveSetGeneratedBy(builder, job.sourceNode);
return injectType(job.parentType, builder);
}
private void addObtainVia(BuilderFieldData bfd, JavacNode node) {
for (JavacNode child : node.down()) {
if (!annotationTypeMatches(ObtainVia.class, child)) continue;
AnnotationValues ann = createAnnotation(ObtainVia.class, child);
bfd.obtainVia = ann.getInstance();
bfd.obtainViaNode = child;
deleteAnnotationIfNeccessary(child, ObtainVia.class);
return;
}
}
/**
* Returns the explicitly requested singular annotation on this node (field
* or parameter), or null if there's no {@code @Singular} annotation on it.
*
* @param node The node (field or method param) to inspect for its name and potential {@code @Singular} annotation.
* @param setterPrefix Explicitly requested setter prefix.
*/
private SingularData getSingularData(JavacNode node, String setterPrefix) {
for (JavacNode child : node.down()) {
if (!annotationTypeMatches(Singular.class, child)) continue;
Name pluralName = node.getKind() == Kind.FIELD ? removePrefixFromField(node) : ((JCVariableDecl) node.get()).name;
AnnotationValues ann = createAnnotation(Singular.class, child);
Singular singularInstance = ann.getInstance();
deleteAnnotationIfNeccessary(child, Singular.class);
String explicitSingular = singularInstance.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(pluralName.toString());
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 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, node);
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, singularInstance.ignoreNullCollections(), setterPrefix);
}
return null;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy