/*
 * Copyright (C) 2013-2020 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.handlers.HandleBuilder.*;
import static lombok.core.handlers.HandlerUtil.*;
import static lombok.javac.Javac.*;
import static lombok.javac.handlers.JavacHandlerUtil.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

import javax.lang.model.element.Modifier;

import org.mangosdk.spi.ProviderFor;

import com.sun.tools.javac.code.BoundKind;
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.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCBlock;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCExpression;
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.JCMethodDecl;
import com.sun.tools.javac.tree.JCTree.JCMethodInvocation;
import com.sun.tools.javac.tree.JCTree.JCModifiers;
import com.sun.tools.javac.tree.JCTree.JCReturn;
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.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;

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.configuration.CheckerFrameworkVersion;
import lombok.core.AnnotationValues;
import lombok.core.HandlerPriority;
import lombok.core.handlers.HandlerUtil;
import lombok.core.handlers.HandlerUtil.FieldAccess;
import lombok.core.handlers.InclusionExclusionUtils.Included;
import lombok.experimental.NonFinal;
import lombok.experimental.SuperBuilder;
import lombok.javac.Javac;
import lombok.javac.JavacAnnotationHandler;
import lombok.javac.JavacNode;
import lombok.javac.JavacTreeMaker;
import lombok.javac.handlers.HandleBuilder.BuilderFieldData;
import lombok.javac.handlers.HandleBuilder.BuilderJob;
import lombok.javac.handlers.JavacHandlerUtil.MemberExistsResult;
import lombok.javac.handlers.JavacSingularsRecipes.ExpressionMaker;
import lombok.javac.handlers.JavacSingularsRecipes.JavacSingularizer;
import lombok.javac.handlers.JavacSingularsRecipes.SingularData;
import lombok.javac.handlers.JavacSingularsRecipes.StatementMaker;

@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 HandleSuperBuilder extends JavacAnnotationHandler<SuperBuilder> {
	private static final String SELF_METHOD = "self";
	private static final String FILL_VALUES_METHOD_NAME = "$fillValuesFrom";
	private static final String STATIC_FILL_VALUES_METHOD_NAME = "$fillValuesFromInstanceIntoBuilder";
	private static final String INSTANCE_VARIABLE_NAME = "instance";
	private static final String BUILDER_VARIABLE_NAME = "b";
	
	class SuperBuilderJob extends BuilderJob {
		JavacNode builderAbstractType;
		String builderAbstractClassName;
		JavacNode builderImplType;
		String builderImplClassName;
		List<JCTypeParameter> builderTypeParams_;
		
		void init(AnnotationValues<SuperBuilder> annValues, SuperBuilder ann, JavacNode node) {
			accessOuters = accessInners = AccessLevel.PUBLIC;
			oldFluent = true;
			oldChain = true;
			
			builderMethodName = ann.builderMethodName();
			buildMethodName = ann.buildMethodName();
			toBuilder = ann.toBuilder();
			
			if (builderMethodName == null) builderMethodName = "builder";
			if (buildMethodName == null) buildMethodName = "build";
			builderClassName = fixBuilderClassName(node, "");
		}
		
		void setBuilderToImpl() {
			builderType = builderImplType;
			builderClassName = builderImplClassName;
			builderTypeParams = typeParams;
		}
		
		void setBuilderToAbstract() {
			builderType = builderAbstractType;
			builderClassName = builderAbstractClassName;
			builderTypeParams = builderTypeParams_;
		}
	}
	
	@Override
	public void handle(AnnotationValues<SuperBuilder> annotation, JCAnnotation ast, JavacNode annotationNode) {
		handleExperimentalFlagUsage(annotationNode, ConfigurationKeys.SUPERBUILDER_FLAG_USAGE, "@SuperBuilder");
		SuperBuilderJob job = new SuperBuilderJob();
		job.sourceNode = annotationNode;
		job.source = ast;
		job.checkerFramework = getCheckerFrameworkVersion(annotationNode);
		job.isStatic = true;
		
		SuperBuilder annInstance = annotation.getInstance();
		
		job.init(annotation, annInstance, annotationNode);
		
		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 SuperBuilder annotation here, we need it for @Jacksonized.
		
		JavacNode parent = annotationNode.up();
		
		job.builderFields = new ArrayList<BuilderFieldData>();
		job.typeParams = List.nil();
		List<JCExpression> buildMethodThrownExceptions = List.nil();
		List<JCExpression> superclassTypeParams = List.nil();
		boolean addCleaning = false;
		
		if (!(parent.get() instanceof JCClassDecl)) {
			annotationNode.addError("@SuperBuilder is only supported on types.");
			return;
		}
		
		// Gather all fields of the class that should be set by the builder.
		job.parentType = parent;
		JCClassDecl td = (JCClassDecl) parent.get();
		ListBuffer<JavacNode> allFields = new ListBuffer<JavacNode>();
		ArrayList<JavacNode> nonFinalNonDefaultedFields = null;
		
		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, true);
			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.");
				isDefault = null;
			}
			
			if (fd.init == null && isDefault != null) {
				isDefault.addWarning("@Builder.Default requires an initializing expression (' = something;').");
				isDefault = null;
			}
			
			if (fd.init != null && isDefault == null) {
				if (isFinal) continue;
				if (nonFinalNonDefaultedFields == null) nonFinalNonDefaultedFields = new ArrayList<JavacNode>();
				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 = HandleBuilder.generateDefaultProvider(bfd.nameOfDefaultProvider, fieldNode, td.typarams);
				recursiveSetGeneratedBy(md, ast, annotationNode.getContext());
				if (md != null) injectMethod(parent, md);
			}
			addObtainVia(bfd, fieldNode);
			job.builderFields.add(bfd);
			allFields.append(fieldNode);
		}
		
		job.typeParams = job.builderTypeParams = td.typarams;
		job.builderClassName = job.replaceBuilderClassName(td.name);
		if (!checkName("builderClassName", job.builderClassName, annotationNode)) return;
		
		// <C, B> are the generics for our builder.
		String classGenericName = "C";
		String builderGenericName = "B";
		// We have to make sure that the generics' names do not collide with any generics on the annotated class,
		// the classname itself, or any member type name of the annotated class.
		// For instance, if there are generics <B, B2, C> on the annotated class, use "C2" and "B3" for our builder.
		java.util.HashSet<String> usedNames = gatherUsedTypeNames(job.typeParams, td);
		classGenericName = generateNonclashingNameFor(classGenericName, usedNames);
		builderGenericName = generateNonclashingNameFor(builderGenericName, usedNames);
		
		JavacTreeMaker maker = annotationNode.getTreeMaker();
		
		{
			JCExpression annotatedClass = namePlusTypeParamsToTypeReference(maker, parent, job.typeParams);
			JCTypeParameter c = maker.TypeParameter(parent.toName(classGenericName), List.<JCExpression>of(annotatedClass));
			ListBuffer<JCExpression> typeParamsForBuilder = getTypeParamExpressions(job.typeParams, maker, job.sourceNode.getContext());
			typeParamsForBuilder.add(maker.Ident(parent.toName(classGenericName)));
			typeParamsForBuilder.add(maker.Ident(parent.toName(builderGenericName)));
			JCTypeApply typeApply = maker.TypeApply(namePlusTypeParamsToTypeReference(maker, parent, job.getBuilderClassName(), false, List.<JCTypeParameter>nil()), typeParamsForBuilder.toList());
			JCTypeParameter d = maker.TypeParameter(parent.toName(builderGenericName), List.<JCExpression>of(typeApply));
			if (job.typeParams == null || job.typeParams.isEmpty()) {
				job.builderTypeParams_ = List.of(c, d);
			} else {
				job.builderTypeParams_ = job.typeParams.append(c).append(d);
			}
		}
		
		JCTree extendsClause = Javac.getExtendsClause(td);
		JCExpression superclassBuilderClass = null;
		if (extendsClause instanceof JCTypeApply) {
			// Remember the type arguments, because we need them for the extends clause of our abstract builder class.
			superclassTypeParams = ((JCTypeApply) extendsClause).getTypeArguments();
			// A class name with a generics type, e.g., "Superclass<A>".
			extendsClause = ((JCTypeApply) extendsClause).getType();
		}
		if (extendsClause instanceof JCFieldAccess) {
			Name superclassName = ((JCFieldAccess) extendsClause).getIdentifier();
			String superclassBuilderClassName = superclassName.toString() + "Builder";
			superclassBuilderClass = parent.getTreeMaker().Select((JCFieldAccess) extendsClause, parent.toName(superclassBuilderClassName));
		} else if (extendsClause != null) {
			String superclassBuilderClassName = extendsClause.toString() + "Builder";
			superclassBuilderClass = chainDots(parent, extendsClause.toString(), superclassBuilderClassName);
		}
		
		// Check validity of @ObtainVia fields, and add check if adding cleaning for @Singular is necessary.
		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;
				}
			}
		}
		
		job.builderAbstractClassName = job.builderClassName = job.replaceBuilderClassName(td.name);
		job.builderImplClassName = job.builderAbstractClassName + "Impl";
		
		// Create the abstract builder class.
		job.builderAbstractType = findInnerClass(parent, job.builderClassName);
		if (job.builderAbstractType == null) {
			job.builderAbstractType = generateBuilderAbstractClass(job, superclassBuilderClass, superclassTypeParams, classGenericName, builderGenericName);
			recursiveSetGeneratedBy(job.builderAbstractType.get(), ast, annotationNode.getContext());
		} else {
			JCClassDecl builderTypeDeclaration = (JCClassDecl) job.builderAbstractType.get();
			if (!builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)
				|| !builderTypeDeclaration.getModifiers().getFlags().contains(Modifier.ABSTRACT)) {
				
				annotationNode.addError("Existing Builder must be an abstract static inner class.");
				return;
			}
			sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(job.builderAbstractType, 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;
				}
			}
		}
		
		// Generate the fields in the abstract builder class that hold the values for the instance.
		job.setBuilderToAbstract();
		generateBuilderFields(job.builderType, job.builderFields, ast);
		if (addCleaning) {
			JCVariableDecl uncleanField = maker.VarDef(maker.Modifiers(Flags.PRIVATE), job.toName("$lombokUnclean"), maker.TypeIdent(CTC_BOOLEAN), null);
			recursiveSetGeneratedBy(uncleanField, ast, annotationNode.getContext());
			injectFieldAndMarkGenerated(job.builderType, uncleanField);
		}
		
		if (job.toBuilder) {
			// Generate $fillValuesFrom() method in the abstract builder.
			JCMethodDecl fvm = generateFillValuesMethod(job, superclassBuilderClass != null, builderGenericName, classGenericName);
			recursiveSetGeneratedBy(fvm, ast, annotationNode.getContext());
			injectMethod(job.builderType, fvm);
			// Generate $fillValuesFromInstanceIntoBuilder() method in the builder implementation class.
			JCMethodDecl sfvm = generateStaticFillValuesMethod(job, annInstance.setterPrefix());
			recursiveSetGeneratedBy(sfvm, ast, annotationNode.getContext());
			injectMethod(job.builderType, sfvm);
		}
		
		// Generate abstract self() and build() methods in the abstract builder.
		JCMethodDecl asm = generateAbstractSelfMethod(job, superclassBuilderClass != null, builderGenericName);
		recursiveSetGeneratedBy(asm, ast, annotationNode.getContext());
		injectMethod(job.builderType, asm);
		JCMethodDecl abm = generateAbstractBuildMethod(job, superclassBuilderClass != null, classGenericName);
		recursiveSetGeneratedBy(abm, ast, annotationNode.getContext());
		injectMethod(job.builderType, abm);
		
		// Create the setter methods in the abstract builder.
		for (BuilderFieldData bfd : job.builderFields) {
			generateSetterMethodsForBuilder(job, bfd, builderGenericName, annInstance.setterPrefix());
		}
		
		// Create the toString() method for the abstract builder.
		java.util.List<Included<JavacNode, ToString.Include>> fieldNodes = new ArrayList<Included<JavacNode, ToString.Include>>();
		for (BuilderFieldData bfd : job.builderFields) {
			for (JavacNode f : bfd.createdFields) {
				fieldNodes.add(new Included<JavacNode, ToString.Include>(f, null, true, false));
			}
		}
		
		// Let toString() call super.toString() if there is a superclass, so that it also shows fields from the superclass' builder.
		JCMethodDecl toStringMethod = HandleToString.createToString(job.builderType, fieldNodes, true, superclassBuilderClass != null, FieldAccess.ALWAYS_FIELD, ast);
		if (toStringMethod != null) injectMethod(job.builderType, toStringMethod);
		
		// If clean methods are requested, add them now.
		if (addCleaning) {
			JCMethodDecl md = generateCleanMethod(job.builderFields, job.builderType, ast);
			recursiveSetGeneratedBy(md, ast, annotationNode.getContext());
			injectMethod(job.builderType, md);
		}
		
		boolean isAbstract = (td.mods.flags & Flags.ABSTRACT) != 0;
		if (!isAbstract) {
			// Only non-abstract classes get the Builder implementation.
			
			// Create the builder implementation class.
			job.builderImplType = findInnerClass(parent, job.builderImplClassName);
			if (job.builderImplType == null) {
				job.builderImplType = generateBuilderImplClass(job);
				recursiveSetGeneratedBy(job.builderImplType.get(), ast, annotationNode.getContext());
			} else {
				JCClassDecl builderImplTypeDeclaration = (JCClassDecl) job.builderImplType.get();
				if (!builderImplTypeDeclaration.getModifiers().getFlags().contains(Modifier.STATIC)
						|| builderImplTypeDeclaration.getModifiers().getFlags().contains(Modifier.ABSTRACT)) {
					annotationNode.addError("Existing BuilderImpl must be a non-abstract static inner class.");
					return;
				}
				sanityCheckForMethodGeneratingAnnotationsOnBuilderClass(job.builderImplType, annotationNode);
			}

			// Create a simple constructor for the BuilderImpl class.
			JCMethodDecl cd = HandleConstructor.createConstructor(AccessLevel.PRIVATE, List.<JCAnnotation>nil(), job.builderImplType, List.<JavacNode>nil(), false, annotationNode);
			if (cd != null) injectMethod(job.builderImplType, cd);
			job.setBuilderToImpl();
			
			// Create the self() and build() methods in the BuilderImpl.
			JCMethodDecl selfMethod = generateSelfMethod(job);
			recursiveSetGeneratedBy(selfMethod, ast, annotationNode.getContext());
			injectMethod(job.builderType, selfMethod);
			if (methodExists(job.buildMethodName, job.builderType, -1) == MemberExistsResult.NOT_EXISTS) {
				JCMethodDecl buildMethod = generateBuildMethod(job, buildMethodThrownExceptions);
				recursiveSetGeneratedBy(buildMethod, ast, annotationNode.getContext());
				injectMethod(job.builderType, buildMethod);
			}
		}
		
		// Generate a constructor in the annotated class that takes a builder as argument.
		if (!constructorExists(job.parentType, job.builderAbstractClassName)) {
			job.setBuilderToAbstract();
			generateBuilderBasedConstructor(job, superclassBuilderClass != null);
		}
		
		if (!isAbstract) {
			// Only non-abstract classes get the builder() and toBuilder() methods.
			
			// Add the builder() method to the annotated class.
			// Allow users to specify their own builder() methods, e.g., to provide default values.
			if (generateBuilderMethod && methodExists(job.builderMethodName, job.parentType, -1) != MemberExistsResult.NOT_EXISTS) generateBuilderMethod = false;
			if (generateBuilderMethod) {
				JCMethodDecl builderMethod = generateBuilderMethod(job);
				if (builderMethod != null) {
					recursiveSetGeneratedBy(builderMethod, ast, annotationNode.getContext());
					injectMethod(job.parentType, builderMethod);
				}
			}
	
			// Add the toBuilder() method to the annotated class.
			if (job.toBuilder) {
				switch (methodExists(TO_BUILDER_METHOD_NAME, job.parentType, 0)) {
				case EXISTS_BY_USER:
					break;
				case NOT_EXISTS:
					JCMethodDecl md = generateToBuilderMethod(job);
					if (md != null) {
						recursiveSetGeneratedBy(md, ast, annotationNode.getContext());
						injectMethod(job.parentType, md);
					}
					break;
				default:
					// Should not happen.
				}
			}
		}
		
		if (nonFinalNonDefaultedFields != null && generateBuilderMethod) {
			for (JavacNode fieldNode : nonFinalNonDefaultedFields) {
				fieldNode.addWarning("@SuperBuilder 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.");
			}
		}
	}
	
	
Creates and returns the abstract builder class and injects it into the annotated class.
/** * Creates and returns the abstract builder class and injects it into the annotated class. */
private JavacNode generateBuilderAbstractClass(SuperBuilderJob job, JCExpression superclassBuilderClass, List<JCExpression> superclassTypeParams, String classGenericName, String builderGenericName) { JavacTreeMaker maker = job.parentType.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.ABSTRACT | Flags.PUBLIC); // Keep any type params of the annotated class. ListBuffer<JCTypeParameter> allTypeParams = new ListBuffer<JCTypeParameter>(); allTypeParams.addAll(copyTypeParams(job.sourceNode, job.typeParams)); // Add builder-specific type params required for inheritable builders. // 1. The return type for the build() method, named "C", which extends the annotated class. JCExpression annotatedClass = namePlusTypeParamsToTypeReference(maker, job.parentType, job.typeParams); allTypeParams.add(maker.TypeParameter(job.toName(classGenericName), List.<JCExpression>of(annotatedClass))); // 2. The return type for all setter methods, named "B", which extends this builder class. Name builderClassName = job.toName(job.builderClassName); ListBuffer<JCExpression> typeParamsForBuilder = getTypeParamExpressions(job.typeParams, maker, job.sourceNode.getContext()); typeParamsForBuilder.add(maker.Ident(job.toName(classGenericName))); typeParamsForBuilder.add(maker.Ident(job.toName(builderGenericName))); JCTypeApply typeApply = maker.TypeApply(namePlusTypeParamsToTypeReference(maker, job.parentType, builderClassName, false, List.<JCTypeParameter>nil()), typeParamsForBuilder.toList()); allTypeParams.add(maker.TypeParameter(job.toName(builderGenericName), List.<JCExpression>of(typeApply))); JCExpression extending = null; if (superclassBuilderClass != null) { // If the annotated class extends another class, we want this builder to extend the builder of the superclass. // 1. Add the type parameters of the superclass. typeParamsForBuilder = getTypeParamExpressions(superclassTypeParams, maker, job.sourceNode.getContext()); // 2. Add the builder type params <C, B>. typeParamsForBuilder.add(maker.Ident(job.toName(classGenericName))); typeParamsForBuilder.add(maker.Ident(job.toName(builderGenericName))); extending = maker.TypeApply(superclassBuilderClass, typeParamsForBuilder.toList()); } JCClassDecl builder = maker.ClassDef(mods, builderClassName, allTypeParams.toList(), extending, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(job.parentType, builder); }
Creates and returns the concrete builder implementation class and injects it into the annotated class.
/** * Creates and returns the concrete builder implementation class and injects it into the annotated class. */
private JavacNode generateBuilderImplClass(SuperBuilderJob job) { JavacTreeMaker maker = job.getTreeMaker(); JCModifiers mods = maker.Modifiers(Flags.STATIC | Flags.PRIVATE | Flags.FINAL); // Extend the abstract builder. JCExpression extending = namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderAbstractClassName), false, List.<JCTypeParameter>nil()); // Add any type params of the annotated class. ListBuffer<JCTypeParameter> allTypeParams = new ListBuffer<JCTypeParameter>(); allTypeParams.addAll(copyTypeParams(job.sourceNode, job.typeParams)); // Add builder-specific type params required for inheritable builders. // 1. The return type for the build() method (named "C" in the abstract builder), which is the annotated class. JCExpression annotatedClass = namePlusTypeParamsToTypeReference(maker, job.parentType, job.typeParams); // 2. The return type for all setter methods (named "B" in the abstract builder), which is this builder class. JCExpression builderImplClassExpression = namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderImplClassName), false, job.typeParams); ListBuffer<JCExpression> typeParamsForBuilder = getTypeParamExpressions(job.typeParams, maker, job.getContext()); typeParamsForBuilder.add(annotatedClass); typeParamsForBuilder.add(builderImplClassExpression); extending = maker.TypeApply(extending, typeParamsForBuilder.toList()); JCClassDecl builder = maker.ClassDef(mods, job.toName(job.builderImplClassName), copyTypeParams(job.parentType, job.typeParams), extending, List.<JCExpression>nil(), List.<JCTree>nil()); return injectType(job.parentType, builder); }
Generates a constructor that has a builder as the only parameter. The values from the builder are used to initialize the fields of new instances.
Params:
  • callBuilderBasedSuperConstructor – If true, the constructor will explicitly call a super constructor with the builder as argument. Requires builderClassAsParameter != null.
/** * Generates a constructor that has a builder as the only parameter. * The values from the builder are used to initialize the fields of new instances. * * @param callBuilderBasedSuperConstructor * If {@code true}, the constructor will explicitly call a super * constructor with the builder as argument. Requires * {@code builderClassAsParameter != null}. */
private void generateBuilderBasedConstructor(SuperBuilderJob job, boolean callBuilderBasedSuperConstructor) { JavacTreeMaker maker = job.getTreeMaker(); AccessLevel level = AccessLevel.PROTECTED; ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); Name builderVariableName = job.toName(BUILDER_VARIABLE_NAME); for (BuilderFieldData bfd : job.builderFields) { JCExpression rhs; if (bfd.singularData != null && bfd.singularData.getSingularizer() != null) { bfd.singularData.getSingularizer().appendBuildCode(bfd.singularData, bfd.originalFieldNode, bfd.type, statements, bfd.builderFieldName, "b"); rhs = maker.Ident(bfd.singularData.getPluralName()); } else { rhs = maker.Select(maker.Ident(builderVariableName), bfd.builderFieldName); } JCFieldAccess fieldInThis = maker.Select(maker.Ident(job.toName("this")), bfd.rawName); JCStatement assign = maker.Exec(maker.Assign(fieldInThis, rhs)); // In case of @Builder.Default, set the value to the default if it was not set in the builder. if (bfd.nameOfSetFlag != null) { JCFieldAccess setField = maker.Select(maker.Ident(builderVariableName), bfd.nameOfSetFlag); fieldInThis = maker.Select(maker.Ident(job.toName("this")), bfd.rawName); JCExpression parentTypeRef = namePlusTypeParamsToTypeReference(maker, job.parentType, List.<JCTypeParameter>nil()); JCAssign assignDefault = maker.Assign(fieldInThis, maker.Apply(typeParameterNames(maker, ((JCClassDecl) job.parentType.get()).typarams), maker.Select(parentTypeRef, bfd.nameOfDefaultProvider), List.<JCExpression>nil())); statements.append(maker.If(setField, assign, maker.Exec(assignDefault))); } else { statements.append(assign); } if (hasNonNullAnnotations(bfd.originalFieldNode)) { JCStatement nullCheck = generateNullCheck(maker, bfd.originalFieldNode, job.sourceNode); if (nullCheck != null) statements.append(nullCheck); } } List<JCAnnotation> annsOnMethod = job.checkerFramework.generateUnique() ? List.of(maker.Annotation(genTypeRef(job.parentType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil())) : List.<JCAnnotation>nil(); JCModifiers mods = maker.Modifiers(toJavacModifier(level), annsOnMethod); // Create a constructor that has just the builder as parameter. ListBuffer<JCVariableDecl> params = new ListBuffer<JCVariableDecl>(); long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, job.getContext()); // First add all generics that are present on the parent type. ListBuffer<JCExpression> typeParamsForBuilderParameter = getTypeParamExpressions(job.typeParams, maker, job.getContext()); // Now add the <?, ?>. JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); typeParamsForBuilderParameter.add(wildcard); wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); typeParamsForBuilderParameter.add(wildcard); JCTypeApply paramType = maker.TypeApply(namePlusTypeParamsToTypeReference(maker, job.parentType, job.getBuilderClassName(), false, List.<JCTypeParameter>nil()), typeParamsForBuilderParameter.toList()); JCVariableDecl param = maker.VarDef(maker.Modifiers(flags), builderVariableName, paramType, null); params.append(param); if (callBuilderBasedSuperConstructor) { // The first statement must be the call to the super constructor. JCMethodInvocation callToSuperConstructor = maker.Apply(List.<JCExpression>nil(), maker.Ident(job.toName("super")), List.<JCExpression>of(maker.Ident(builderVariableName))); statements.prepend(maker.Exec(callToSuperConstructor)); } JCMethodDecl constr = recursiveSetGeneratedBy(maker.MethodDef(mods, job.toName("<init>"), null, List.<JCTypeParameter>nil(), params.toList(), List.<JCExpression>nil(), maker.Block(0L, statements.toList()), null), job.source, job.getContext()); injectMethod(job.parentType, constr, null, Javac.createVoidType(job.builderType.getSymbolTable(), CTC_VOID)); } private JCMethodDecl generateBuilderMethod(SuperBuilderJob job) { JavacTreeMaker maker = job.getTreeMaker(); ListBuffer<JCExpression> typeArgs = new ListBuffer<JCExpression>(); for (JCTypeParameter typeParam : job.typeParams) typeArgs.append(maker.Ident(typeParam.name)); JCExpression call = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderImplClassName), false, job.typeParams), List.<JCExpression>nil(), null); JCStatement statement = maker.Return(call); JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); int modifiers = Flags.PUBLIC; modifiers |= Flags.STATIC; // Add any type params of the annotated class to the return type. ListBuffer<JCExpression> typeParameterNames = new ListBuffer<JCExpression>(); typeParameterNames.addAll(typeParameterNames(maker, job.typeParams)); // Now add the <?, ?>. JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); typeParameterNames.add(wildcard); typeParameterNames.add(wildcard); JCTypeApply returnType = maker.TypeApply(namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderAbstractClassName), false, List.<JCTypeParameter>nil()), typeParameterNames.toList()); List<JCAnnotation> annsOnMethod = job.checkerFramework.generateUnique() ? List.of(maker.Annotation(genTypeRef(job.parentType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil())) : List.<JCAnnotation>nil(); JCMethodDecl methodDef = maker.MethodDef(maker.Modifiers(modifiers, annsOnMethod), job.toName(job.builderMethodName), returnType, copyTypeParams(job.sourceNode, job.typeParams), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); createRelevantNonNullAnnotation(job.parentType, methodDef); return methodDef; }
Generates a toBuilder() method in the annotated class that looks like this:
public ParentBuilder<?, ?> toBuilder() {
    return new FoobarBuilderImpl().$fillValuesFrom(this);
}
/** * Generates a <code>toBuilder()</code> method in the annotated class that looks like this: * <pre> * public ParentBuilder&lt;?, ?&gt; toBuilder() { * return new <i>Foobar</i>BuilderImpl().$fillValuesFrom(this); * } * </pre> */
private JCMethodDecl generateToBuilderMethod(SuperBuilderJob job) { JavacTreeMaker maker = job.getTreeMaker(); ListBuffer<JCExpression> typeArgs = new ListBuffer<JCExpression>(); for (JCTypeParameter typeParam : job.typeParams) typeArgs.append(maker.Ident(typeParam.name)); JCExpression newClass = maker.NewClass(null, List.<JCExpression>nil(), namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderImplClassName), false, job.typeParams), List.<JCExpression>nil(), null); List<JCExpression> methodArgs = List.<JCExpression>of(maker.Ident(job.toName("this"))); JCMethodInvocation invokeFillMethod = maker.Apply(List.<JCExpression>nil(), maker.Select(newClass, job.toName(FILL_VALUES_METHOD_NAME)), methodArgs); JCStatement statement = maker.Return(invokeFillMethod); JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); int modifiers = Flags.PUBLIC; // Add any type params of the annotated class to the return type. ListBuffer<JCExpression> typeParameterNames = new ListBuffer<JCExpression>(); typeParameterNames.addAll(typeParameterNames(maker, job.typeParams)); // Now add the <?, ?>. JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); typeParameterNames.add(wildcard); typeParameterNames.add(wildcard); JCTypeApply returnType = maker.TypeApply(namePlusTypeParamsToTypeReference(maker, job.parentType, job.toName(job.builderAbstractClassName), false, List.<JCTypeParameter>nil()), typeParameterNames.toList()); List<JCAnnotation> annsOnMethod = job.checkerFramework.generateUnique() ? List.of(maker.Annotation(genTypeRef(job.parentType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil())) : List.<JCAnnotation>nil(); JCMethodDecl methodDef = maker.MethodDef(maker.Modifiers(modifiers, annsOnMethod), job.toName(TO_BUILDER_METHOD_NAME), returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); createRelevantNonNullAnnotation(job.parentType, methodDef); return methodDef; }
Generates a $fillValuesFrom() method in the abstract builder class that looks like this:
protected B $fillValuesFrom(final C instance) {
    super.$fillValuesFrom(instance);
    FoobarBuilder.$fillValuesFromInstanceIntoBuilder(instance, this);
    return self();
}
/** * Generates a <code>$fillValuesFrom()</code> method in the abstract builder class that looks * like this: * <pre> * protected B $fillValuesFrom(final C instance) { * super.$fillValuesFrom(instance); * FoobarBuilder.$fillValuesFromInstanceIntoBuilder(instance, this); * return self(); * } * </pre> */
private JCMethodDecl generateFillValuesMethod(SuperBuilderJob job, boolean inherited, String builderGenericName, String classGenericName) { JavacTreeMaker maker = job.getTreeMaker(); List<JCAnnotation> annotations = List.nil(); if (inherited) { JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(job.builderType, "Override"), List.<JCExpression>nil()); annotations = List.of(overrideAnnotation); } JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED, annotations); Name name = job.toName(FILL_VALUES_METHOD_NAME); JCExpression returnType = maker.Ident(job.toName(builderGenericName)); JCExpression classGenericNameExpr = maker.Ident(job.toName(classGenericName)); JCVariableDecl param = maker.VarDef(maker.Modifiers(Flags.PARAMETER | Flags.FINAL), job.toName(INSTANCE_VARIABLE_NAME), classGenericNameExpr, null); ListBuffer<JCStatement> body = new ListBuffer<JCStatement>(); if (inherited) { // Call super. JCMethodInvocation callToSuper = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(job.toName("super")), name), List.<JCExpression>of(maker.Ident(job.toName(INSTANCE_VARIABLE_NAME)))); body.append(maker.Exec(callToSuper)); } // Call the builder implemention's helper method that actually fills the values from the instance. JCExpression ref = namePlusTypeParamsToTypeReference(maker, job.parentType, job.getBuilderClassName(), false, List.<JCTypeParameter>nil()); JCMethodInvocation callStaticFillValuesMethod = maker.Apply(List.<JCExpression>nil(), maker.Select(ref, job.toName(STATIC_FILL_VALUES_METHOD_NAME)), List.<JCExpression>of(maker.Ident(job.toName(INSTANCE_VARIABLE_NAME)), maker.Ident(job.toName("this")))); body.append(maker.Exec(callStaticFillValuesMethod)); JCReturn returnStatement = maker.Return(maker.Apply(List.<JCExpression>nil(), maker.Ident(job.toName(SELF_METHOD)), List.<JCExpression>nil())); body.append(returnStatement); JCBlock bodyBlock = maker.Block(0, body.toList()); return maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.of(param), List.<JCExpression>nil(), bodyBlock, null); }
Generates a $fillValuesFromInstanceIntoBuilder() method in the builder implementation class that copies all fields from the instance to the builder. It looks like this:
protected B $fillValuesFromInstanceIntoBuilder(Foobar instance, FoobarBuilder<?, ?> b) {
	b.field(instance.field);
}
/** * Generates a <code>$fillValuesFromInstanceIntoBuilder()</code> method in * the builder implementation class that copies all fields from the instance * to the builder. It looks like this: * * <pre> * protected B $fillValuesFromInstanceIntoBuilder(Foobar instance, FoobarBuilder&lt;?, ?&gt; b) { * b.field(instance.field); * } * </pre> */
private JCMethodDecl generateStaticFillValuesMethod(SuperBuilderJob job, String setterPrefix) { JavacTreeMaker maker = job.getTreeMaker(); List<JCAnnotation> annotations = List.nil(); JCModifiers modifiers = maker.Modifiers(Flags.PRIVATE | Flags.STATIC, annotations); Name name = job.toName(STATIC_FILL_VALUES_METHOD_NAME); JCExpression returnType = maker.TypeIdent(CTC_VOID); // 1st parameter: "Foobar instance" JCVariableDecl paramInstance = maker.VarDef(maker.Modifiers(Flags.PARAMETER | Flags.FINAL), job.toName(INSTANCE_VARIABLE_NAME), cloneSelfType(job.parentType), null); // 2nd parameter: "FoobarBuilder<?, ?> b" (plus generics on the annotated type) // First add all generics that are present on the parent type. ListBuffer<JCExpression> typeParamsForBuilderParameter = getTypeParamExpressions(job.typeParams, maker, job.getContext()); // Now add the <?, ?>. JCWildcard wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); typeParamsForBuilderParameter.add(wildcard); wildcard = maker.Wildcard(maker.TypeBoundKind(BoundKind.UNBOUND), null); typeParamsForBuilderParameter.add(wildcard); JCTypeApply builderType = maker.TypeApply(namePlusTypeParamsToTypeReference(maker, job.parentType, job.getBuilderClassName(), false, List.<JCTypeParameter>nil()), typeParamsForBuilderParameter.toList()); JCVariableDecl paramBuilder = maker.VarDef(maker.Modifiers(Flags.PARAMETER | Flags.FINAL), job.toName(BUILDER_VARIABLE_NAME), builderType, null); ListBuffer<JCStatement> body = new ListBuffer<JCStatement>(); // Call the builder's setter methods to fill the values from the instance. for (BuilderFieldData bfd : job.builderFields) { JCExpressionStatement exec = createSetterCallWithInstanceValue(bfd, job.parentType, maker, setterPrefix); body.append(exec); } JCBlock bodyBlock = maker.Block(0, body.toList()); return maker.MethodDef(modifiers, name, returnType, copyTypeParams(job.builderType, job.typeParams), List.of(paramInstance, paramBuilder), List.<JCExpression>nil(), bodyBlock, null); } private JCExpressionStatement createSetterCallWithInstanceValue(BuilderFieldData bfd, JavacNode type, JavacTreeMaker maker, String setterPrefix) { 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(type.toName(INSTANCE_VARIABLE_NAME)), bfd.obtainVia == null ? bfd.rawName : type.toName(bfd.obtainVia.field())); } } else { if (bfd.obtainVia.isStatic()) { for (int i = 0; i < tgt.length; i++) { JCExpression typeRef = namePlusTypeParamsToTypeReference(maker, type, List.<JCTypeParameter>nil()); JCExpression c = maker.Select(typeRef, type.toName(bfd.obtainVia.method())); tgt[i] = maker.Apply(List.<JCExpression>nil(), c, List.<JCExpression>of(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)))); } } else { for (int i = 0; i < tgt.length; i++) { JCExpression c = maker.Select(maker.Ident(type.toName(INSTANCE_VARIABLE_NAME)), type.toName(bfd.obtainVia.method())); tgt[i] = maker.Apply(List.<JCExpression>nil(), c, List.<JCExpression>nil()); } } } JCExpression arg; if (bfd.singularData == null) { arg = tgt[0]; } else { JCExpression eqNull = maker.Binary(CTC_EQUAL, tgt[0], maker.Literal(CTC_BOT, null)); String emptyMaker = bfd.singularData.getSingularizer().getEmptyMaker(bfd.singularData.getTargetFqn()); JCExpression emptyCollection = maker.Apply(List.<JCExpression>nil(), chainDots(type, emptyMaker.split("\\.")), List.<JCExpression>nil()); arg = maker.Conditional(eqNull, emptyCollection, tgt[1]); } String setterName = HandlerUtil.buildAccessorName(setterPrefix, bfd.name.toString()); JCMethodInvocation apply = maker.Apply(List.<JCExpression>nil(), maker.Select(maker.Ident(type.toName(BUILDER_VARIABLE_NAME)), type.toName(setterName)), List.of(arg)); JCExpressionStatement exec = maker.Exec(apply); return exec; } private JCMethodDecl generateAbstractSelfMethod(SuperBuilderJob job, boolean override, String builderGenericName) { JavacTreeMaker maker = job.getTreeMaker(); List<JCAnnotation> annotations = List.nil(); JCAnnotation overrideAnnotation = override ? maker.Annotation(genJavaLangTypeRef(job.builderType, "Override"), List.<JCExpression>nil()) : null; JCAnnotation rrAnnotation = job.checkerFramework.generateReturnsReceiver() ? maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__RETURNS_RECEIVER), List.<JCExpression>nil()) : null; JCAnnotation sefAnnotation = job.checkerFramework.generatePure() ? maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__PURE), List.<JCExpression>nil()) : null; if (sefAnnotation != null) annotations = annotations.prepend(sefAnnotation); if (rrAnnotation != null) annotations = annotations.prepend(rrAnnotation); if (overrideAnnotation != null) annotations = annotations.prepend(overrideAnnotation); JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED | Flags.ABSTRACT, annotations); Name name = job.toName(SELF_METHOD); JCExpression returnType = maker.Ident(job.toName(builderGenericName)); return maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null); } private JCMethodDecl generateSelfMethod(SuperBuilderJob job) { JavacTreeMaker maker = job.getTreeMaker(); JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(job.builderType, "Override"), List.<JCExpression>nil()); JCAnnotation rrAnnotation = job.checkerFramework.generateReturnsReceiver() ? maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__RETURNS_RECEIVER), List.<JCExpression>nil()) : null; JCAnnotation sefAnnotation = job.checkerFramework.generatePure() ? maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__PURE), List.<JCExpression>nil()) : null; List<JCAnnotation> annsOnMethod = List.nil(); if (sefAnnotation != null) annsOnMethod = annsOnMethod.prepend(sefAnnotation); if (rrAnnotation != null) annsOnMethod = annsOnMethod.prepend(rrAnnotation); annsOnMethod = annsOnMethod.prepend(overrideAnnotation); JCModifiers modifiers = maker.Modifiers(Flags.PROTECTED, annsOnMethod); Name name = job.toName(SELF_METHOD); JCExpression returnType = namePlusTypeParamsToTypeReference(maker, job.builderType.up(), job.getBuilderClassName(), false, job.typeParams); JCStatement statement = maker.Return(maker.Ident(job.toName("this"))); JCBlock body = maker.Block(0, List.<JCStatement>of(statement)); return maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } private JCMethodDecl generateAbstractBuildMethod(SuperBuilderJob job, boolean override, String classGenericName) { JavacTreeMaker maker = job.getTreeMaker(); List<JCAnnotation> annotations = List.nil(); if (override) { JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(job.builderType, "Override"), List.<JCExpression>nil()); annotations = List.of(overrideAnnotation); } if (job.checkerFramework.generateSideEffectFree()) annotations = annotations.prepend(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil())); JCModifiers modifiers = maker.Modifiers(Flags.PUBLIC | Flags.ABSTRACT, annotations); Name name = job.toName(job.buildMethodName); JCExpression returnType = maker.Ident(job.toName(classGenericName)); JCVariableDecl recv = HandleBuilder.generateReceiver(job); JCMethodDecl methodDef; if (recv != null && maker.hasMethodDefWithRecvParam()) { methodDef = maker.MethodDefWithRecvParam(modifiers, name, returnType, List.<JCTypeParameter>nil(), recv, List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null); } else { methodDef = maker.MethodDef(modifiers, name, returnType, List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), null, null); } return methodDef; } private JCMethodDecl generateBuildMethod(SuperBuilderJob job, List<JCExpression> thrownExceptions) { JavacTreeMaker maker = job.getTreeMaker(); JCExpression call; ListBuffer<JCStatement> statements = new ListBuffer<JCStatement>(); // Use a constructor that only has this builder as parameter. List<JCExpression> builderArg = List.<JCExpression>of(maker.Ident(job.toName("this"))); call = maker.NewClass(null, List.<JCExpression>nil(), cloneSelfType(job.parentType), builderArg, null); statements.append(maker.Return(call)); JCBlock body = maker.Block(0, statements.toList()); JCAnnotation overrideAnnotation = maker.Annotation(genJavaLangTypeRef(job.builderType, "Override"), List.<JCExpression>nil()); List<JCAnnotation> annsOnMethod = List.of(overrideAnnotation); if (job.checkerFramework.generateSideEffectFree()) annsOnMethod = annsOnMethod.prepend(maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__SIDE_EFFECT_FREE), List.<JCExpression>nil())); JCModifiers modifiers = maker.Modifiers(Flags.PUBLIC, annsOnMethod); JCVariableDecl recv = HandleBuilder.generateReceiver(job); JCMethodDecl methodDef; if (recv != null && maker.hasMethodDefWithRecvParam()) { methodDef = maker.MethodDefWithRecvParam(modifiers, job.toName(job.buildMethodName), cloneSelfType(job.parentType), List.<JCTypeParameter>nil(), recv, List.<JCVariableDecl>nil(), thrownExceptions, body, null); } else { methodDef = maker.MethodDef(modifiers, job.toName(job.buildMethodName), cloneSelfType(job.parentType), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), thrownExceptions, body, null); } createRelevantNonNullAnnotation(job.builderType, methodDef); return methodDef; } 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, 0)))); JCBlock body = maker.Block(0, statements.toList()); return maker.MethodDef(maker.Modifiers(Flags.PUBLIC), type.toName("$lombokClean"), maker.Type(Javac.createVoidType(type.getSymbolTable(), CTC_VOID)), List.<JCTypeParameter>nil(), List.<JCVariableDecl>nil(), List.<JCExpression>nil(), body, null); } private 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<JCVariableDecl> generated = new ArrayList<JCVariableDecl>(); for (int i = len - 1; i >= 0; i--) { 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 { 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 = builderType.getTreeMaker(); if (field == null) { JCModifiers mods = maker.Modifiers(Flags.PRIVATE); JCVariableDecl newField = maker.VarDef(mods, bfd.builderFieldName, cloneType(maker, bfd.type, source, builderType.getContext()), null); field = injectFieldAndMarkGenerated(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(builderType, newField); generated.add(newField); } bfd.createdFields.add(field); } } for (JCVariableDecl gen : generated) recursiveSetGeneratedBy(gen, source, builderType.getContext()); } private void generateSetterMethodsForBuilder(final SuperBuilderJob job, BuilderFieldData fieldNode, final String builderGenericName, String setterPrefix) { boolean deprecate = isFieldDeprecated(fieldNode.originalFieldNode); final JavacTreeMaker maker = job.getTreeMaker(); ExpressionMaker returnTypeMaker = new ExpressionMaker() { @Override public JCExpression make() { return maker.Ident(job.toName(builderGenericName)); }}; StatementMaker returnStatementMaker = new StatementMaker() { @Override public JCStatement make() { return maker.Return(maker.Apply(List.<JCExpression>nil(), maker.Ident(job.toName(SELF_METHOD)), List.<JCExpression>nil())); }}; if (fieldNode.singularData == null || fieldNode.singularData.getSingularizer() == null) { generateSimpleSetterMethodForBuilder(job, deprecate, fieldNode.createdFields.get(0), fieldNode.name, fieldNode.nameOfSetFlag, returnTypeMaker.make(), returnStatementMaker.make(), fieldNode.annotations, fieldNode.originalFieldNode, setterPrefix); } else { fieldNode.singularData.getSingularizer().generateMethods(job.checkerFramework, fieldNode.singularData, deprecate, job.builderType, job.source, true, returnTypeMaker, returnStatementMaker, AccessLevel.PUBLIC); } } private void generateSimpleSetterMethodForBuilder(SuperBuilderJob job, boolean deprecate, JavacNode fieldNode, Name paramName, Name nameOfSetFlag, JCExpression returnType, JCStatement returnStatement, List<JCAnnotation> annosOnParam, JavacNode originalFieldNode, String setterPrefix) { String setterName = HandlerUtil.buildAccessorName(setterPrefix, paramName.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<JCAnnotation> methodAnns = JavacHandlerUtil.findCopyableToSetterAnnotations(originalFieldNode); JCMethodDecl newMethod = null; if (job.checkerFramework.generateCalledMethods() && maker.hasMethodDefWithRecvParam()) { JCAnnotation ncAnno = maker.Annotation(genTypeRef(job.sourceNode, CheckerFrameworkVersion.NAME__NOT_CALLED), List.<JCExpression>of(maker.Literal(setterName.toString()))); JCClassDecl builderTypeNode = (JCClassDecl) job.builderType.get(); JCExpression selfType = namePlusTypeParamsToTypeReference(maker, job.builderType, builderTypeNode.typarams, List.<JCAnnotation>of(ncAnno)); JCVariableDecl recv = maker.VarDef(maker.Modifiers(0L, List.<JCAnnotation>nil()), job.toName("this"), selfType, null); newMethod = HandleSetter.createSetterWithRecv(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, paramName, nameOfSetFlag, returnType, returnStatement, job.sourceNode, methodAnns, annosOnParam, recv); } if (newMethod == null) newMethod = HandleSetter.createSetter(Flags.PUBLIC, deprecate, fieldNode, maker, setterName, paramName, nameOfSetFlag, returnType, returnStatement, job.sourceNode, methodAnns, annosOnParam); if (job.checkerFramework.generateReturnsReceiver()) { List<JCAnnotation> annotations = newMethod.mods.annotations; if (annotations == null) annotations = List.nil(); JCAnnotation anno = maker.Annotation(genTypeRef(job.builderType, CheckerFrameworkVersion.NAME__RETURNS_RECEIVER), List.<JCExpression>nil()); recursiveSetGeneratedBy(anno, job.source, job.getContext()); newMethod.mods.annotations = annotations.prepend(anno); } injectMethod(job.builderType, newMethod); } private void addObtainVia(BuilderFieldData bfd, JavacNode node) { for (JavacNode child : node.down()) { if (!annotationTypeMatches(ObtainVia.class, child)) continue; AnnotationValues<ObtainVia> 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 @Singular annotation on it.
Params:
  • node – The node (field or method param) to inspect for its name and potential @Singular annotation.
  • setterPrefix – the prefix for setter methods
/** * 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 the prefix for setter methods */
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<Singular> 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<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, 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; } private java.util.HashSet<String> gatherUsedTypeNames(List<JCTypeParameter> typeParams, JCClassDecl td) { java.util.HashSet<String> usedNames = new HashSet<String>(); // 1. Add type parameter names. for (JCTypeParameter typeParam : typeParams) usedNames.add(typeParam.getName().toString()); // 2. Add class name. usedNames.add(td.name.toString()); // 3. Add used type names. for (JCTree member : td.getMembers()) { if (member.getKind() == com.sun.source.tree.Tree.Kind.VARIABLE && member instanceof JCVariableDecl) { JCTree type = ((JCVariableDecl)member).getType(); if (type instanceof JCIdent) usedNames.add(((JCIdent)type).getName().toString()); } } return usedNames; } private String generateNonclashingNameFor(String classGenericName, java.util.HashSet<String> typeParamStrings) { if (!typeParamStrings.contains(classGenericName)) return classGenericName; int counter = 2; while (typeParamStrings.contains(classGenericName + counter)) counter++; return classGenericName + counter; } private JavacNode findInnerClass(JavacNode parent, String name) { for (JavacNode child : parent.down()) { if (child.getKind() != Kind.TYPE) continue; JCClassDecl td = (JCClassDecl) child.get(); if (td.name.contentEquals(name)) return child; } return null; } private ListBuffer<JCExpression> getTypeParamExpressions(List<? extends JCTree> typeParams, JavacTreeMaker maker, Context context) { ListBuffer<JCExpression> typeParamsForBuilderParameter = new ListBuffer<JCExpression>(); for (JCTree typeParam : typeParams) { if (typeParam instanceof JCTypeParameter) { typeParamsForBuilderParameter.add(maker.Ident(((JCTypeParameter)typeParam).getName())); } else if (typeParam instanceof JCIdent) { typeParamsForBuilderParameter.add(maker.Ident(((JCIdent)typeParam).getName())); } else if (typeParam instanceof JCFieldAccess) { typeParamsForBuilderParameter.add(copySelect(maker, (JCFieldAccess) typeParam)); } else if (typeParam instanceof JCTypeApply) { typeParamsForBuilderParameter.add(cloneType(maker, (JCTypeApply)typeParam, typeParam, context)); } } return typeParamsForBuilderParameter; } private JCExpression copySelect(JavacTreeMaker maker, JCFieldAccess typeParam) { java.util.List<Name> chainNames = new ArrayList<Name>(); JCExpression expression = typeParam; while (expression != null) { if (expression instanceof JCFieldAccess) { chainNames.add(((JCFieldAccess) expression).getIdentifier()); expression = ((JCFieldAccess) expression).getExpression(); } else if (expression instanceof JCIdent) { chainNames.add(((JCIdent) expression).getName()); expression = null; } } Collections.reverse(chainNames); JCExpression typeParameter = null; for (Name name : chainNames) { if (typeParameter == null) { typeParameter = maker.Ident(name); } else { typeParameter = maker.Select(typeParameter, name); } } return typeParameter; }
Checks if there is a manual constructor in the given type with a single parameter (builder).
/** * Checks if there is a manual constructor in the given type with a single parameter (builder). */
private boolean constructorExists(JavacNode type, String builderClassName) { if (type != null && type.get() instanceof JCClassDecl) { for (JCTree def : ((JCClassDecl)type.get()).defs) { if (def instanceof JCMethodDecl) { JCMethodDecl md = (JCMethodDecl) def; String name = md.name.toString(); boolean matches = name.equals("<init>"); if (isTolerate(type, md)) continue; if (matches && md.params != null && md.params.length() == 1) { // Cannot use typeMatches() here, because the parameter could be fully-qualified, partially-qualified, or not qualified. // A string-compare of the last part should work. If it's a false-positive, users could still @Tolerate it. String typeName = md.params.get(0).getType().toString(); int lastIndexOfDot = typeName.lastIndexOf('.'); if (lastIndexOfDot >= 0) { typeName = typeName.substring(lastIndexOfDot+1); } if ((builderClassName+"<?, ?>").equals(typeName)) return true; } } } } return false; } }