package de.plushnikov.intellij.plugin.action.lombok; import com.intellij.codeInsight.CodeInsightActionHandler; import com.intellij.codeInsight.generation.OverrideImplementUtil; import com.intellij.openapi.command.undo.UndoUtil; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnnotationParameterList; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiNameValuePair; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiParameterList; import com.intellij.psi.PsiType; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.util.PsiUtil; import de.plushnikov.intellij.plugin.util.LombokProcessorUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationSearchUtil; import de.plushnikov.intellij.plugin.util.PsiAnnotationUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.HashSet; import java.util.Map; import java.util.Set; public abstract class BaseLombokHandler implements CodeInsightActionHandler { public boolean startInWriteAction() { return true; } public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { if (file.isWritable()) { PsiClass psiClass = OverrideImplementUtil.getContextClass(project, editor, file, false); if (null != psiClass) { processClass(psiClass); UndoUtil.markPsiFileForUndo(file); } } } protected abstract void processClass(@NotNull PsiClass psiClass); protected void processIntern(@NotNull Map<PsiField, PsiMethod> fieldMethodMap, @NotNull PsiClass psiClass, @NotNull Class<? extends Annotation> annotationClass) { if (fieldMethodMap.isEmpty()) { return; } final PsiMethod firstPropertyMethod = fieldMethodMap.values().iterator().next(); final boolean useAnnotationOnClass = haveAllMethodsSameAccessLevel(fieldMethodMap.values()) && isNotAnnotatedWithOrSameAccessLevelAs(psiClass, firstPropertyMethod, annotationClass); if (useAnnotationOnClass) { addAnnotation(psiClass, firstPropertyMethod, annotationClass); } for (Map.Entry<PsiField, PsiMethod> fieldMethodEntry : fieldMethodMap.entrySet()) { final PsiField propertyField = fieldMethodEntry.getKey(); final PsiMethod propertyMethod = fieldMethodEntry.getValue(); if (null != propertyField) { boolean isStatic = propertyField.hasModifierProperty(PsiModifier.STATIC); if (isStatic || !useAnnotationOnClass) { addAnnotation(propertyField, propertyMethod, annotationClass); } // Move all annotations to field declaration for (PsiAnnotation psiMethodAnnotation : propertyMethod.getModifierList().getAnnotations()) { psiClass.addBefore(psiMethodAnnotation, propertyField); } propertyMethod.delete(); } } } private boolean isNotAnnotatedWithOrSameAccessLevelAs(PsiClass psiClass, PsiMethod firstPropertyMethod, Class<? extends Annotation> annotationClass) { final PsiAnnotation presentAnnotation = PsiAnnotationSearchUtil.findAnnotation(psiClass, annotationClass); if (null != presentAnnotation) { final String presentAccessModifier = LombokProcessorUtil.getMethodModifier(presentAnnotation); final String currentAccessModifier = PsiUtil.getAccessModifier(PsiUtil.getAccessLevel(firstPropertyMethod.getModifierList())); return (presentAccessModifier == null && currentAccessModifier == null) || (presentAccessModifier != null && presentAccessModifier.equals(currentAccessModifier)); } return true; } private boolean haveAllMethodsSameAccessLevel(Collection<PsiMethod> psiMethods) { final Set<Integer> accessLevelSet = new HashSet<Integer>(); for (PsiMethod psiMethod : psiMethods) { accessLevelSet.add(PsiUtil.getAccessLevel(psiMethod.getModifierList())); } return accessLevelSet.size() <= 1; } private void addAnnotation(@NotNull PsiModifierListOwner targetElement, @NotNull PsiModifierListOwner sourceElement, @NotNull Class<? extends Annotation> annotationClass) { final PsiAnnotation newPsiAnnotation = LombokProcessorUtil.createAnnotationWithAccessLevel(annotationClass, sourceElement); addAnnotation(targetElement, newPsiAnnotation, annotationClass); } protected void addAnnotation(@NotNull PsiClass targetElement, @NotNull Class<? extends Annotation> annotationClass) { final PsiAnnotation newPsiAnnotation = PsiAnnotationUtil.createPsiAnnotation(targetElement, annotationClass); addAnnotation(targetElement, newPsiAnnotation, annotationClass); } private void addAnnotation(@NotNull PsiModifierListOwner targetElement, @NotNull PsiAnnotation newPsiAnnotation, @NotNull Class<? extends Annotation> annotationClass) { final PsiAnnotation presentAnnotation = PsiAnnotationSearchUtil.findAnnotation(targetElement, annotationClass); final Project project = targetElement.getProject(); final JavaCodeStyleManager javaCodeStyleManager = JavaCodeStyleManager.getInstance(project); javaCodeStyleManager.shortenClassReferences(newPsiAnnotation); if (null == presentAnnotation) { PsiModifierList modifierList = targetElement.getModifierList(); if (null != modifierList) { modifierList.addAfter(newPsiAnnotation, null); } } else { presentAnnotation.setDeclaredAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME, newPsiAnnotation.findDeclaredAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME)); } } protected void removeDefaultAnnotation(@NotNull PsiModifierListOwner targetElement, @NotNull Class<? extends Annotation> annotationClass) { final PsiAnnotation psiAnnotation = PsiAnnotationSearchUtil.findAnnotation(targetElement, annotationClass); if (null != psiAnnotation) { boolean hasOnlyDefaultValues = true; final PsiAnnotationParameterList psiAnnotationParameterList = psiAnnotation.getParameterList(); for (PsiNameValuePair nameValuePair : psiAnnotationParameterList.getAttributes()) { if (null != psiAnnotation.findDeclaredAttributeValue(nameValuePair.getName())) { hasOnlyDefaultValues = false; break; } } if (hasOnlyDefaultValues) { psiAnnotation.delete(); } } } @Nullable protected PsiMethod findPublicNonStaticMethod(@NotNull PsiClass psiClass, @NotNull String methodName, @NotNull PsiType returnType, PsiType... params) { final PsiMethod[] toStringMethods = psiClass.findMethodsByName(methodName, false); for (PsiMethod method : toStringMethods) { if (method.hasModifierProperty(PsiModifier.PUBLIC) && !method.hasModifierProperty(PsiModifier.STATIC) && returnType.equals(method.getReturnType())) { final PsiParameterList parameterList = method.getParameterList(); final PsiParameter[] psiParameters = parameterList.getParameters(); final int paramsCount = params.length; if (psiParameters.length == paramsCount) { boolean allParametersFound = true; for (int i = 0; i < paramsCount; i++) { if (!psiParameters[i].getType().equals(params[i])) { allParametersFound = false; break; } } if (allParametersFound) { return method; } } } } return null; } }