package de.plushnikov.intellij.plugin.processor.field; import com.intellij.lang.java.JavaLanguage; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiClass; 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.PsiType; import com.intellij.util.StringBuilderSpinAllocator; import de.plushnikov.intellij.plugin.problem.ProblemBuilder; import de.plushnikov.intellij.plugin.processor.LombokPsiElementUsage; import de.plushnikov.intellij.plugin.processor.clazz.constructor.RequiredArgsConstructorProcessor; import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder; import de.plushnikov.intellij.plugin.psi.LombokLightParameter; import de.plushnikov.intellij.plugin.quickfix.PsiQuickFixFactory; import de.plushnikov.intellij.plugin.thirdparty.LombokUtils; import de.plushnikov.intellij.plugin.util.LombokProcessorUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; import de.plushnikov.intellij.plugin.util.PsiClassUtil; import de.plushnikov.intellij.plugin.util.PsiMethodUtil; import lombok.AllArgsConstructor; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.Value; import lombok.experimental.Wither; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Collection; import java.util.List; public class WitherFieldProcessor extends AbstractFieldProcessor { private final RequiredArgsConstructorProcessor requiredArgsConstructorProcessor; public WitherFieldProcessor(RequiredArgsConstructorProcessor requiredArgsConstructorProcessor) { super(PsiMethod.class, Wither.class); this.requiredArgsConstructorProcessor = requiredArgsConstructorProcessor; } @Override protected boolean validate(@NotNull PsiAnnotation psiAnnotation, @NotNull PsiField psiField, @NotNull ProblemBuilder builder) { boolean valid = validateVisibility(psiAnnotation); valid &= validName(psiField, builder); valid &= validNonStatic(psiField, builder); valid &= validNonFinalInitialized(psiField, builder); valid &= validIsWitherUnique(psiField, builder); final PsiClass containingClass = psiField.getContainingClass(); valid &= null != containingClass && (containingClass.hasModifierProperty(PsiModifier.ABSTRACT) || validConstructor(containingClass, builder)); return valid; } private boolean validateVisibility(@NotNull PsiAnnotation psiAnnotation) { final String methodVisibility = LombokProcessorUtil.getMethodModifier(psiAnnotation); return null != methodVisibility; } @Override protected void generatePsiElements(@NotNull PsiField psiField, @NotNull PsiAnnotation psiAnnotation, @NotNull List<? super PsiElement> target) { String methodModifier = LombokProcessorUtil.getMethodModifier(psiAnnotation); if (methodModifier != null) { AccessorsInfo accessorsInfo = AccessorsInfo.build(psiField); PsiMethod method = createWitherMethod(psiField, methodModifier, accessorsInfo); if (method != null) { target.add(method); } } } private boolean validName(@NotNull PsiField psiField, @NotNull ProblemBuilder builder) { if (psiField.getName().startsWith(LombokUtils.LOMBOK_INTERN_FIELD_MARKER)) { builder.addWarning("Not generating wither for this field: Withers cannot be generated for fields starting with $."); return false; } return true; } private boolean validNonStatic(@NotNull PsiField psiField, @NotNull final ProblemBuilder builder) { if (psiField.hasModifierProperty(PsiModifier.STATIC)) { builder.addWarning("Not generating wither for this field: Withers cannot be generated for static fields.", PsiQuickFixFactory.createModifierListFix(psiField, PsiModifier.STATIC, false, false)); return false; } return true; } private boolean validNonFinalInitialized(@NotNull PsiField psiField, @NotNull ProblemBuilder builder) { if (psiField.hasModifierProperty(PsiModifier.FINAL) && psiField.getInitializer() != null) { builder.addWarning("Not generating wither for this field: Withers cannot be generated for final, initialized fields.", PsiQuickFixFactory.createModifierListFix(psiField, PsiModifier.FINAL, false, false)); return false; } return true; } private boolean validIsWitherUnique(@NotNull PsiField psiField, @NotNull final ProblemBuilder builder) { final PsiClass fieldContainingClass = psiField.getContainingClass(); final String psiFieldName = psiField.getName(); if (psiFieldName != null && fieldContainingClass != null) { final Collection<PsiMethod> classMethods = PsiClassUtil.collectClassMethodsIntern(fieldContainingClass); final AccessorsInfo accessorsInfo = AccessorsInfo.build(psiField); final Collection<String> possibleWitherNames = LombokUtils.toAllWitherNames(accessorsInfo, psiFieldName, PsiType.BOOLEAN.equals(psiField.getType())); for (String witherName : possibleWitherNames) { if (PsiMethodUtil.hasSimilarMethod(classMethods, witherName, 1)) { builder.addWarning("Not generating %s(): A method with that name already exists", witherName); return false; } } } return true; } @SuppressWarnings("deprecation") public boolean validConstructor(@NotNull PsiClass psiClass, @NotNull ProblemBuilder builder) { if (PsiAnnotationSearchUtil.isAnnotatedWith(psiClass, AllArgsConstructor.class, Value.class, lombok.experimental.Value.class)) { return true; } final Collection<PsiField> constructorParameters = filterFields(psiClass); if (PsiAnnotationSearchUtil.isAnnotatedWith(psiClass, RequiredArgsConstructor.class, Data.class)) { final Collection<PsiField> requiredConstructorParameters = requiredArgsConstructorProcessor.getRequiredFields(psiClass); if (constructorParameters.size() == requiredConstructorParameters.size()) { return true; } } final Collection<PsiMethod> classConstructors = PsiClassUtil.collectClassConstructorIntern(psiClass); boolean constructorExists = false; for (PsiMethod classConstructor : classConstructors) { if (classConstructor.getParameterList().getParametersCount() == constructorParameters.size()) { constructorExists = true; break; } } if (!constructorExists) { builder.addWarning("@Wither needs constructor for all fields (%d parameters)", constructorParameters.size()); } return constructorExists; } private Collection<PsiField> filterFields(@NotNull PsiClass psiClass) { final Collection<PsiField> psiFields = PsiClassUtil.collectClassFieldsIntern(psiClass); Collection<PsiField> result = new ArrayList<PsiField>(psiFields.size()); for (PsiField classField : psiFields) { final String classFieldName = classField.getName(); if (classFieldName.startsWith(LombokUtils.LOMBOK_INTERN_FIELD_MARKER)) { continue; } if (classField.hasModifierProperty(PsiModifier.STATIC)) { continue; } if (classField.hasModifierProperty(PsiModifier.FINAL) && null != classField.getInitializer()) { continue; } result.add(classField); } return result; } @Nullable public PsiMethod createWitherMethod(@NotNull PsiField psiField, @NotNull String methodModifier, @NotNull AccessorsInfo accessorsInfo) { LombokLightMethodBuilder result = null; final PsiClass psiFieldContainingClass = psiField.getContainingClass(); if (psiFieldContainingClass != null) { final PsiType returnType = PsiClassUtil.getTypeWithGenerics(psiFieldContainingClass); final String psiFieldName = psiField.getName(); final PsiType psiFieldType = psiField.getType(); result = new LombokLightMethodBuilder(psiField.getManager(), getWitherName(accessorsInfo, psiFieldName, psiFieldType)) .withMethodReturnType(returnType) .withContainingClass(psiFieldContainingClass) .withNavigationElement(psiField) .withModifier(methodModifier); PsiAnnotation witherAnnotation = PsiAnnotationSearchUtil.findAnnotation(psiField, Wither.class); addOnXAnnotations(witherAnnotation, result.getModifierList(), "onMethod"); final LombokLightParameter methodParameter = new LombokLightParameter(psiFieldName, psiFieldType, result, JavaLanguage.INSTANCE); PsiModifierList methodParameterModifierList = methodParameter.getModifierList(); copyAnnotations(psiField, methodParameterModifierList, LombokUtils.NON_NULL_PATTERN, LombokUtils.NULLABLE_PATTERN, LombokUtils.DEPRECATED_PATTERN); addOnXAnnotations(witherAnnotation, methodParameterModifierList, "onParam"); result.withParameter(methodParameter); if (psiFieldContainingClass.hasModifierProperty(PsiModifier.ABSTRACT)) { result.withModifier(PsiModifier.ABSTRACT); } else { result.withBody(createCodeBlock(psiField, psiFieldContainingClass, returnType, psiFieldName)); } } return result; } @NotNull private PsiCodeBlock createCodeBlock(@NotNull PsiField psiField, PsiClass psiFieldContainingClass, PsiType returnType, String psiFieldName) { final String blockText; if (isShouldGenerateFullBodyBlock()) { final String paramString = getConstructorCall(psiField, psiFieldContainingClass); blockText = String.format("return this.%s == %s ? this : new %s(%s);", psiFieldName, psiFieldName, returnType.getCanonicalText(), paramString); } else { blockText = "return null;"; } return PsiMethodUtil.createCodeBlockFromText(blockText, psiFieldContainingClass); } private String getWitherName(@NotNull AccessorsInfo accessorsInfo, String psiFieldName, PsiType psiFieldType) { return LombokUtils.toWitherName(accessorsInfo, psiFieldName, PsiType.BOOLEAN.equals(psiFieldType)); } private String getConstructorCall(@NotNull PsiField psiField, @NotNull PsiClass psiClass) { final StringBuilder paramString = StringBuilderSpinAllocator.alloc(); try { final Collection<PsiField> psiFields = filterFields(psiClass); for (PsiField classField : psiFields) { final String classFieldName = classField.getName(); if (classField.equals(psiField)) { paramString.append(classFieldName); } else { paramString.append("this.").append(classFieldName); } paramString.append(','); } if (paramString.length() > 1) { paramString.deleteCharAt(paramString.length() - 1); } return paramString.toString(); } finally { StringBuilderSpinAllocator.dispose(paramString); } } @Override public LombokPsiElementUsage checkFieldUsage(@NotNull PsiField psiField, @NotNull PsiAnnotation psiAnnotation) { return LombokPsiElementUsage.READ_WRITE; } }