package de.plushnikov.intellij.plugin.processor.clazz; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiArrayType; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiPrimitiveType; import com.intellij.psi.PsiType; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.util.StringBuilderSpinAllocator; import de.plushnikov.intellij.plugin.lombokconfig.ConfigKey; import de.plushnikov.intellij.plugin.problem.ProblemBuilder; import de.plushnikov.intellij.plugin.processor.LombokPsiElementUsage; import de.plushnikov.intellij.plugin.psi.LombokLightMethodBuilder; import de.plushnikov.intellij.plugin.quickfix.PsiQuickFixFactory; import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil; import de.plushnikov.intellij.plugin.util.PsiClassUtil; import de.plushnikov.intellij.plugin.util.PsiMethodUtil; import lombok.ToString; import org.jetbrains.annotations.NotNull; import java.util.Collection; import java.util.Collections; import java.util.List; /** * Inspect and validate @ToString lombok annotation on a class * Creates toString() method for fields of this class * * @author Plushnikov Michail */ public class ToStringProcessor extends AbstractClassProcessor { public static final String METHOD_NAME = "toString"; public ToStringProcessor() { super(PsiMethod.class, ToString.class); } @Override protected boolean validate(@NotNull PsiAnnotation psiAnnotation, @NotNull PsiClass psiClass, @NotNull ProblemBuilder builder) { final boolean result = validateAnnotationOnRigthType(psiClass, builder); if (result) { validateExistingMethods(psiClass, builder); } final Collection<String> excludeProperty = PsiAnnotationUtil.getAnnotationValues(psiAnnotation, "exclude", String.class); final Collection<String> ofProperty = PsiAnnotationUtil.getAnnotationValues(psiAnnotation, "of", String.class); if (!excludeProperty.isEmpty() && !ofProperty.isEmpty()) { builder.addWarning("exclude and of are mutually exclusive; the 'exclude' parameter will be ignored", PsiQuickFixFactory.createChangeAnnotationParameterFix(psiAnnotation, "exclude", null)); } else { validateExcludeParam(psiClass, builder, psiAnnotation, excludeProperty); } validateOfParam(psiClass, builder, psiAnnotation, ofProperty); return result; } private boolean validateAnnotationOnRigthType(@NotNull PsiClass psiClass, @NotNull ProblemBuilder builder) { boolean result = true; if (psiClass.isAnnotationType() || psiClass.isInterface()) { builder.addError("@ToString is only supported on a class or enum type"); result = false; } return result; } private boolean validateExistingMethods(@NotNull PsiClass psiClass, @NotNull ProblemBuilder builder) { boolean result = true; final Collection<PsiMethod> classMethods = PsiClassUtil.collectClassMethodsIntern(psiClass); if (PsiMethodUtil.hasMethodByName(classMethods, METHOD_NAME)) { builder.addWarning("Not generated '%s'(): A method with same name already exists", METHOD_NAME); result = false; } return result; } protected void generatePsiElements(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation, @NotNull List<? super PsiElement> target) { target.addAll(createToStringMethod(psiClass, psiAnnotation)); } @NotNull Collection<PsiMethod> createToStringMethod(@NotNull PsiClass psiClass, @NotNull PsiAnnotation psiAnnotation) { final Collection<PsiMethod> classMethods = PsiClassUtil.collectClassMethodsIntern(psiClass); if (PsiMethodUtil.hasMethodByName(classMethods, METHOD_NAME)) { return Collections.emptyList(); } final Collection<PsiField> psiFields = filterFields(psiClass, psiAnnotation, false); final PsiMethod stringMethod = createToStringMethod(psiClass, psiFields, psiAnnotation); return Collections.singletonList(stringMethod); } @NotNull public PsiMethod createToStringMethod(@NotNull PsiClass psiClass, @NotNull Collection<PsiField> psiFields, @NotNull PsiAnnotation psiAnnotation) { final PsiManager psiManager = psiClass.getManager(); return new LombokLightMethodBuilder(psiManager, METHOD_NAME) .withMethodReturnType(PsiType.getJavaLangString(psiManager, GlobalSearchScope.allScope(psiClass.getProject()))) .withContainingClass(psiClass) .withNavigationElement(psiAnnotation) .withModifier(PsiModifier.PUBLIC) .withBody(createCodeBlock(psiClass, psiFields, psiAnnotation)); } @NotNull private PsiCodeBlock createCodeBlock(@NotNull PsiClass psiClass, @NotNull Collection<PsiField> psiFields, @NotNull PsiAnnotation psiAnnotation) { final String blockText; if (isShouldGenerateFullBodyBlock()) { final String paramString = createParamString(psiClass, psiFields, psiAnnotation); blockText = String.format("return \"%s(%s)\";", getSimpleClassName(psiClass), paramString); } else { blockText = "return \"\";"; } return PsiMethodUtil.createCodeBlockFromText(blockText, psiClass); } private String getSimpleClassName(@NotNull PsiClass psiClass) { final StringBuilder psiClassName = new StringBuilder(); PsiClass containingClass = psiClass; do { if (psiClassName.length() > 0) { psiClassName.insert(0, '.'); } psiClassName.insert(0, containingClass.getName()); containingClass = containingClass.getContainingClass(); } while (null != containingClass); return psiClassName.toString(); } private String createParamString(@NotNull PsiClass psiClass, @NotNull Collection<PsiField> psiFields, @NotNull PsiAnnotation psiAnnotation) { final boolean callSuper = PsiAnnotationUtil.getBooleanAnnotationValue(psiAnnotation, "callSuper", false); final boolean doNotUseGetters = readAnnotationOrConfigProperty(psiAnnotation, psiClass, "doNotUseGetters", ConfigKey.TOSTRING_DO_NOT_USE_GETTERS); final boolean includeFieldNames = readAnnotationOrConfigProperty(psiAnnotation, psiClass, "includeFieldNames", ConfigKey.TOSTRING_INCLUDE_FIELD_NAMES); final StringBuilder paramString = StringBuilderSpinAllocator.alloc(); try { if (callSuper) { paramString.append("super=\" + super.toString() + \", "); } for (PsiField classField : psiFields) { final String fieldName = classField.getName(); if (includeFieldNames) { paramString.append(fieldName).append('='); } paramString.append("\"+"); final PsiType classFieldType = classField.getType(); if (classFieldType instanceof PsiArrayType) { final PsiType componentType = ((PsiArrayType) classFieldType).getComponentType(); if (componentType instanceof PsiPrimitiveType) { paramString.append("java.util.Arrays.toString("); } else { paramString.append("java.util.Arrays.deepToString("); } } final String fieldAccessor = buildAttributeNameString(doNotUseGetters, classField, psiClass); paramString.append("this.").append(fieldAccessor); if (classFieldType instanceof PsiArrayType) { paramString.append(")"); } paramString.append("+\", "); } if (paramString.length() > 2) { paramString.delete(paramString.length() - 2, paramString.length()); } return paramString.toString(); } finally { StringBuilderSpinAllocator.dispose(paramString); } } @Override public LombokPsiElementUsage checkFieldUsage(@NotNull PsiField psiField, @NotNull PsiAnnotation psiAnnotation) { final PsiClass containingClass = psiField.getContainingClass(); if (null != containingClass) { if (PsiClassUtil.getNames(filterFields(containingClass, psiAnnotation, false)).contains(psiField.getName())) { return LombokPsiElementUsage.READ; } } return LombokPsiElementUsage.NONE; } }