/* * Copyright 2013 Guidewire Software, Inc. */ package gw.plugin.ij.refactor.intoduceField; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.ChangeContextUtil; import com.intellij.codeInsight.TestFrameworks; import com.intellij.codeInsight.highlighting.HighlightManager; import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.navigation.NavigationUtil; import com.intellij.ide.util.DirectoryChooserUtil; import com.intellij.ide.util.PackageUtil; import com.intellij.ide.util.PsiClassListCellRenderer; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Pass; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.JavaDirectoryService; import com.intellij.psi.JavaPsiFacade; import com.intellij.psi.JavaRecursiveElementWalkingVisitor; import com.intellij.psi.JavaTokenType; 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.PsiComment; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiExpression; import com.intellij.psi.PsiExpressionStatement; import com.intellij.psi.PsiField; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiJavaToken; import com.intellij.psi.PsiLocalVariable; import com.intellij.psi.PsiManager; import com.intellij.psi.PsiMember; import com.intellij.psi.PsiMethod; import com.intellij.psi.PsiMethodCallExpression; import com.intellij.psi.PsiModifier; import com.intellij.psi.PsiModifierList; import com.intellij.psi.PsiPackage; import com.intellij.psi.PsiParenthesizedExpression; import com.intellij.psi.PsiReference; import com.intellij.psi.PsiReferenceExpression; import com.intellij.psi.PsiStatement; import com.intellij.psi.PsiType; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.JavaCodeStyleManager; import com.intellij.psi.impl.GeneratedMarkerVisitor; import com.intellij.psi.impl.source.codeStyle.CodeEditUtil; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.search.PsiElementProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.PsiUtilBase; import com.intellij.refactoring.IntroduceHandlerBase; import com.intellij.refactoring.RefactoringBundle; import com.intellij.refactoring.rename.RenameJavaVariableProcessor; import com.intellij.refactoring.util.CommonRefactoringUtil; import com.intellij.refactoring.util.EnumConstantsUtil; import com.intellij.refactoring.util.occurrences.OccurrenceManager; import com.intellij.util.IncorrectOperationException; import com.intellij.util.VisibilityUtil; import gw.internal.gosu.parser.statements.CatchClause; import gw.plugin.ij.lang.psi.IGosuPsiElement; import gw.plugin.ij.lang.psi.impl.statements.GosuFieldImpl; import gw.plugin.ij.lang.psi.impl.statements.GosuForEachStatementImpl; import gw.plugin.ij.lang.psi.impl.statements.GosuUsingStatementImpl; import gw.plugin.ij.lang.psi.impl.statements.params.GosuParameterListImpl; import gw.plugin.ij.lang.psi.util.GosuPsiParseUtil; import gw.plugin.ij.refactor.GosuRefactoringUtil; import gw.plugin.ij.refactor.introduceVariable.GosuIntroduceVariableBase; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; public abstract class GosuBaseExpressionToFieldHandler extends IntroduceHandlerBase { private static final Logger LOG = Logger.getInstance("#com.intellij.refactoring.introduceField.BaseExpressionToFieldHandler"); public enum InitializationPlace { IN_CURRENT_METHOD, IN_FIELD_DECLARATION, IN_CONSTRUCTOR, IN_SETUP_METHOD } private final boolean myIsConstant; private PsiClass myParentClass; protected GosuBaseExpressionToFieldHandler(boolean isConstant) { myIsConstant = isConstant; } protected boolean invokeImpl(final Project project, @NotNull final PsiExpression selectedExpr, final Editor editor) { final PsiElement element = getPhysicalElement(selectedExpr); final PsiFile file = element.getContainingFile(); LOG.assertTrue(file != null, "expr.getContainingFile() == null"); if (LOG.isDebugEnabled()) { LOG.debug("expression:" + selectedExpr); } final PsiType tempType = getTypeByExpression(selectedExpr); if (tempType == null) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("unknown.expression.type")); CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), getHelpID()); return false; } if (PsiType.VOID.equals(tempType)) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("selected.expression.has.void.type")); CommonRefactoringUtil.showErrorHint(project, editor, message, getRefactoringName(), getHelpID()); return false; } myParentClass = getParentClass(selectedExpr); final List<PsiClass> classes = new ArrayList<>(); PsiClass aClass = myParentClass; while (aClass != null) { classes.add(aClass); final PsiField psiField = ConvertToFieldRunnable.checkForwardRefs(selectedExpr, aClass); if (psiField != null && psiField.getParent() == aClass) { break; } aClass = PsiTreeUtil.getParentOfType(aClass, PsiClass.class, true); } if (classes.size() == 1 || editor == null || ApplicationManager.getApplication().isUnitTestMode()) { return !convertExpressionToField(selectedExpr, editor, file, project, tempType); } else { PsiClass selection = null; for (PsiClass psiClass : classes) { if (!(psiClass instanceof PsiAnonymousClass)) { selection = psiClass; break; } } 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) { myParentClass = aClass; convertExpressionToField(selectedExpr, editor, file, project, tempType); return false; } }, selection).showInBestPositionFor(editor); } return true; } private boolean convertExpressionToField(PsiExpression selectedExpr, @Nullable Editor editor, PsiFile file, final Project project, PsiType tempType) { if (myParentClass == null) { if (JspPsiUtil.isInJspFile(file)) { CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.message("error.not.supported.for.jsp", getRefactoringName()), getRefactoringName(), getHelpID()); return true; } else { LOG.assertTrue(false); return true; } } if (!validClass(myParentClass, editor)) { return true; } if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) { return true; } final OccurrenceManager occurrenceManager = createOccurrenceManager(selectedExpr, myParentClass); final PsiExpression[] occurrences = occurrenceManager.getOccurrences(); final PsiElement anchorStatementIfAll = occurrenceManager.getAnchorStatementForAll(); List<RangeHighlighter> highlighters = null; if (editor != null) { highlighters = GosuRefactoringUtil.highlightAllOccurrences(project, occurrences, editor); } PsiElement tempAnchorElement = GosuRefactoringUtil.getParentExpressionAnchorElement(selectedExpr); if (!Comparing.strEqual(GosuIntroduceConstantHandler.REFACTORING_NAME, getRefactoringName()) && GosuIntroduceVariableBase.checkAnchorBeforeThisOrSuper(project, editor, tempAnchorElement, getRefactoringName(), getHelpID())) { return true; } final Settings settings = showRefactoringDialog(project, editor, myParentClass, selectedExpr, tempType, occurrences, tempAnchorElement, anchorStatementIfAll, false); if (settings == null) { return true; } if (settings.getForcedType() != null) { tempType = settings.getForcedType(); } final PsiType type = tempType; if (editor != null) { HighlightManager highlightManager = HighlightManager.getInstance(project); for (RangeHighlighter highlighter : highlighters) { highlightManager.removeSegmentHighlighter(editor, highlighter); } } final Runnable runnable = new ConvertToFieldRunnable(settings.getSelectedExpr(), settings, type, settings.getOccurrences(), occurrenceManager, anchorStatementIfAll, tempAnchorElement, editor, myParentClass); new WriteCommandAction(project, getRefactoringName()) { @Override protected void run(Result result) throws Throwable { runnable.run(); } }.execute(); return false; } public static void setModifiers(PsiField field, Settings settings) { if (!settings.isIntroduceEnumConstant()) { if (settings.isDeclareStatic()) { PsiUtil.setModifierProperty(field, PsiModifier.STATIC, true); } if (settings.isDeclareFinal()) { PsiUtil.setModifierProperty(field, PsiModifier.FINAL, true); } if (settings.isAnnotateAsNonNls()) { PsiAnnotation annotation = JavaPsiFacade.getInstance(field.getProject()).getElementFactory() .createAnnotationFromText("@" + AnnotationUtil.NON_NLS, field); final PsiModifierList modifierList = field.getModifierList(); LOG.assertTrue(modifierList != null); modifierList.addAfter(annotation, null); } } JavaCodeStyleManager.getInstance(field.getProject()).shortenClassReferences(field); } public static PsiElement getPhysicalElement(final PsiExpression selectedExpr) { PsiElement element = selectedExpr.getUserData(GosuElementToWorkOn.PARENT); if (element == null) { element = selectedExpr; } return element; } private static TextAttributes highlightAttributes() { return EditorColorsManager.getInstance().getGlobalScheme().getAttributes( EditorColors.SEARCH_RESULT_ATTRIBUTES ); } protected abstract OccurrenceManager createOccurrenceManager(PsiExpression selectedExpr, PsiClass parentClass); protected final PsiClass getParentClass() { return myParentClass; } protected abstract boolean validClass(PsiClass parentClass, Editor editor); private static PsiElement getNormalizedAnchor(PsiElement anchorElement) { PsiElement child = anchorElement; while (child != null) { PsiElement prev = child.getPrevSibling(); if (GosuRefactoringUtil.isExpressionAnchorElement(prev)) { break; } if (prev instanceof PsiJavaToken && ((PsiJavaToken) prev).getTokenType() == JavaTokenType.LBRACE) { break; } child = prev; } child = PsiTreeUtil.skipSiblingsForward(child, PsiWhiteSpace.class, PsiComment.class); PsiElement anchor; if (child != null) { anchor = child; } else { anchor = anchorElement; } return anchor; } protected abstract String getHelpID(); protected abstract Settings showRefactoringDialog(Project project, Editor editor, PsiClass parentClass, PsiExpression expr, PsiType type, PsiExpression[] occurrences, PsiElement anchorElement, PsiElement anchorElementIfAll, boolean isLocalVariable); private static PsiType getTypeByExpression(PsiExpression expr) { return GosuRefactoringUtil.getTypeByExpressionWithExpectedType(expr); } public PsiClass getParentClass(@NotNull PsiExpression initializerExpression) { PsiElement element = initializerExpression.getUserData(GosuElementToWorkOn.PARENT); if (element == null) { element = initializerExpression.getParent(); } PsiElement parent = element; while (parent != null) { if (parent instanceof PsiClass && !(parent instanceof PsiAnonymousClass && myIsConstant)) { return (PsiClass) parent; } parent = parent.getParent(); } return null; } public static PsiMethod getEnclosingConstructor(PsiClass parentClass, PsiElement element) { if (element == null) { return null; } final PsiMethod[] constructors = parentClass.getConstructors(); for (PsiMethod constructor : constructors) { if (PsiTreeUtil.isAncestor(constructor, element, false)) { if (PsiTreeUtil.getParentOfType(element, PsiClass.class) != parentClass) { return null; } return constructor; } } return null; } private static void addInitializationToSetUp(final PsiExpression initializer, final PsiField field, final PsiExpression[] occurrences, final boolean replaceAll, final PsiClass parentClass) throws IncorrectOperationException { final PsiMethod setupMethod = TestFrameworks.getInstance().findOrCreateSetUpMethod(parentClass); assert setupMethod != null; PsiElement anchor = null; if (PsiTreeUtil.isAncestor(setupMethod, initializer, true)) { anchor = replaceAll ? GosuRefactoringUtil.getAnchorElementForMultipleExpressions(occurrences, setupMethod) : PsiTreeUtil.getParentOfType(initializer, PsiStatement.class); } final PsiExpressionStatement expressionStatement = (PsiExpressionStatement) JavaPsiFacade.getInstance(parentClass.getProject()).getElementFactory() .createStatementFromText(field.getName() + "= expr;", null); PsiAssignmentExpression expr = (PsiAssignmentExpression) expressionStatement.getExpression(); final PsiExpression rExpression = expr.getRExpression(); LOG.assertTrue(rExpression != null); rExpression.replace(initializer); final PsiCodeBlock body = setupMethod.getBody(); assert body != null; body.addBefore(expressionStatement, anchor); } private static void addInitializationToConstructors(PsiExpression initializerExpression, PsiField field, PsiMethod enclosingConstructor, final PsiClass parentClass) { try { PsiClass aClass = field.getContainingClass(); PsiMethod[] constructors = aClass.getConstructors(); 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; } } } } PsiStatement assignment = createAssignment(field, initializerExpression, body.getLastChild(), parentClass); assignment = (PsiStatement) body.add(assignment); ChangeContextUtil.decodeContextInfo(assignment, field.getContainingClass(), GosuRefactoringUtil.createThisExpression(field.getManager(), null)); added = true; } if (!added && enclosingConstructor == null) { PsiMethod constructor = (PsiMethod) aClass.add(GosuPsiParseUtil.createConstructor(aClass.getName(), aClass.getManager(), null)); final PsiCodeBlock body = constructor.getBody(); PsiStatement assignment = createAssignment(field, initializerExpression, body.getLastChild(), parentClass); assignment = (PsiStatement) body.add(assignment); ChangeContextUtil.decodeContextInfo(assignment, field.getContainingClass(), GosuRefactoringUtil.createThisExpression(field.getManager(), null)); } } catch (IncorrectOperationException e) { LOG.error(e); } } private static PsiField createField(String fieldName, PsiType type, PsiExpression initializerExpr, boolean includeInitializer, final PsiClass parentClass, boolean isPrivate) { @NonNls StringBuilder pattern = new StringBuilder(); pattern.append("var "); pattern.append(fieldName); if (initializerExpr == null) { includeInitializer = false; } //only private members can have their type ommited if (!isPrivate || !includeInitializer) { pattern.append(": ").append(type != null ? type.getPresentableText() : "Object"); } if (includeInitializer) { pattern.append(" = ").append(initializerExpr.getText()); } final PsiElement psiElement = GosuPsiParseUtil.parseProgramm(pattern.toString(), PsiManager.getInstance(parentClass.getProject()), null); final GosuFieldImpl field = PsiTreeUtil.findChildOfType(psiElement, GosuFieldImpl.class); GeneratedMarkerVisitor.markGenerated(field); return field; } private static PsiStatement createAssignment(PsiField field, PsiExpression initializerExpr, PsiElement context, final PsiClass parentClass) { try { PsiManager psiManager = parentClass.getManager(); final PsiReferenceExpression fieldReference = RenameJavaVariableProcessor.createMemberReference(field, context); PsiAssignmentExpression assignmentExpression = GosuPsiParseUtil.createAssignmentStatement(psiManager.getProject(), field.getName(), initializerExpr); assignmentExpression = (PsiAssignmentExpression) CodeStyleManager.getInstance(psiManager.getProject()).reformat(assignmentExpression); assignmentExpression.getLExpression().replace(fieldReference); return (PsiStatement) CodeStyleManager.getInstance(field.getProject()).reformat(assignmentExpression); } catch (IncorrectOperationException e) { LOG.error(e); return null; } } protected Pass<GosuElementToWorkOn> getElementProcessor(final Project project, final Editor editor) { return new Pass<GosuElementToWorkOn>() { @Override public void pass(final GosuElementToWorkOn elementToWorkOn) { if (!isValidContext(elementToWorkOn)) { String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message("error.wrong.caret.position.local.or.expression.name")); CommonRefactoringUtil.showErrorHint(project, editor, message, GosuIntroduceFieldHandler.REFACTORING_NAME, getHelpID()); return; } final boolean hasRunTemplate = LookupManager.getActiveLookup(editor) == null; if (elementToWorkOn.getExpression() == null) { final PsiLocalVariable localVariable = elementToWorkOn.getLocalVariable(); final boolean result = invokeImpl(project, localVariable, editor) && hasRunTemplate; if (result) { editor.getSelectionModel().removeSelection(); } } else { if (invokeImpl(project, elementToWorkOn.getExpression(), editor) && hasRunTemplate) { editor.getSelectionModel().removeSelection(); } } } }; } private boolean isValidContext(final GosuElementToWorkOn elementToWorkOn) { if (elementToWorkOn == null) { return false; } final PsiLocalVariable localVariable = elementToWorkOn.getLocalVariable(); if (localVariable == null) { return true; } final PsiElement parent = localVariable.getParent(); if (parent instanceof GosuForEachStatementImpl || parent instanceof GosuUsingStatementImpl || parent instanceof GosuParameterListImpl) { return false; } if (parent instanceof IGosuPsiElement) { IGosuPsiElement gosuElement = (IGosuPsiElement) parent; if (gosuElement.getParsedElement() instanceof CatchClause) { return false; } } return true; } protected abstract String getRefactoringName(); public static class Settings { private final String myFieldName; private final PsiType myForcedType; private final boolean myReplaceAll; private final boolean myDeclareStatic; private final boolean myDeclareFinal; private final InitializationPlace myInitializerPlace; private final String myVisibility; private final boolean myDeleteLocalVariable; private final TargetDestination myTargetClass; private final boolean myAnnotateAsNonNls; private final boolean myIntroduceEnumConstant; private final PsiExpression mySelectedExpr; private PsiExpression[] myOccurrences; public PsiLocalVariable getLocalVariable() { return myLocalVariable; } public boolean isDeleteLocalVariable() { return myDeleteLocalVariable; } private final PsiLocalVariable myLocalVariable; public String getFieldName() { return myFieldName; } public boolean isDeclareStatic() { return myDeclareStatic; } public boolean isDeclareFinal() { return myDeclareFinal; } public InitializationPlace getInitializerPlace() { return myInitializerPlace; } public String getFieldVisibility() { return myVisibility; } @Nullable public PsiClass getDestinationClass() { return myTargetClass != null ? myTargetClass.getTargetClass() : null; } public PsiType getForcedType() { return myForcedType; } public boolean isReplaceAll() { return myReplaceAll; } public boolean isAnnotateAsNonNls() { return myAnnotateAsNonNls; } public boolean isIntroduceEnumConstant() { return myIntroduceEnumConstant; } public Settings(String fieldName, PsiExpression selectedExpr, PsiExpression[] occurrences, boolean replaceAll, boolean declareStatic, boolean declareFinal, InitializationPlace initializerPlace, String visibility, PsiLocalVariable localVariableToRemove, PsiType forcedType, boolean deleteLocalVariable, TargetDestination targetDestination, final boolean annotateAsNonNls, final boolean introduceEnumConstant) { myFieldName = fieldName; myOccurrences = occurrences; mySelectedExpr = selectedExpr; myReplaceAll = replaceAll; myDeclareStatic = declareStatic; myDeclareFinal = declareFinal; myInitializerPlace = initializerPlace; myVisibility = visibility; myLocalVariable = localVariableToRemove; myDeleteLocalVariable = deleteLocalVariable; myForcedType = forcedType; myTargetClass = targetDestination; myAnnotateAsNonNls = annotateAsNonNls; myIntroduceEnumConstant = introduceEnumConstant; } public Settings(String fieldName, PsiExpression selectedExpression, PsiExpression[] occurrences, boolean replaceAll, boolean declareStatic, boolean declareFinal, InitializationPlace initializerPlace, String visibility, PsiLocalVariable localVariableToRemove, PsiType forcedType, boolean deleteLocalVariable, PsiClass targetClass, final boolean annotateAsNonNls, final boolean introduceEnumConstant) { this(fieldName, selectedExpression, occurrences, replaceAll, declareStatic, declareFinal, initializerPlace, visibility, localVariableToRemove, forcedType, deleteLocalVariable, new TargetDestination(targetClass), annotateAsNonNls, introduceEnumConstant); } public PsiExpression getSelectedExpr() { return mySelectedExpr; } public PsiExpression[] getOccurrences() { return myOccurrences; } public void setOccurrences(PsiExpression[] occurrences) { myOccurrences = occurrences; } } public static class TargetDestination { private final String myQualifiedName; private final Project myProject; private PsiClass myParentClass; private PsiClass myTargetClass; public TargetDestination(String qualifiedName, PsiClass parentClass) { myQualifiedName = qualifiedName; myParentClass = parentClass; myProject = parentClass.getProject(); } public TargetDestination(@NotNull PsiClass targetClass) { myTargetClass = targetClass; myQualifiedName = targetClass.getQualifiedName(); myProject = targetClass.getProject(); } @Nullable public PsiClass getTargetClass() { if (myTargetClass != null) { return myTargetClass; } final String packageName = StringUtil.getPackageName(myQualifiedName); final String shortName = StringUtil.getShortName(myQualifiedName); if (Comparing.strEqual(myParentClass.getQualifiedName(), packageName)) { myTargetClass = (PsiClass) myParentClass.add(JavaPsiFacade.getElementFactory(myProject).createClass(shortName)); return myTargetClass; } PsiPackage psiPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName); final PsiDirectory psiDirectory; if (psiPackage != null) { final PsiDirectory[] directories = psiPackage.getDirectories(GlobalSearchScope.allScope(myProject)); psiDirectory = directories.length > 1 ? DirectoryChooserUtil.chooseDirectory(directories, null, myProject, new HashMap<PsiDirectory, String>()) : directories[0]; } else { psiDirectory = PackageUtil.findOrCreateDirectoryForPackage(myProject, packageName, myParentClass.getContainingFile().getContainingDirectory(), false); } myTargetClass = psiDirectory != null ? JavaDirectoryService.getInstance().createClass(psiDirectory, shortName) : null; return myTargetClass; } } public static class ConvertToFieldRunnable implements Runnable { private PsiExpression mySelectedExpr; private final Settings mySettings; private final PsiElement myAnchorElement; private final Project myProject; private final String myFieldName; private final PsiType myType; private final PsiExpression[] myOccurrences; private final boolean myReplaceAll; private final OccurrenceManager myOccurrenceManager; private final PsiElement myAnchorStatementIfAll; private final PsiElement myAnchorElementIfOne; private final Boolean myOutOfCodeBlockExtraction; private final PsiElement myElement; private boolean myDeleteSelf; private final Editor myEditor; private final PsiClass myParentClass; private PsiField myField; public ConvertToFieldRunnable(PsiExpression selectedExpr, Settings settings, PsiType type, PsiExpression[] occurrences, OccurrenceManager occurrenceManager, PsiElement anchorStatementIfAll, PsiElement anchorElementIfOne, Editor editor, PsiClass parentClass) { mySelectedExpr = selectedExpr; mySettings = settings; myAnchorElement = settings.isReplaceAll() ? anchorStatementIfAll : anchorElementIfOne; myProject = selectedExpr.getProject(); myFieldName = settings.getFieldName(); myType = type; myOccurrences = occurrences; myReplaceAll = settings.isReplaceAll(); myOccurrenceManager = occurrenceManager; myAnchorStatementIfAll = anchorStatementIfAll; myAnchorElementIfOne = anchorElementIfOne; myOutOfCodeBlockExtraction = selectedExpr.getUserData(GosuElementToWorkOn.OUT_OF_CODE_BLOCK); myDeleteSelf = myOutOfCodeBlockExtraction != null; myElement = getPhysicalElement(selectedExpr); if (myElement.getParent() instanceof PsiExpressionStatement && getNormalizedAnchor(myAnchorElement).equals(myAnchorElement) && selectedExpr.isPhysical()) { PsiStatement statement = (PsiStatement) myElement.getParent(); if (statement.getParent() instanceof PsiCodeBlock) { myDeleteSelf = true; } } myEditor = editor; myParentClass = parentClass; } public void run() { try { InitializationPlace initializerPlace = mySettings.getInitializerPlace(); final PsiLocalVariable localVariable = mySettings.getLocalVariable(); final boolean deleteLocalVariable = mySettings.isDeleteLocalVariable(); @Nullable PsiExpression initializer = null; if (localVariable != null) { initializer = localVariable.getInitializer(); } else if (!(GosuRefactoringUtil.isPsiReferenceExpression(mySelectedExpr) && ((PsiReference) mySelectedExpr).resolve() == null)) { initializer = mySelectedExpr; } initializer = GosuIntroduceVariableBase.replaceExplicitWithDiamondWhenApplicable(initializer, myType); final PsiMethod enclosingConstructor = getEnclosingConstructor(myParentClass, myAnchorElement); final PsiClass destClass = mySettings.getDestinationClass() == null ? myParentClass : mySettings.getDestinationClass(); if (!CommonRefactoringUtil.checkReadOnlyStatus(myProject, destClass.getContainingFile())) { return; } if (initializer != null) { ChangeContextUtil.encodeContextInfo(initializer, true); } myField = mySettings.isIntroduceEnumConstant() ? EnumConstantsUtil.createEnumConstant(destClass, myFieldName, initializer) : createField(myFieldName, myType, initializer, initializerPlace == InitializationPlace.IN_FIELD_DECLARATION && initializer != null, myParentClass, PsiModifier.PRIVATE.equalsIgnoreCase(mySettings.getFieldVisibility())); setModifiers(myField, mySettings); myField = appendField(initializer, initializerPlace, destClass, myParentClass, myAnchorElement, myField); if (!mySettings.isIntroduceEnumConstant()) { VisibilityUtil.fixVisibility(myOccurrences, myField, mySettings.getFieldVisibility()); } PsiStatement assignStatement = null; PsiElement anchorElementHere = null; if (initializerPlace == InitializationPlace.IN_CURRENT_METHOD && initializer != null || initializerPlace == InitializationPlace.IN_CONSTRUCTOR && enclosingConstructor != null && initializer != null) { if (myReplaceAll) { if (enclosingConstructor != null) { final PsiElement anchorInConstructor = GosuRefactoringUtil.getAnchorElementForMultipleExpressions(mySettings.myOccurrences, enclosingConstructor); anchorElementHere = anchorInConstructor != null ? anchorInConstructor : myAnchorStatementIfAll; } else { anchorElementHere = myAnchorStatementIfAll; } } else { anchorElementHere = myAnchorElementIfOne; } assignStatement = createAssignment(myField, initializer, anchorElementHere, myParentClass); if (!GosuIntroduceVariableBase.isLoopOrIf(anchorElementHere.getParent())) { final PsiElement added = anchorElementHere.getParent().addBefore(assignStatement, getNormalizedAnchor(anchorElementHere)); GeneratedMarkerVisitor.markGenerated(added); } } if (initializerPlace == InitializationPlace.IN_CONSTRUCTOR && initializer != null) { addInitializationToConstructors(initializer, myField, enclosingConstructor, myParentClass); } if (initializerPlace == InitializationPlace.IN_SETUP_METHOD && initializer != null) { addInitializationToSetUp(initializer, myField, myOccurrences, myReplaceAll, myParentClass); } if (mySelectedExpr.getParent() instanceof PsiParenthesizedExpression) { mySelectedExpr = (PsiExpression) mySelectedExpr.getParent(); } if (myOutOfCodeBlockExtraction != null) { final int endOffset = mySelectedExpr.getUserData(GosuElementToWorkOn.TEXT_RANGE).getEndOffset(); PsiElement endElement = myElement.getContainingFile().findElementAt(endOffset); while (true) { final PsiElement parent = endElement.getParent(); if (parent instanceof PsiClass) { break; } endElement = parent; } myElement.getParent().deleteChildRange(myElement, PsiTreeUtil.skipSiblingsBackward(endElement, PsiWhiteSpace.class)); } else if (myDeleteSelf) { myElement.getParent().delete(); } if (myReplaceAll) { List<PsiElement> array = new ArrayList<>(); for (PsiExpression occurrence : myOccurrences) { if (occurrence instanceof PsiExpression) { occurrence = GosuRefactoringUtil.outermostParenthesizedExpression(occurrence); } if (myDeleteSelf && occurrence.equals(mySelectedExpr)) { continue; } final PsiElement replaced = GosuRefactoringUtil.replaceOccurenceWithFieldRef(occurrence, myField, destClass); if (replaced != null) { array.add(replaced); } } if (myEditor != null) { if (!ApplicationManager.getApplication().isUnitTestMode()) { PsiElement[] exprsToHighlight = PsiUtilBase.toPsiElementArray(array); HighlightManager highlightManager = HighlightManager.getInstance(myProject); highlightManager.addOccurrenceHighlights(myEditor, exprsToHighlight, highlightAttributes(), true, null); WindowManager .getInstance().getStatusBar(myProject).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); } } } else { if (!myDeleteSelf) { mySelectedExpr = GosuRefactoringUtil.outermostParenthesizedExpression(mySelectedExpr); GosuRefactoringUtil.replaceOccurenceWithFieldRef(mySelectedExpr, myField, destClass); } } if (anchorElementHere != null && GosuIntroduceVariableBase.isLoopOrIf(anchorElementHere.getParent())) { GosuIntroduceVariableBase.putStatementInLoopBody(assignStatement, anchorElementHere.getParent(), anchorElementHere); } if (localVariable != null) { if (deleteLocalVariable) { localVariable.normalizeDeclaration(); localVariable.delete(); } } if (initializer != null) { ChangeContextUtil.clearContextInfo(initializer); } } catch (IncorrectOperationException e) { LOG.error(e); } } static PsiField appendField(final PsiExpression initializer, InitializationPlace initializerPlace, final PsiClass destClass, final PsiClass parentClass, final PsiElement anchorElement, final PsiField psiField) { PsiElement finalAnchorElement = null; if (destClass == parentClass) { for (finalAnchorElement = anchorElement; finalAnchorElement != null && finalAnchorElement.getParent() != destClass; finalAnchorElement = finalAnchorElement.getParent()) { } } PsiMember anchorMember = finalAnchorElement instanceof PsiMember ? (PsiMember) finalAnchorElement : null; if ((anchorMember instanceof PsiField) && anchorMember.hasModifierProperty(PsiModifier.STATIC) == psiField.hasModifierProperty(PsiModifier.STATIC)) { return (PsiField) destClass.addBefore(psiField, anchorMember); } else if (anchorMember instanceof PsiClassInitializer) { PsiField field = (PsiField) destClass.addBefore(psiField, anchorMember); destClass.addBefore(CodeEditUtil.createLineFeed(field.getManager()), anchorMember); return field; } else { final PsiField forwardReference = initializerPlace == InitializationPlace.IN_FIELD_DECLARATION ? checkForwardRefs(initializer, parentClass) : null; if (forwardReference != null) { return forwardReference.getParent() == destClass ? (PsiField) destClass.addAfter(psiField, forwardReference) : (PsiField) forwardReference.getParent().addAfter(psiField, forwardReference); } else { return (PsiField) destClass.add(psiField); } } } @Nullable private static PsiField checkForwardRefs(@Nullable PsiExpression initializer, final PsiClass parentClass) { if (initializer == null) { return null; } final PsiField[] refConstantFields = new PsiField[1]; initializer.accept(new JavaRecursiveElementWalkingVisitor() { @Override public void visitReferenceExpression(PsiReferenceExpression expression) { super.visitReferenceExpression(expression); final PsiElement resolve = expression.resolve(); if (resolve instanceof PsiField && ((PsiField) resolve).hasModifierProperty(PsiModifier.FINAL) && PsiTreeUtil.isAncestor(parentClass, resolve, false) && ((PsiField) resolve).hasInitializer()) { if (refConstantFields[0] == null || refConstantFields[0].getTextOffset() < resolve.getTextOffset()) { refConstantFields[0] = (PsiField) resolve; } } } }); return refConstantFields[0]; } public PsiField getField() { return myField; } } }