/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.refactor.intoduceField; import com.intellij.codeInsight.TestFrameworks; import com.intellij.codeInsight.navigation.NavigationUtil; import com.intellij.ide.util.PsiClassListCellRenderer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.JspPsiUtil; import com.intellij.psi.PsiAnnotation; import com.intellij.psi.PsiAnonymousClass; import com.intellij.psi.PsiAssignmentExpression; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiClassInitializer; import com.intellij.psi.PsiCodeBlock; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementFactory; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionStatement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiModifierListOwner; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiStatement; import com.intellij.psi.PsiType; import com.intellij.psi.PsiVariable; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.GeneratedMarkerVisitor; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.search.searches.ReferencesSearch; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.refactoring.HelpID; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.EnumConstantsUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.VisibilityUtil; import gw.plugin.ij.lang.psi.impl.statements.GosuFieldImpl; import gw.plugin.ij.lang.psi.util.GosuPsiParseUtil; import gw.plugin.ij.refactor.GosuCodeInsightUtil; import gw.plugin.ij.refactor.GosuRefactoringUtil; import gw.plugin.ij.util.ClassLord; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; import static gw.plugin.ij.refactor.intoduceField.GosuBaseExpressionToFieldHandler.InitializationPlace.IN_CONSTRUCTOR; import static gw.plugin.ij.refactor.intoduceField.GosuBaseExpressionToFieldHandler.InitializationPlace.IN_FIELD_DECLARATION; public abstract class GosuLocalToFieldHandler { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceField.LocalToFieldHandler"); private static final String REFACTORING_NAME = RefactoringBundle.message("convert.local.to.field.title"); private final Project myProject; private final boolean myIsConstant; private int fff; public GosuLocalToFieldHandler(Project project, boolean isConstant) { myProject = project; myIsConstant = isConstant; } protected abstract GosuBaseExpressionToFieldHandler.Settings showRefactoringDialog(PsiClass aClass, PsiLocalVariable local, PsiExpression[] occurences, boolean isStatic); public boolean convertLocalToField(final PsiLocalVariable local, final Editor editor) { boolean tempIsStatic = myIsConstant; PsiElement parent = local.getParent(); List<PsiClass> classes = new ArrayList<>(); if ("gr".equalsIgnoreCase(local.getContainingFile().getVirtualFile().getExtension())) { //this is special ij GW studio check for rule files CommonRefactoringUtil.showErrorHint(myProject, editor, "Introduce field is not supported for rule files", REFACTORING_NAME, HelpID.LOCAL_TO_FIELD); return false; } while (parent != null && parent.getContainingFile() != null) { if (parent instanceof PsiClass && !(myIsConstant && parent instanceof PsiAnonymousClass)) { classes.add((PsiClass) parent); } if (parent instanceof PsiFile && JspPsiUtil.isInJspFile(parent)) { String message = RefactoringBundle.message("error.not.supported.for.jsp", REFACTORING_NAME); CommonRefactoringUtil.showErrorHint(myProject, editor, message, REFACTORING_NAME, HelpID.LOCAL_TO_FIELD); return false; } if (parent instanceof PsiModifierListOwner && ((PsiModifierListOwner) parent).hasModifierProperty(PsiModifier.STATIC)) { tempIsStatic = true; } parent = parent.getParent(); } if (classes.isEmpty()) { return false; } if (classes.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) { if (convertLocalToField(local, classes.get(getChosenClassIndex(classes)), editor, tempIsStatic)) { return false; } } else { final boolean isStatic = tempIsStatic; NavigationUtil.getPsiElementPopup(classes.toArray(new PsiClass[classes.size()]), new PsiClassListCellRenderer(), "Choose class to introduce " + (myIsConstant ? "constant" : "field"), new PsiElementProcessor<PsiClass>() { @Override public boolean execute(@NotNull PsiClass aClass) { convertLocalToField(local, aClass, editor, isStatic); return false; } }).showInBestPositionFor(editor); } return true; } protected int getChosenClassIndex(List<PsiClass> classes) { return classes.size() - 1; } private boolean convertLocalToField(PsiLocalVariable local, PsiClass aClass, Editor editor, boolean isStatic) { final PsiExpression[] occurences = GosuCodeInsightUtil.findReferenceExpressions(GosuRefactoringUtil.getVariableScope(local), local); if (editor != null) { GosuRefactoringUtil.highlightAllOccurrences(myProject, occurences, editor); } final GosuBaseExpressionToFieldHandler.Settings settings = showRefactoringDialog(aClass, local, occurences, isStatic); if (settings == null) { return true; } //LocalToFieldDialog dialog = new LocalToFieldDialog(project, aClass, local, isStatic); final PsiClass destinationClass = settings.getDestinationClass(); boolean rebindNeeded = false; if (destinationClass != null) { aClass = destinationClass; rebindNeeded = true; } final PsiClass aaClass = aClass; final boolean rebindNeeded1 = rebindNeeded; final Runnable runnable = new IntroduceFieldRunnable(rebindNeeded1, local, aaClass, settings, isStatic, occurences); CommandProcessor.getInstance().executeCommand(myProject, new Runnable() { public void run() { ApplicationManager.getApplication().runWriteAction(runnable); } }, REFACTORING_NAME, null); return false; } private static PsiField createField(PsiLocalVariable local, PsiType forcedType, String fieldName, boolean includeInitializer) { @NonNls StringBuilder pattern = new StringBuilder(); pattern.append("var "); pattern.append(fieldName); PsiType type = local.getType(); if (local.getInitializer() == null) { includeInitializer = false; } else { type = local.getInitializer().getType(); } pattern.append(": ").append(type.getPresentableText()); if (includeInitializer) { pattern.append(" = ").append(local.getInitializer().getText()); } final PsiElement psiElement = GosuPsiParseUtil.parseProgramm(pattern.toString(), PsiManager.getInstance(local.getProject()), null); final GosuFieldImpl field = PsiTreeUtil.findChildOfType(psiElement, GosuFieldImpl.class); GeneratedMarkerVisitor.markGenerated(field); try { final PsiModifierList modifierList = local.getModifierList(); if (modifierList != null) { for (PsiAnnotation annotation : modifierList.getAnnotations()) { field.getModifierList().add(annotation.copy()); } } return field; } catch (IncorrectOperationException e) { LOG.error(e); return null; } } private static PsiStatement createAssignment(PsiLocalVariable local, String fieldname, PsiElementFactory factory) { try { PsiAssignmentExpression assignmentExpression = GosuPsiParseUtil.createAssignmentStatement(local.getProject(), fieldname, local.getInitializer()); return (PsiStatement) CodeStyleManager.getInstance(local.getProject()).reformat(assignmentExpression); } catch (IncorrectOperationException e) { LOG.error(e); return null; } } private static PsiStatement addInitializationToSetUp(final PsiLocalVariable local, final PsiField field, final PsiElementFactory factory) throws IncorrectOperationException { PsiMethod inClass = TestFrameworks.getInstance().findOrCreateSetUpMethod(field.getContainingClass()); assert inClass != null; PsiStatement assignment = createAssignment(local, field.getName(), factory); final PsiCodeBlock body = inClass.getBody(); assert body != null; if (PsiTreeUtil.isAncestor(body, local, false)) { assignment = (PsiStatement) body.addBefore(assignment, PsiTreeUtil.getParentOfType(local, PsiStatement.class)); } else { assignment = (PsiStatement) body.add(assignment); } local.delete(); return assignment; } private static PsiStatement addInitializationToConstructors(PsiLocalVariable local, PsiField field, PsiMethod enclosingConstructor, PsiElementFactory factory) throws IncorrectOperationException { PsiClass aClass = field.getContainingClass(); PsiMethod[] constructors = aClass.getConstructors(); PsiStatement assignment = createAssignment(local, field.getName(), factory); boolean added = false; for (PsiMethod constructor : constructors) { if (constructor == enclosingConstructor) { continue; } PsiCodeBlock body = constructor.getBody(); if (body == null) { continue; } PsiStatement[] statements = body.getStatements(); if (statements.length > 0) { PsiStatement first = statements[0]; if (first instanceof PsiExpressionStatement) { PsiExpression expression = ((PsiExpressionStatement) first).getExpression(); if (expression instanceof PsiMethodCallExpression) { @NonNls String text = ((PsiMethodCallExpression) expression).getMethodExpression().getText(); if ("this".equals(text)) { continue; } if ("super".equals(text) && enclosingConstructor == null && PsiTreeUtil.isAncestor(constructor, local, false)) { local.delete(); return (PsiStatement) body.addAfter(assignment, first); } } } if (enclosingConstructor == null && PsiTreeUtil.isAncestor(constructor, local, false)) { local.delete(); return (PsiStatement) body.addBefore(assignment, first); } } assignment = (PsiStatement) body.add(assignment); added = true; } if (!added && enclosingConstructor == null) { if (aClass instanceof PsiAnonymousClass) { final PsiClassInitializer classInitializer = (PsiClassInitializer) aClass.addAfter(factory.createClassInitializer(), field); assignment = (PsiStatement) classInitializer.getBody().add(assignment); } else { PsiMethod constructor = (PsiMethod) aClass.add(GosuPsiParseUtil.createConstructor(aClass.getName(), aClass.getManager(), null)); assignment = (PsiStatement) constructor.getBody().add(assignment); } } if (enclosingConstructor == null) { local.delete(); } return assignment; } static class IntroduceFieldRunnable implements Runnable { private static final String LOCAL_VARIABLE_WITH_THIS_NAME_ALREADY_EXISTS = "Local variable with this name already exists"; private final String myVariableName; private final String myFieldName; private final boolean myRebindNeeded; private final PsiLocalVariable myLocal; private final Project myProject; private final PsiClass myDestinationClass; private final GosuBaseExpressionToFieldHandler.Settings mySettings; private final GosuBaseExpressionToFieldHandler.InitializationPlace myInitializerPlace; private final PsiExpression[] myOccurences; private PsiField myField; private PsiStatement myAssignmentStatement; public IntroduceFieldRunnable(boolean rebindNeeded, PsiLocalVariable local, PsiClass aClass, GosuBaseExpressionToFieldHandler.Settings settings, boolean isStatic, PsiExpression[] occurrences) { myVariableName = local.getName(); myFieldName = settings.getFieldName(); myRebindNeeded = rebindNeeded; myLocal = local; myProject = local.getProject(); myDestinationClass = aClass; mySettings = settings; myInitializerPlace = settings.getInitializerPlace(); myOccurences = occurrences; } public void run() { try { final boolean rebindNeeded2 = !myVariableName.equals(myFieldName) || myRebindNeeded; final PsiReference[] refs; if (rebindNeeded2) { refs = ReferencesSearch.search(myLocal, GlobalSearchScope.projectScope(myProject), false).toArray(new PsiReference[0]); } else { refs = null; } final JavaPsiFacade facade = JavaPsiFacade.getInstance(myProject); PsiVariable psiVariable = facade.getResolveHelper().resolveAccessibleReferencedVariable(myFieldName, myLocal); if (psiVariable != null && (!psiVariable.equals(myLocal))) { CommonRefactoringUtil.showErrorMessage( GosuIntroduceFieldHandler.REFACTORING_NAME, LOCAL_VARIABLE_WITH_THIS_NAME_ALREADY_EXISTS, HelpID.INTRODUCE_FIELD, myProject ); return; } if (refs != null) { for (PsiReference occur : refs) { psiVariable = facade.getResolveHelper().resolveAccessibleReferencedVariable(myFieldName, (PsiElement) occur); if (psiVariable != null && (psiVariable.getName().equals(myFieldName))) { CommonRefactoringUtil.showErrorMessage( GosuIntroduceFieldHandler.REFACTORING_NAME, LOCAL_VARIABLE_WITH_THIS_NAME_ALREADY_EXISTS, HelpID.INTRODUCE_FIELD, myProject ); return; } } } final PsiMethod enclosingConstructor = GosuBaseExpressionToFieldHandler.getEnclosingConstructor(myDestinationClass, myLocal); myField = mySettings.isIntroduceEnumConstant() ? EnumConstantsUtil.createEnumConstant(myDestinationClass, myLocal, myFieldName) : createField(myLocal, mySettings.getForcedType(), myFieldName, myInitializerPlace == IN_FIELD_DECLARATION); myField = (PsiField) myDestinationClass.add(myField); GosuBaseExpressionToFieldHandler.setModifiers(myField, mySettings); if (!mySettings.isIntroduceEnumConstant()) { VisibilityUtil.fixVisibility(myOccurences, myField, mySettings.getFieldVisibility()); } myLocal.normalizeDeclaration(); // PsiDeclarationStatement declarationStatement = (PsiDeclarationStatement) myLocal.getParent(); final GosuBaseExpressionToFieldHandler.InitializationPlace finalInitializerPlace; if (myLocal.getInitializer() == null) { finalInitializerPlace = IN_FIELD_DECLARATION; } else { finalInitializerPlace = myInitializerPlace; } final PsiElementFactory factory = JavaPsiFacade.getInstance(myProject).getElementFactory(); switch (finalInitializerPlace) { case IN_FIELD_DECLARATION: // declarationStatement.delete(); myLocal.delete(); break; case IN_CURRENT_METHOD: PsiStatement statement = createAssignment(myLocal, myFieldName, null); // myAssignmentStatement = (PsiStatement) declarationStatement.replace(statement); myAssignmentStatement = (PsiStatement) myLocal.replace(statement); GeneratedMarkerVisitor.markGenerated(myAssignmentStatement); break; case IN_CONSTRUCTOR: myAssignmentStatement = addInitializationToConstructors(myLocal, myField, enclosingConstructor, factory); GeneratedMarkerVisitor.markGenerated(myAssignmentStatement); break; case IN_SETUP_METHOD: // myAssignmentStatement = addInitializationToSetUp(myLocal, myField, factory); } if (enclosingConstructor != null && myInitializerPlace == IN_CONSTRUCTOR) { PsiStatement statement = createAssignment(myLocal, myFieldName, null); // myAssignmentStatement = (PsiStatement) declarationStatement.replace(statement); myAssignmentStatement = (PsiStatement) myLocal.replace(statement); GeneratedMarkerVisitor.markGenerated(myAssignmentStatement); } if (rebindNeeded2) { for (final PsiReference reference : refs) { if (reference != null) { //expr = GosuRefactoringUtil.outermostParenthesizedExpression(expr); GosuRefactoringUtil.replaceOccurenceWithFieldRef((PsiExpression) reference, myField, myDestinationClass); //replaceOccurenceWithFieldRef((PsiExpression)reference, field, aaClass); } } //GosuRefactoringUtil.renameVariableReferences(local, pPrefix + fieldName, GlobalSearchScope.projectScope(myProject)); } ClassLord.doImportAndStick(mySettings.getForcedType().getCanonicalText(), myDestinationClass.getContainingFile()); } catch (IncorrectOperationException e) { LOG.error(e); } } public PsiField getField() { return myField; } public PsiStatement getAssignmentStatement() { return myAssignmentStatement; } } }