package de.plushnikov.intellij.plugin.processor.handler;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifier;
import com.intellij.psi.PsiModifierList;
import com.intellij.psi.PsiNameHelper;
import com.intellij.psi.PsiParameter;
import com.intellij.psi.PsiSubstitutor;
import com.intellij.psi.PsiType;
import com.intellij.psi.PsiTypeParameter;
import com.intellij.psi.PsiTypeParameterListOwner;
import com.intellij.psi.PsiVariable;
import com.intellij.psi.util.PsiTypesUtil;
import de.plushnikov.intellij.plugin.problem.ProblemBuilder;
import de.plushnikov.intellij.plugin.processor.ShouldGenerateFullCodeBlock;
import de.plushnikov.intellij.plugin.processor.clazz.ToStringProcessor;
import de.plushnikov.intellij.plugin.processor.clazz.constructor.NoArgsConstructorProcessor;
import de.plushnikov.intellij.plugin.processor.field.AccessorsInfo;
import de.plushnikov.intellij.plugin.processor.handler.singular.AbstractSingularHandler;
import de.plushnikov.intellij.plugin.processor.handler.singular.BuilderElementHandler;
import de.plushnikov.intellij.plugin.processor.handler.singular.SingularHandlerFactory;
import de.plushnikov.intellij.plugin.psi.LombokLightClassBuilder;
import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder;
import de.plushnikov.intellij.plugin.thirdparty.ErrorMessages;
import de.plushnikov.intellij.plugin.thirdparty.LombokUtils;
import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil;
import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil;
import de.plushnikov.intellij.plugin.util.PsiClassUtil;
import de.plushnikov.intellij.plugin.util.PsiMethodUtil;
import de.plushnikov.intellij.plugin.util.PsiTypeUtil;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.Singular;
import lombok.ToString;
import lombok.Value;
import lombok.experimental.FieldDefaults;
import lombok.experimental.Wither;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Handler methods for Builder-processing
*
* @author Tomasz KalkosiĆski
* @author Michail Plushnikov
*/
public class BuilderHandler {
private final static String ANNOTATION_BUILDER_CLASS_NAME = "builderClassName";
private static final String ANNOTATION_BUILD_METHOD_NAME = "buildMethodName";
private static final String ANNOTATION_BUILDER_METHOD_NAME = "builderMethodName";
private final static String BUILDER_CLASS_NAME = "Builder";
private final static String BUILD_METHOD_NAME = "build";
private final static String BUILDER_METHOD_NAME = "builder";
private static final String TO_BUILDER_METHOD_NAME = "toBuilder";
private static final String TO_BUILDER_ANNOTATION_KEY = "toBuilder";
@SuppressWarnings("deprecation")
private static final Collection<String> INVALID_ON_BUILDERS = Collections.unmodifiableSet(new HashSet<String>(Arrays.asList(
Getter.class.getSimpleName(), Setter.class.getSimpleName(), Wither.class.getSimpleName(), ToString.class.getSimpleName(), EqualsAndHashCode.class.getSimpleName(),
RequiredArgsConstructor.class.getSimpleName(), AllArgsConstructor.class.getSimpleName(), NoArgsConstructor.class.getSimpleName(),
Data.class.getSimpleName(), Value.class.getSimpleName(), lombok.experimental.Value.class.getSimpleName(), FieldDefaults.class.getSimpleName())));
private final ToStringProcessor toStringProcessor;
private final NoArgsConstructorProcessor noArgsConstructorProcessor;
public BuilderHandler(ToStringProcessor toStringProcessor, NoArgsConstructorProcessor noArgsConstructorProcessor) {
this.toStringProcessor = toStringProcessor;
this.noArgsConstructorProcessor = noArgsConstructorProcessor;
}
public static PsiSubstitutor getBuilderSubstitutor(@NotNull PsiTypeParameterListOwner classOrMethodToBuild, @NotNull PsiClass innerClass) {
PsiSubstitutor substitutor = PsiSubstitutor.EMPTY;
if (innerClass.hasModifierProperty(PsiModifier.STATIC)) {
PsiTypeParameter[] typeParameters = classOrMethodToBuild.getTypeParameters();
PsiTypeParameter[] builderParams = innerClass.getTypeParameters();
if (typeParameters.length == builderParams.length) {
for (int i = 0; i < typeParameters.length; i++) {
PsiTypeParameter typeParameter = typeParameters[i];
substitutor = substitutor.put(typeParameter, PsiSubstitutor.EMPTY.substitute(builderParams[i]));
}
}
}
return substitutor;
}
public boolean validate(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation, @NotNull ProblemBuilder problemBuilder) {
boolean result = validateAnnotationOnRightType(psiClass, problemBuilder);
if (result) {
final String builderClassName = getBuilderClassName(psiClass, psiAnnotation);
result = validateBuilderClassName(builderClassName, psiAnnotation.getProject(), problemBuilder) &&
validateExistingBuilderClass(builderClassName, psiClass, problemBuilder) &&
validateSingular(psiClass, problemBuilder);
}
return result;
}
private boolean validateSingular(@NotNull PsiClass psiClass, @NotNull ProblemBuilder problemBuilder) {
boolean result = true;
final AccessorsInfo accessorsInfo = AccessorsInfo.build(psiClass);
final Collection<PsiField> builderFields = getBuilderFields(psiClass, Collections.<PsiField>emptySet(), accessorsInfo);
for (PsiVariable builderVariable : builderFields) {
final PsiAnnotation singularAnnotation = PsiAnnotationSearchUtil.findAnnotation(builderVariable, Singular.class);
if (null != singularAnnotation) {
final String qualifiedName = PsiTypeUtil.getQualifiedName(builderVariable.getType());
if (SingularHandlerFactory.isInvalidSingularType(qualifiedName)) {
problemBuilder.addError("Lombok does not know how to create the singular-form builder methods for type '%s'; " +
"they won't be generated.", qualifiedName != null ? qualifiedName : builderVariable.getType().getCanonicalText());
result = false;
}
final String variableName = builderVariable.getName();
if (!AbstractSingularHandler.validateSingularName(singularAnnotation, accessorsInfo.removePrefix(variableName))) {
problemBuilder.addError("Can't singularize this name: \"%s\"; please specify the singular explicitly (i.e. @Singular(\"sheep\"))", variableName);
result = false;
}
}
}
return result;
}
private boolean validateBuilderClassName(@NotNull String builderClassName, @NotNull Project project, @NotNull ProblemBuilder builder) {
final PsiNameHelper psiNameHelper = PsiNameHelper.getInstance(project);
if (!psiNameHelper.isIdentifier(builderClassName)) {
builder.addError("%s ist not a valid identifier", builderClassName);
return false;
}
return true;
}
private boolean validateExistingBuilderClass(@NotNull String builderClassName, @NotNull PsiClass psiClass, @NotNull ProblemBuilder builder) {
for (PsiClass psiInnerClass : PsiClassUtil.collectInnerClassesIntern(psiClass)) {
if (builderClassName.equals(psiInnerClass.getName())) {
if (PsiAnnotationSearchUtil.checkAnnotationsSimpleNameExistsIn(psiInnerClass, INVALID_ON_BUILDERS)) {
builder.addError("Lombok annotations are not allowed on builder class.");
return false;
}
}
}
return true;
}
private boolean validateAnnotationOnRightType(@NotNull PsiClass psiClass, @NotNull ProblemBuilder builder) {
if (psiClass.isAnnotationType() || psiClass.isInterface() || psiClass.isEnum()) {
builder.addError(ErrorMessages.canBeUsedOnClassOnly(Builder.class));
return false;
}
return true;
}
public boolean validate(@NotNull PsiMethod psiMethod, @NotNull PsiAnnotation psiAnnotation, @NotNull ProblemBuilder problemBuilder) {
final PsiClass psiClass = psiMethod.getContainingClass();
boolean result = null != psiClass;
if (result) {
final String builderClassName = getBuilderClassName(psiClass, psiAnnotation, psiMethod);
result = validateBuilderClassName(builderClassName, psiAnnotation.getProject(), problemBuilder) &&
validateExistingBuilderClass(builderClassName, psiClass, problemBuilder);
}
return result;
}
public boolean notExistInnerClass(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation) {
return notExistInnerClass(psiClass, null, psiAnnotation);
}
public boolean notExistInnerClass(@NotNull PsiClass psiClass, @Nullable PsiMethod psiMethod, @NotNull PsiAnnotation psiAnnotation) {
final String builderClassName = getBuilderClassName(psiClass, psiAnnotation, psiMethod);
final PsiClass innerBuilderClass = PsiClassUtil.getInnerClassInternByName(psiClass, builderClassName);
return null == innerBuilderClass;
}
private PsiType getReturnTypeOfBuildMethod(@NotNull PsiClass psiClass, @Nullable PsiMethod psiMethod) {
final PsiType result;
if (null == psiMethod || psiMethod.isConstructor()) {
result = PsiClassUtil.getTypeWithGenerics(psiClass);
} else {
result = psiMethod.getReturnType();
}
return result;
}
@NotNull
private static String getBuildMethodName(@NotNull PsiAnnotation psiAnnotation) {
final String buildMethodName = PsiAnnotationUtil.getStringAnnotationValue(psiAnnotation, ANNOTATION_BUILD_METHOD_NAME);
return StringUtil.isEmptyOrSpaces(buildMethodName) ? BUILD_METHOD_NAME : buildMethodName;
}
@NotNull
private static String getBuilderMethodName(@NotNull PsiAnnotation psiAnnotation) {
final String builderMethodName = PsiAnnotationUtil.getStringAnnotationValue(psiAnnotation, ANNOTATION_BUILDER_METHOD_NAME);
return StringUtil.isEmptyOrSpaces(builderMethodName) ? BUILDER_METHOD_NAME : builderMethodName;
}
@NotNull
public String getBuilderClassName(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation) {
return getBuilderClassName(psiClass, psiAnnotation, null);
}
@NotNull
public String getBuilderClassName(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation, @Nullable PsiMethod psiMethod) {
String builderClassName = PsiAnnotationUtil.getStringAnnotationValue(psiAnnotation, ANNOTATION_BUILDER_CLASS_NAME);
if (StringUtil.isEmptyOrSpaces(builderClassName)) {
final PsiClass psiBuilderClass;
if (null != psiMethod && !psiMethod.isConstructor()) {
final PsiType psiMethodReturnType = psiMethod.getReturnType();
if (PsiType.VOID.equals(psiMethodReturnType)) {
return StringUtil.capitalize(PsiType.VOID.getCanonicalText()) + BUILDER_CLASS_NAME;
} else {
final PsiClass psiMethodReturnClass = PsiTypesUtil.getPsiClass(psiMethodReturnType);
psiBuilderClass = null == psiMethodReturnClass ? psiClass : psiMethodReturnClass;
}
} else {
psiBuilderClass = psiClass;
}
return StringUtil.capitalize(psiBuilderClass.getName()) + BUILDER_CLASS_NAME;
}
return builderClassName;
}
private boolean hasMethod(@NotNull PsiClass psiClass, String builderMethodName) {
final Collection<PsiMethod> existingMethods = PsiClassUtil.collectClassStaticMethodsIntern(psiClass);
for (PsiMethod existingMethod : existingMethods) {
if (existingMethod.getName().equals(builderMethodName)) {
return true;
}
}
return false;
}
public void createBuilderMethodIfNecessary(@NotNull Collection<? super PsiElement> target, @NotNull PsiClass containingClass, @Nullable PsiMethod psiMethod, @NotNull PsiClass builderPsiClass, @NotNull PsiAnnotation psiAnnotation) {
final String builderMethodName = getBuilderMethodName(psiAnnotation);
if (!hasMethod(containingClass, builderMethodName)) {
LombokLightMethodBuilder method = createBuilderMethod(containingClass, psiMethod, builderPsiClass, psiAnnotation, builderMethodName);
if (null == psiMethod || psiMethod.isConstructor() || psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
method.withModifier(PsiModifier.STATIC);
}
target.add(method);
}
}
public void createToBuilderMethodIfNecessary(@NotNull Collection<? super PsiElement> target, @NotNull PsiClass containingClass, @Nullable PsiMethod psiMethod, @NotNull PsiClass builderPsiClass, @NotNull PsiAnnotation psiAnnotation) {
if (PsiAnnotationUtil.getBooleanAnnotationValue(psiAnnotation, TO_BUILDER_ANNOTATION_KEY, false)) {
LombokLightMethodBuilder method = createBuilderMethod(containingClass, psiMethod, builderPsiClass, psiAnnotation, TO_BUILDER_METHOD_NAME);
target.add(method);
}
}
@NotNull
private LombokLightMethodBuilder createBuilderMethod(@NotNull PsiClass containingClass, @Nullable PsiMethod psiMethod, @NotNull PsiClass builderPsiClass, @NotNull PsiAnnotation psiAnnotation, String methodName) {
final PsiType psiTypeWithGenerics = PsiClassUtil.getTypeWithGenerics(builderPsiClass);
final LombokLightMethodBuilder method = new LombokLightMethodBuilder(containingClass.getManager(), methodName)
.withMethodReturnType(psiTypeWithGenerics)
.withContainingClass(containingClass)
.withNavigationElement(psiAnnotation)
.withModifier(PsiModifier.PUBLIC)
.withBody(createBuilderMethodCodeBlock(containingClass, psiTypeWithGenerics));
addTypeParameters(builderPsiClass, psiMethod, method);
return method;
}
@NotNull
private PsiCodeBlock createBuilderMethodCodeBlock(@NotNull PsiClass containingClass, PsiType psiTypeWithGenerics) {
final String blockText;
if (isShouldGenerateFullBodyBlock()) {
blockText = String.format("return new %s();", psiTypeWithGenerics.getPresentableText());
} else {
blockText = "return null;";
}
return PsiMethodUtil.createCodeBlockFromText(blockText, containingClass);
}
private boolean isShouldGenerateFullBodyBlock() {
return ShouldGenerateFullCodeBlock.getInstance().isStateActive();
}
@NotNull
public PsiClass createBuilderClass(@NotNull PsiClass psiClass, @NotNull PsiMethod psiMethod, @NotNull PsiAnnotation psiAnnotation) {
final String builderClassName = getBuilderClassName(psiClass, psiAnnotation, psiMethod);
LombokLightClassBuilder builderClass = createBuilderClass(psiClass, psiMethod, builderClassName,
psiMethod.isConstructor() || psiMethod.hasModifierProperty(PsiModifier.STATIC), psiAnnotation);
builderClass.withConstructors(createConstructors(builderClass, psiAnnotation));
final Collection<PsiParameter> builderParameters = getBuilderParameters(psiMethod, Collections.<PsiField>emptySet());
final PsiSubstitutor builderSubstitutor = getBuilderSubstitutor(psiClass, builderClass);
builderClass.withFields(generateFields(builderParameters, builderClass, AccessorsInfo.EMPTY, builderSubstitutor));
builderClass.withMethods(createMethods(psiClass, psiMethod, builderClass, psiAnnotation, builderParameters, builderSubstitutor));
return builderClass;
}
@NotNull
public PsiClass createBuilderClass(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation) {
final String builderClassName = getBuilderClassName(psiClass, psiAnnotation);
LombokLightClassBuilder builderClass = createBuilderClass(psiClass, psiClass, builderClassName, true, psiAnnotation);
builderClass.withConstructors(createConstructors(builderClass, psiAnnotation));
final AccessorsInfo accessorsInfo = AccessorsInfo.build(psiClass);
final Collection<PsiField> psiFields = getBuilderFields(psiClass, Collections.<PsiField>emptySet(), accessorsInfo);
final PsiSubstitutor builderSubstitutor = getBuilderSubstitutor(psiClass, builderClass);
builderClass.withFields(generateFields(psiFields, builderClass, accessorsInfo, builderSubstitutor));
builderClass.withMethods(createMethods(psiClass, null, builderClass, psiAnnotation, psiFields, builderSubstitutor));
return builderClass;
}
@NotNull
public Collection<PsiMethod> createMethods(@NotNull PsiClass psiParentClass, @Nullable PsiMethod psiMethod, @NotNull PsiClass psiBuilderClass,
@NotNull PsiAnnotation psiAnnotation, @NotNull Collection<? extends PsiVariable> psiVariables,
@NotNull PsiSubstitutor builderSubstitutor) {
final Collection<PsiMethod> methodsIntern = PsiClassUtil.collectClassMethodsIntern(psiBuilderClass);
final Set<String> existedMethodNames = new HashSet<String>(methodsIntern.size());
for (PsiMethod existedMethod : methodsIntern) {
existedMethodNames.add(existedMethod.getName());
}
final List<PsiMethod> psiMethods = new ArrayList<PsiMethod>();
// use AccessorsInfo only for @Builder on class, not on method
final AccessorsInfo accessorsInfo = null == psiMethod ? AccessorsInfo.build(psiParentClass) : AccessorsInfo.EMPTY;
final boolean fluentBuilder = isFluentBuilder(psiAnnotation);
final PsiType psiBuilderClassType = PsiClassUtil.getTypeWithGenerics(psiBuilderClass);
final PsiType returnType = createSetterReturnType(psiAnnotation, psiBuilderClassType);
final StringBuilder buildMethodPrepareString = new StringBuilder(psiVariables.size() * 20);
final StringBuilder buildMethodParameterString = new StringBuilder(psiVariables.size() * 20);
for (PsiVariable psiVariable : psiVariables) {
final String fieldName = accessorsInfo.removePrefix(psiVariable.getName());
final PsiAnnotation singularAnnotation = PsiAnnotationSearchUtil.findAnnotation(psiVariable, Singular.class);
final BuilderElementHandler handler = SingularHandlerFactory.getHandlerFor(psiVariable, singularAnnotation, isShouldGenerateFullBodyBlock());
// skip methods already defined in builder class
if (!existedMethodNames.contains(fieldName)) {
final String singularName = handler.createSingularName(singularAnnotation, fieldName);
handler.addBuilderMethod(psiMethods, psiVariable, fieldName, psiBuilderClass, fluentBuilder, returnType, singularName, builderSubstitutor);
}
handler.appendBuildPrepare(buildMethodPrepareString, psiVariable, fieldName);
handler.appendBuildCall(buildMethodParameterString, fieldName);
buildMethodParameterString.append(',');
}
final String buildMethodName = getBuildMethodName(psiAnnotation);
if (!existedMethodNames.contains(buildMethodName)) {
if (buildMethodParameterString.length() > 0) {
buildMethodParameterString.deleteCharAt(buildMethodParameterString.length() - 1);
}
psiMethods.add(createBuildMethod(psiParentClass, psiMethod, psiBuilderClass, builderSubstitutor,
buildMethodName, buildMethodPrepareString.toString(), buildMethodParameterString.toString()));
}
if (!existedMethodNames.contains(ToStringProcessor.METHOD_NAME)) {
psiMethods.add(toStringProcessor.createToStringMethod(psiBuilderClass, Arrays.asList(psiBuilderClass.getFields()), psiAnnotation));
}
return psiMethods;
}
@NotNull
private LombokLightClassBuilder createBuilderClass(@NotNull PsiClass psiClass, @NotNull PsiTypeParameterListOwner psiTypeParameterListOwner, @NotNull String builderClassName, final boolean isStatic, @NotNull PsiAnnotation psiAnnotation) {
final String builderClassQualifiedName = psiClass.getQualifiedName() + "." + builderClassName;
final Project project = psiClass.getProject();
final LombokLightClassBuilder classBuilder = new LombokLightClassBuilder(project, builderClassName, builderClassQualifiedName)
.withContainingClass(psiClass)
.withNavigationElement(psiAnnotation)
.withParameterTypes(psiTypeParameterListOwner instanceof PsiMethod && ((PsiMethod) psiTypeParameterListOwner).isConstructor() ? psiClass.getTypeParameterList() : psiTypeParameterListOwner.getTypeParameterList())
.withModifier(PsiModifier.PUBLIC);
if (isStatic) {
classBuilder.withModifier(PsiModifier.STATIC);
}
return classBuilder;
}
@NotNull
public Collection<PsiMethod> createConstructors(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation) {
final Collection<PsiMethod> methodsIntern = PsiClassUtil.collectClassConstructorIntern(psiClass);
final String constructorName = noArgsConstructorProcessor.getConstructorName(psiClass);
for (PsiMethod existedConstructor : methodsIntern) {
if (constructorName.equals(existedConstructor.getName()) && existedConstructor.getParameterList().getParametersCount() == 0) {
return Collections.emptySet();
}
}
return noArgsConstructorProcessor.createNoArgsConstructor(psiClass, PsiModifier.PACKAGE_LOCAL, psiAnnotation);
}
@NotNull
public Collection<PsiField> getBuilderFields(@NotNull PsiClass psiClass, @NotNull Collection<PsiField> existedFields, @NotNull AccessorsInfo accessorsInfo) {
final List<PsiField> fields = new ArrayList<PsiField>();
final Set<String> existedFieldNames = new HashSet<String>(existedFields.size());
for (PsiField existedField : existedFields) {
existedFieldNames.add(existedField.getName());
}
for (PsiField psiField : psiClass.getFields()) {
boolean selectField = true;
PsiModifierList modifierList = psiField.getModifierList();
if (null != modifierList) {
//Skip static fields.
selectField = !modifierList.hasModifierProperty(PsiModifier.STATIC);
// skip initialized final fields
selectField &= !(null != psiField.getInitializer() && modifierList.hasModifierProperty(PsiModifier.FINAL));
}
//Skip fields that start with $
final String psiFieldName = psiField.getName();
if (null != psiFieldName) {
selectField &= !psiFieldName.startsWith(LombokUtils.LOMBOK_INTERN_FIELD_MARKER);
if (!existedFieldNames.isEmpty()) {
// skip fields already defined in builder class
final String fieldName = accessorsInfo.removePrefix(psiFieldName);
selectField &= !existedFieldNames.contains(fieldName);
}
}
if (selectField) {
fields.add(psiField);
}
}
return fields;
}
@NotNull
public Collection<PsiField> generateFields(@NotNull Collection<? extends PsiVariable> psiVariables, @NotNull PsiClass psiBuilderClass,
@NotNull AccessorsInfo accessorsInfo, @NotNull PsiSubstitutor builderSubstitutor) {
List<PsiField> fields = new ArrayList<PsiField>();
for (PsiVariable psiVariable : psiVariables) {
final PsiAnnotation singularAnnotation = PsiAnnotationSearchUtil.findAnnotation(psiVariable, Singular.class);
BuilderElementHandler handler = SingularHandlerFactory.getHandlerFor(psiVariable, singularAnnotation, isShouldGenerateFullBodyBlock());
handler.addBuilderField(fields, psiVariable, psiBuilderClass, accessorsInfo, builderSubstitutor);
}
return fields;
}
@NotNull
public Collection<PsiParameter> getBuilderParameters(@NotNull PsiMethod psiMethod, @NotNull Collection<PsiField> existedFields) {
final Set<String> existedFieldNames = new HashSet<String>(existedFields.size());
for (PsiField existedField : existedFields) {
existedFieldNames.add(existedField.getName());
}
Collection<PsiParameter> result = new ArrayList<PsiParameter>();
for (PsiParameter psiParameter : psiMethod.getParameterList().getParameters()) {
final String parameterName = psiParameter.getName();
if (null != parameterName && !existedFieldNames.contains(parameterName)) {
result.add(psiParameter);
}
}
return result;
}
@NotNull
private PsiMethod createBuildMethod(@NotNull PsiClass parentClass, @Nullable PsiMethod psiMethod, @NotNull PsiClass builderClass, @NotNull PsiSubstitutor builderSubstitutor,
@NotNull String buildMethodName, @NotNull String buildMethodPrepare, @NotNull String buildMethodParameters) {
final PsiType builderType = getReturnTypeOfBuildMethod(parentClass, psiMethod);
final PsiType returnType = builderSubstitutor.substitute(builderType);
final LombokLightMethodBuilder methodBuilder = new LombokLightMethodBuilder(parentClass.getManager(), buildMethodName)
.withMethodReturnType(returnType)
.withContainingClass(builderClass)
.withNavigationElement(parentClass)
.withModifier(PsiModifier.PUBLIC)
.withBody(createBuildMethodCodeBlock(psiMethod, builderClass, returnType, buildMethodPrepare, buildMethodParameters));
if (null == psiMethod) {
final Collection<PsiMethod> classConstructors = PsiClassUtil.collectClassConstructorIntern(parentClass);
if (!classConstructors.isEmpty()) {
final PsiMethod constructor = classConstructors.iterator().next();
addExceptions(methodBuilder, constructor);
}
} else {
addExceptions(methodBuilder, psiMethod);
}
return methodBuilder;
}
@NotNull
private PsiCodeBlock createBuildMethodCodeBlock(@Nullable PsiMethod psiMethod, @NotNull PsiClass psiClass, @NotNull PsiType buildMethodReturnType,
@NotNull String buildMethodPrepare, @NotNull String buildMethodParameters) {
final String blockText;
if (isShouldGenerateFullBodyBlock()) {
final String codeBlockFormat, callExpressionText;
if (null == psiMethod || psiMethod.isConstructor()) {
codeBlockFormat = "%s\n return new %s(%s);";
callExpressionText = buildMethodReturnType.getPresentableText();
} else {
if (PsiType.VOID.equals(buildMethodReturnType)) {
codeBlockFormat = "%s\n %s(%s);";
} else {
codeBlockFormat = "%s\n return %s(%s);";
}
callExpressionText = calculateCallExpressionForMethod(psiMethod, psiClass);
}
blockText = String.format(codeBlockFormat, buildMethodPrepare, callExpressionText, buildMethodParameters);
} else {
blockText = "return " + PsiTypeUtil.getReturnValueOfType(buildMethodReturnType) + ";";
}
return PsiMethodUtil.createCodeBlockFromText(blockText, psiClass);
}
@NotNull
private String calculateCallExpressionForMethod(@NotNull PsiMethod psiMethod, @NotNull PsiClass builderClass) {
final PsiClass containingClass = psiMethod.getContainingClass();
StringBuilder className = new StringBuilder();
if (null != containingClass) {
className.append(containingClass.getName()).append(".");
if (!psiMethod.isConstructor() && !psiMethod.hasModifierProperty(PsiModifier.STATIC)) {
className.append("this.");
}
if (builderClass.hasTypeParameters()) {
className.append('<');
for (PsiTypeParameter typeParameter : builderClass.getTypeParameters()) {
className.append(typeParameter.getName()).append(',');
}
className.setCharAt(className.length() - 1, '>');
}
}
return className + psiMethod.getName();
}
private void addExceptions(LombokLightMethodBuilder methodBuilder, PsiMethod psiMethod) {
for (PsiClassType psiClassType : psiMethod.getThrowsList().getReferencedTypes()) {
methodBuilder.withException(psiClassType);
}
}
private void addTypeParameters(PsiClass builderClass, PsiMethod psiMethod, LombokLightMethodBuilder methodBuilder) {
final PsiTypeParameter[] psiTypeParameters;
if (null == psiMethod || psiMethod.isConstructor()) {
psiTypeParameters = builderClass.getTypeParameters();
} else {
psiTypeParameters = psiMethod.getTypeParameters();
}
for (PsiTypeParameter psiTypeParameter : psiTypeParameters) {
methodBuilder.withTypeParameter(psiTypeParameter);
}
}
private static final String ANNOTATION_FLUENT = "fluent";
private static final String ANNOTATION_CHAIN = "chain";
private boolean isFluentBuilder(@NotNull PsiAnnotation psiAnnotation) {
return PsiAnnotationUtil.getBooleanAnnotationValue(psiAnnotation, ANNOTATION_FLUENT, true);
}
@NotNull
private PsiType createSetterReturnType(@NotNull PsiAnnotation psiAnnotation, @NotNull PsiType fieldType) {
final boolean isChain = PsiAnnotationUtil.getBooleanAnnotationValue(psiAnnotation, ANNOTATION_CHAIN, true);
return isChain ? fieldType : PsiType.VOID;
}
}