package de.plushnikov.intellij.plugin.action.delombok; import com.intellij.openapi.command.undo.UndoUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.JavaPsiFacade; 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.PsiElementFactory; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJavaCodeReferenceElement; import com.intellij.psi.PsiJavaFile; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiNameValuePair; import com.intellij.psi.PsiParameter; import com.intellij.psi.PsiType; import com.intellij.psi.PsiTypeParameterList; import com.intellij.psi.PsiTypeParameterListOwner; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import de.plushnikov.intellij.plugin.processor.AbstractProcessor; import de.plushnikov.intellij.plugin.processor.ShouldGenerateFullCodeBlock; import de.plushnikov.intellij.plugin.settings.ProjectSettings; import de.plushnikov.intellij.plugin.util.PsiMethodUtil; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.List; public class BaseDelombokHandler { private final boolean processInnerClasses; private final Collection<AbstractProcessor> lombokProcessors; protected BaseDelombokHandler(AbstractProcessor... lombokProcessors) { this(false, lombokProcessors); } protected BaseDelombokHandler(boolean processInnerClasses, AbstractProcessor... lombokProcessors) { this.processInnerClasses = processInnerClasses; this.lombokProcessors = new ArrayList<AbstractProcessor>(Arrays.asList(lombokProcessors)); } public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @NotNull PsiClass psiClass) { if (psiFile.isWritable()) { invoke(project, psiClass, processInnerClasses); finish(project, psiFile); } } public void invoke(@NotNull Project project, @NotNull PsiJavaFile psiFile) { for (PsiClass psiClass : psiFile.getClasses()) { invoke(project, psiClass, true); } finish(project, psiFile); } private void invoke(Project project, PsiClass psiClass, boolean processInnerClasses) { Collection<PsiAnnotation> processedAnnotations = new HashSet<PsiAnnotation>(); for (AbstractProcessor lombokProcessor : lombokProcessors) { processedAnnotations.addAll(processClass(project, psiClass, lombokProcessor)); } if (processInnerClasses) { for (PsiClass innerClass : psiClass.getAllInnerClasses()) { invoke(project, innerClass, processInnerClasses); } } deleteAnnotations(processedAnnotations); } private void finish(Project project, PsiFile psiFile) { JavaCodeStyleManager.getInstance(project).optimizeImports(psiFile); UndoUtil.markPsiFileForUndo(psiFile); } private Collection<PsiAnnotation> processClass(@NotNull Project project, @NotNull PsiClass psiClass, @NotNull AbstractProcessor lombokProcessor) { Collection<PsiAnnotation> psiAnnotations = lombokProcessor.collectProcessedAnnotations(psiClass); final List<? super PsiElement> psiElements; ShouldGenerateFullCodeBlock.getInstance().activate(); try { psiElements = lombokProcessor.process(psiClass); } finally { ShouldGenerateFullCodeBlock.getInstance().deactivate(); } ProjectSettings.setLombokEnabledInProject(project, false); try { for (Object psiElement : psiElements) { final PsiElement element = rebuildPsiElement(project, (PsiElement) psiElement); if (null != element) { psiClass.add(element); } } } finally { ProjectSettings.setLombokEnabledInProject(project, true); } return psiAnnotations; } public Collection<PsiAnnotation> collectProcessableAnnotations(@NotNull PsiClass psiClass) { Collection<PsiAnnotation> result = new ArrayList<PsiAnnotation>(); for (AbstractProcessor lombokProcessor : lombokProcessors) { result.addAll(lombokProcessor.collectProcessedAnnotations(psiClass)); } return result; } private PsiElement rebuildPsiElement(@NotNull Project project, PsiElement psiElement) { if (psiElement instanceof PsiMethod) { return rebuildMethod(project, (PsiMethod) psiElement); } else if (psiElement instanceof PsiField) { return rebuildField(project, (PsiField) psiElement); } else if (psiElement instanceof PsiClass) { return rebuildClass(project, (PsiClass) psiElement); } return null; } private PsiClass rebuildClass(@NotNull Project project, @NotNull PsiClass fromClass) { final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); PsiClass resultClass = elementFactory.createClass(StringUtil.defaultIfEmpty(fromClass.getName(), "UnknownClassName")); copyModifiers(fromClass.getModifierList(), resultClass.getModifierList()); rebuildTypeParameter(fromClass, resultClass); for (PsiField psiField : fromClass.getFields()) { resultClass.add(rebuildField(project, psiField)); } for (PsiMethod psiMethod : fromClass.getMethods()) { resultClass.add(rebuildMethod(project, psiMethod)); } return (PsiClass) CodeStyleManager.getInstance(project).reformat(resultClass); } private PsiMethod rebuildMethod(@NotNull Project project, @NotNull PsiMethod fromMethod) { final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); final PsiMethod resultMethod; final PsiType returnType = fromMethod.getReturnType(); if (null == returnType) { resultMethod = elementFactory.createConstructor(fromMethod.getName()); } else { resultMethod = elementFactory.createMethod(fromMethod.getName(), returnType); } rebuildTypeParameter(fromMethod, resultMethod); final PsiClassType[] referencedTypes = fromMethod.getThrowsList().getReferencedTypes(); if (referencedTypes.length > 0) { PsiJavaCodeReferenceElement[] refs = new PsiJavaCodeReferenceElement[referencedTypes.length]; for (int i = 0; i < refs.length; i++) { refs[i] = elementFactory.createReferenceElementByType(referencedTypes[i]); } resultMethod.getThrowsList().replace(elementFactory.createReferenceList(refs)); } for (PsiParameter parameter : fromMethod.getParameterList().getParameters()) { PsiParameter param = elementFactory.createParameter(parameter.getName(), parameter.getType()); if (parameter.getModifierList() != null) { PsiModifierList modifierList = param.getModifierList(); for (PsiAnnotation originalAnnotation : parameter.getModifierList().getAnnotations()) { final PsiAnnotation annotation = modifierList.addAnnotation(originalAnnotation.getQualifiedName()); for (PsiNameValuePair nameValuePair : originalAnnotation.getParameterList().getAttributes()) { annotation.setDeclaredAttributeValue(nameValuePair.getName(), nameValuePair.getValue()); } } } resultMethod.getParameterList().add(param); } final PsiModifierList fromMethodModifierList = fromMethod.getModifierList(); final PsiModifierList resultMethodModifierList = resultMethod.getModifierList(); copyModifiers(fromMethodModifierList, resultMethodModifierList); for (PsiAnnotation psiAnnotation : fromMethodModifierList.getAnnotations()) { final PsiAnnotation annotation = resultMethodModifierList.addAnnotation(psiAnnotation.getQualifiedName()); for (PsiNameValuePair nameValuePair : psiAnnotation.getParameterList().getAttributes()) { annotation.setDeclaredAttributeValue(nameValuePair.getName(), nameValuePair.getValue()); } } PsiCodeBlock body = fromMethod.getBody(); if (null != body) { resultMethod.getBody().replace(body); } return (PsiMethod) CodeStyleManager.getInstance(project).reformat(resultMethod); } private void rebuildTypeParameter(@NotNull PsiTypeParameterListOwner listOwner, @NotNull PsiTypeParameterListOwner resultOwner) { final PsiTypeParameterList fromMethodTypeParameterList = listOwner.getTypeParameterList(); if (listOwner.hasTypeParameters() && null != fromMethodTypeParameterList) { PsiTypeParameterList typeParameterList = PsiMethodUtil.createTypeParameterList(fromMethodTypeParameterList); if (null != typeParameterList) { final PsiTypeParameterList resultOwnerTypeParameterList = resultOwner.getTypeParameterList(); if (null != resultOwnerTypeParameterList) { resultOwnerTypeParameterList.replace(typeParameterList); } } } } private void copyModifiers(PsiModifierList fromModifierList, PsiModifierList resultModifierList) { for (String modifier : PsiModifier.MODIFIERS) { resultModifierList.setModifierProperty(modifier, fromModifierList.hasModifierProperty(modifier)); } } private PsiField rebuildField(@NotNull Project project, @NotNull PsiField fromField) { final PsiElementFactory elementFactory = JavaPsiFacade.getElementFactory(project); final PsiField resultField = elementFactory.createField(fromField.getName(), fromField.getType()); copyModifiers(fromField.getModifierList(), resultField.getModifierList()); resultField.setInitializer(fromField.getInitializer()); return (PsiField) CodeStyleManager.getInstance(project).reformat(resultField); } private void deleteAnnotations(Collection<PsiAnnotation> psiAnnotations) { for (PsiAnnotation psiAnnotation : psiAnnotations) { psiAnnotation.delete(); } } }