All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lombok.core.handlers.BuilderAndExtensionHandler Maven / Gradle / Ivy

There is a newer version: 0.11.3
Show newest version
/*
 * Copyright © 2011 Philipp Eichhorn
 *
 * 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.core.handlers;

import static lombok.ast.AST.*;
import static lombok.ast.IMethod.ArgumentStyle.INCLUDE_ANNOTATIONS;
import static lombok.ast.Wildcard.Bound.EXTENDS;
import static lombok.core.TransformationsUtil.NON_NULL_PATTERN;
import static lombok.core.util.Names.*;

import java.util.*;

import lombok.*;
import lombok.ast.*;

public abstract class BuilderAndExtensionHandler, METHOD_TYPE extends IMethod, FIELD_TYPE extends IField> {
	public static final String OPTIONAL_DEF = "OptionalDef";
	public static final String BUILDER = "$Builder";

	public void handleBuilder(final TYPE_TYPE type, final Builder builder) {
		final BuilderData builderData = BuilderData.create(type, builder).collect();
		final List requiredFieldDefTypes = builderData.getRequiredFieldDefTypes();
		final List interfaceTypes = new ArrayList(requiredFieldDefTypes);
		interfaceTypes.add(Type(OPTIONAL_DEF));
		for (TypeRef interfaceType : interfaceTypes) interfaceType.withTypeArguments(type.typeArguments());
		final List> builderMethods = new ArrayList>();
		final TypeRef fieldDefType = builderData.getRequiredFields().isEmpty() ? Type(OPTIONAL_DEF) : requiredFieldDefTypes.get(0);

		createConstructor(builderData);
		createInitializeBuilderMethod(builderData, fieldDefType);
		createRequiredFieldInterfaces(builderData, builderMethods);
		createOptionalFieldInterface(builderData, builderMethods);
		createBuilder(builderData, interfaceTypes, builderMethods);
	}

	public void handleExtension(final TYPE_TYPE type, final METHOD_TYPE method, final IParameterValidator validation,
			final IParameterSanitizer sanitizer, final Builder builder) {
		TYPE_TYPE builderType = type. memberType(BUILDER);
		final BuilderData builderData = BuilderData.create(type, builder).collect();
		List requiredFieldNames = builderData.getAllRequiredFieldNames();
		List uninitializedRequiredFieldNames = new ArrayList();
		for (FIELD_TYPE field : builderType.fields()) {
			if (requiredFieldNames.contains(field.name()) && !field.isInitialized()) {
				uninitializedRequiredFieldNames.add(field.name());
			}
		}

		IExtensionCollector extensionCollector = getExtensionCollector().withRequiredFieldNames(uninitializedRequiredFieldNames);
		collectExtensions(method, extensionCollector);
		if (extensionCollector.isExtension()) {
			TYPE_TYPE interfaceType;
			if (extensionCollector.isRequiredFieldsExtension()) {
				interfaceType = type. memberType(builderData.getRequiredFieldDefTypeNames().get(0));
			} else {
				interfaceType = type. memberType(OPTIONAL_DEF);
			}
			builderType.injectMethod(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), method.name()).posHint(method.get()).makePublic().implementing().withArguments(method.arguments(INCLUDE_ANNOTATIONS)) //
					.withStatements(validation.validateParameterOf(method)) //
					.withStatements(sanitizer.sanitizeParameterOf(method)) //
					.withStatements(method.statements()) //
					.withStatement(Return(This())));
			interfaceType.injectMethod(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), method.name()).makePublic().withNoBody().withArguments(method.arguments(INCLUDE_ANNOTATIONS)));
			type.removeMethod(method);
		}
	}

	private void createConstructor(final BuilderData builderData) {
		TYPE_TYPE type = builderData.getType();
		ConstructorDecl constructorDecl = ConstructorDecl(type.name()).makePrivate().withArgument(Arg(Type(BUILDER).withTypeArguments(type.typeArguments()), "builder").makeFinal()).withImplicitSuper();
		for (final FIELD_TYPE field : builderData.getAllFields()) {
			if (field.isFinal() && field.isInitialized()) {
				if (isCollection(field)) {
					constructorDecl.withStatement(Call(Field(field.name()), "addAll").withArgument(Field(Name("builder"), field.name())));
				} else if (isMap(field)) {
					constructorDecl.withStatement(Call(Field(field.name()), "putAll").withArgument(Field(Name("builder"), field.name())));
				}
			} else {
				constructorDecl.withStatement(Assign(Field(field.name()), Field(Name("builder"), field.name())));
			}
		}
		type.injectConstructor(constructorDecl);
	}

	private void createInitializeBuilderMethod(final BuilderData builderData, final TypeRef fieldDefType) {
		final TYPE_TYPE type = builderData.getType();
		type.injectMethod(MethodDecl(fieldDefType, decapitalize(type.name())).makeStatic().withAccessLevel(builderData.getLevel()).withTypeParameters(type.typeParameters()) //
				.withStatement(Return(New(Type(BUILDER).withTypeArguments(type.typeArguments())))));
	}

	private void createRequiredFieldInterfaces(final BuilderData builderData, final List> builderMethods) {
		List fields = builderData.getRequiredFields();
		if (!fields.isEmpty()) {
			TYPE_TYPE type = builderData.getType();
			List names = builderData.getRequiredFieldDefTypeNames();
			FIELD_TYPE field = fields.get(0);
			String name = names.get(0);
			for (int i = 1, iend = fields.size(); i < iend; i++) {
				List> interfaceMethods = new ArrayList>();
				createFluentSetter(builderData, names.get(i), field, interfaceMethods, builderMethods);

				type.injectType(InterfaceDecl(name).makePublic().makeStatic().withMethods(interfaceMethods));
				field = fields.get(i);
				name = names.get(i);
			}
			List> interfaceMethods = new ArrayList>();
			createFluentSetter(builderData, OPTIONAL_DEF, field, interfaceMethods, builderMethods);

			type.injectType(InterfaceDecl(name).makePublic().makeStatic().withTypeParameters(type.typeParameters()).withMethods(interfaceMethods));
		}
	}

	private void createOptionalFieldInterface(final BuilderData builderData, final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();
		List> interfaceMethods = new ArrayList>();
		for (FIELD_TYPE field : builderData.getOptionalFields()) {
			if (isInitializedMapOrCollection(field)) {
				if (builderData.isGenerateConvenientMethodsEnabled()) {
					if (isCollection(field)) {
						createCollectionMethods(builderData, field, interfaceMethods, builderMethods);
					} else if (isMap(field)) {
						createMapMethods(builderData, field, interfaceMethods, builderMethods);
					}
				}
			} else {
				createFluentSetter(builderData, OPTIONAL_DEF, field, interfaceMethods, builderMethods);
			}
		}

		createBuildMethod(builderData, type.name(), interfaceMethods, builderMethods);

		for (String callMethod : builderData.getCallMethods()) {
			createMethodCall(builderData, callMethod, interfaceMethods, builderMethods);
		}

		type.injectType(InterfaceDecl(OPTIONAL_DEF).makePublic().makeStatic().withTypeParameters(type.typeParameters()).withMethods(interfaceMethods));
	}

	private void createFluentSetter(final BuilderData builderData, final String typeName, final FIELD_TYPE field,
			final List> interfaceMethods, final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();
		String methodName = camelCase(builderData.getPrefix(), field.name());
		final Argument arg0 = Arg(field.type(), field.name()).makeFinal();
		builderMethods.add(MethodDecl(Type(typeName).withTypeArguments(type.typeArguments()), methodName).makePublic().implementing().withArgument(arg0) //
				.withStatement(Assign(Field(field.name()), Name(field.name()))) //
				.withStatement(Return(This())));
		interfaceMethods.add(MethodDecl(Type(typeName).withTypeArguments(type.typeArguments()), methodName).makePublic().withNoBody().withArgument(arg0));
	}

	private void createCollectionMethods(final BuilderData builderData, final FIELD_TYPE field,
			final List> interfaceMethods, final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();
		TypeRef elementType = Type(Object.class);
		TypeRef collectionType = Type(Collection.class);
		List typeArguments = field.typeArguments();
		if (typeArguments.size() == 1) {
			elementType = typeArguments.get(0);
			collectionType.withTypeArgument(Wildcard(EXTENDS, elementType));
		}

		{ // add
			String addMethodName = singular(camelCase(builderData.getPrefix(), field.name()));
			final Argument arg0 = Arg(elementType, "arg0").makeFinal();
			builderMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), addMethodName).makePublic().implementing().withArgument(arg0) //
					.withStatement(Call(Field(field.name()), "add").withArgument(Name("arg0"))) //
					.withStatement(Return(This())));
			interfaceMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), addMethodName).makePublic().withNoBody().withArgument(arg0));
		}
		{ // addAll
			String addAllMethodName = camelCase(builderData.getPrefix(), field.name());
			final Argument arg0 = Arg(collectionType, "arg0").makeFinal();
			builderMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), addAllMethodName).makePublic().implementing().withArgument(arg0) //
					.withStatement(Call(Field(field.name()), "addAll").withArgument(Name("arg0"))) //
					.withStatement(Return(This())));
			interfaceMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), addAllMethodName).makePublic().withNoBody().withArgument(arg0));
		}
	}

	private void createMapMethods(final BuilderData builderData, final FIELD_TYPE field, final List> interfaceMethods,
			final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();
		TypeRef keyType = Type(Object.class);
		TypeRef valueType = Type(Object.class);
		TypeRef mapType = Type(Map.class);
		List typeArguments = field.typeArguments();
		if (typeArguments.size() == 2) {
			keyType = typeArguments.get(0);
			valueType = typeArguments.get(1);
			mapType.withTypeArgument(Wildcard(EXTENDS, keyType)) //
					.withTypeArgument(Wildcard(EXTENDS, valueType));
		}

		{ // put
			final String putMethodName = singular(camelCase(builderData.getPrefix(), field.name()));
			final Argument arg0 = Arg(keyType, "arg0").makeFinal();
			final Argument arg1 = Arg(valueType, "arg1").makeFinal();
			builderMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), putMethodName).makePublic().implementing().withArgument(arg0).withArgument(arg1) //
					.withStatement(Call(Field(field.name()), "put").withArgument(Name("arg0")).withArgument(Name("arg1")))
					.withStatement(Return(This())));
			interfaceMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), putMethodName).makePublic().withNoBody().withArgument(arg0).withArgument(arg1));
		}
		{ // putAll
			String putAllMethodName = camelCase(builderData.getPrefix(), field.name());
			final Argument arg0 = Arg(mapType, "arg0").makeFinal();
			builderMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), putAllMethodName).makePublic().implementing().withArgument(arg0) //
					.withStatement(Call(Field(field.name()), "putAll").withArgument(Name("arg0"))) //
					.withStatement(Return(This())));
			interfaceMethods.add(MethodDecl(Type(OPTIONAL_DEF).withTypeArguments(type.typeArguments()), putAllMethodName).makePublic().withNoBody().withArgument(arg0));
		}
	}

	private void createBuildMethod(final BuilderData builderData, final String typeName, final List> interfaceMethods,
			final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();
		builderMethods.add(MethodDecl(Type(typeName).withTypeArguments(type.typeArguments()), "build").makePublic().implementing() //
				.withStatement(Return(New(Type(typeName).withTypeArguments(type.typeArguments())).withArgument(This()))));
		interfaceMethods.add(MethodDecl(Type(typeName).withTypeArguments(type.typeArguments()), "build").makePublic().withNoBody());
	}

	private void createMethodCall(final BuilderData builderData, final String methodName, final List> interfaceMethods,
			final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();

		TypeRef returnType = Type("void");
		boolean returnsVoid = true;
		List thrownExceptions = new ArrayList();
		if ("toString".equals(methodName)) {
			returnType = Type(String.class);
			returnsVoid = false;
		} else {
			for (METHOD_TYPE method : type.methods()) {
				if (methodName.equals(method.name()) && !method.hasArguments()) {
					returnType = method.returns();
					returnsVoid = method.returns("void");
					thrownExceptions.addAll(method.thrownExceptions());
					break;
				}
			}
		}

		Call call = Call(Call("build"), methodName);
		if (returnsVoid) {
			builderMethods.add(MethodDecl(returnType, methodName).makePublic().implementing().withThrownExceptions(thrownExceptions) //
					.withStatement(call));
		} else {
			builderMethods.add(MethodDecl(returnType, methodName).makePublic().implementing().withThrownExceptions(thrownExceptions) //
					.withStatement(Return(call)));
		}
		interfaceMethods.add(MethodDecl(returnType, methodName).makePublic().withNoBody().withThrownExceptions(thrownExceptions));
	}

	private void createBuilder(final BuilderData builderData, final List interfaceTypes,
			final List> builderMethods) {
		TYPE_TYPE type = builderData.getType();
		List builderFields = new ArrayList();
		List> builderFieldDefaultMethods = new ArrayList>();
		for (FIELD_TYPE field : builderData.getAllFields()) {
			FieldDecl builderField = FieldDecl(field.type(), field.name()).makePrivate();
			if (field.isInitialized()) {
				String fieldDefaultMethodName = "$" + field.name() + "Default";
				builderFieldDefaultMethods.add(MethodDecl(field.type(), fieldDefaultMethodName).makeStatic().withTypeParameters(type.typeParameters()) //
						.withStatement(Return(field.initialization())));
				builderField.withInitialization(Call(fieldDefaultMethodName));
				field.replaceInitialization(Call(Name(BUILDER), fieldDefaultMethodName));
			}
			builderFields.add(builderField);
		}
		type.injectType(ClassDecl(BUILDER).withTypeParameters(type.typeParameters()).makePrivate().makeStatic().implementing(interfaceTypes) //
				.withFields(builderFields) //
				.withMethods(builderFieldDefaultMethods) //
				.withMethods(builderMethods) //
				.withMethod(ConstructorDecl(BUILDER).makePrivate().withImplicitSuper()));
	}

	private static > boolean isInitializedMapOrCollection(final FIELD_TYPE field) {
		return (isMap(field) || isCollection(field)) && field.isInitialized();
	}

	private static > boolean isCollection(final FIELD_TYPE field) {
		return (field.isOfType("Collection") || field.isOfType("List") || field.isOfType("Set"));
	}

	private static > boolean isMap(final FIELD_TYPE field) {
		return field.isOfType("Map");
	}

	protected abstract void collectExtensions(METHOD_TYPE method, IExtensionCollector collector);

	protected abstract IExtensionCollector getExtensionCollector();

	@Getter
	public static class BuilderData, METHOD_TYPE extends IMethod, FIELD_TYPE extends IField> {
		private final List requiredFields = new ArrayList();
		private final List optionalFields = new ArrayList();
		private final List requiredFieldDefTypes = new ArrayList();
		private final List allRequiredFieldNames = new ArrayList();
		private final List requiredFieldDefTypeNames = new ArrayList();;
		private final TYPE_TYPE type;
		private final String prefix;
		private final List callMethods;
		private final boolean generateConvenientMethodsEnabled;
		private final AccessLevel level;
		private final Set excludes;

		private BuilderData(final TYPE_TYPE type, final Builder builder) {
			this.type = type;
			excludes = new HashSet(Arrays.asList(builder.exclude()));
			generateConvenientMethodsEnabled = builder.convenientMethods();
			prefix = builder.prefix();
			callMethods = Arrays.asList(builder.callMethods());
			level = builder.value();
		}

		public BuilderData collect() {
			for (FIELD_TYPE field : type.fields()) {
				if (field.isStatic()) continue;
				String fieldName = field.name();
				if (excludes.contains(fieldName)) continue;
				if ((!field.isInitialized()) && (field.isFinal() || !field.annotations(NON_NULL_PATTERN).isEmpty())) {
					requiredFields.add(field);
					allRequiredFieldNames.add(fieldName);
					String typeName = capitalize(camelCase(fieldName, "def"));
					requiredFieldDefTypeNames.add(typeName);
					requiredFieldDefTypes.add(Type(typeName));
				} else if ((generateConvenientMethodsEnabled && isInitializedMapOrCollection(field)) || !field.isFinal()) {
					optionalFields.add(field);
				}
			}
			return this;
		}

		public List getAllFields() {
			List allFields = new ArrayList(getRequiredFields());
			allFields.addAll(getOptionalFields());
			return allFields;
		}

		public static , METHOD_TYPE extends IMethod, FIELD_TYPE extends IField> BuilderData create(TYPE_TYPE type, Builder builder) {
			return new BuilderData(type, builder);
		}
	}

	public static interface IExtensionCollector {
		public IExtensionCollector withRequiredFieldNames(final List fieldNames);

		public boolean isRequiredFieldsExtension();

		public boolean isExtension();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy