| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.refactoring.introduceField; |
| |
| import com.intellij.codeInsight.ChangeContextUtil; |
| import com.intellij.codeInsight.CodeInsightUtil; |
| import com.intellij.codeInsight.TestFrameworks; |
| import com.intellij.codeInsight.daemon.impl.quickfix.AnonymousTargetClassPreselectionUtil; |
| 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.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| 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.introduce.inplace.AbstractInplaceIntroducer; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import com.intellij.refactoring.util.EnumConstantsUtil; |
| import com.intellij.refactoring.util.RefactoringUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.VisibilityUtil; |
| import com.intellij.psi.util.FileTypeUtils; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import static com.intellij.refactoring.introduceField.BaseExpressionToFieldHandler.InitializationPlace.IN_CONSTRUCTOR; |
| import static com.intellij.refactoring.introduceField.BaseExpressionToFieldHandler.InitializationPlace.IN_FIELD_DECLARATION; |
| |
| public abstract class LocalToFieldHandler { |
| 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; |
| |
| public LocalToFieldHandler(Project project, boolean isConstant) { |
| myProject = project; |
| myIsConstant = isConstant; |
| } |
| |
| protected abstract BaseExpressionToFieldHandler.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(); |
| final List<PsiClass> classes = new ArrayList<PsiClass>(); |
| while (parent != null && parent.getContainingFile() != null) { |
| if (parent instanceof PsiClass && !(myIsConstant && parent instanceof PsiAnonymousClass)) { |
| classes.add((PsiClass)parent); |
| } |
| if (parent instanceof PsiFile && FileTypeUtils.isInServerPageFile(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; |
| final AbstractInplaceIntroducer activeIntroducer = AbstractInplaceIntroducer.getActiveIntroducer(editor); |
| final boolean shouldSuggestDialog = activeIntroducer instanceof InplaceIntroduceConstantPopup && |
| activeIntroducer.startsOnTheSameElement(null, local); |
| if (classes.size() == 1 || ApplicationManager.getApplication().isUnitTestMode() || shouldSuggestDialog) { |
| if (convertLocalToField(local, classes.get(getChosenClassIndex(classes)), editor, tempIsStatic)) return false; |
| } else { |
| final boolean isStatic = tempIsStatic; |
| final PsiClass firstClass = classes.get(0); |
| final PsiClass preselection = AnonymousTargetClassPreselectionUtil.getPreselection(classes, firstClass); |
| 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) { |
| AnonymousTargetClassPreselectionUtil.rememberSelection(aClass, aClass); |
| convertLocalToField(local, aClass, editor, isStatic); |
| return false; |
| } |
| }, preselection).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 = CodeInsightUtil.findReferenceExpressions(RefactoringUtil.getVariableScope(local), local); |
| if (editor != null) { |
| RefactoringUtil.highlightAllOccurrences(myProject, occurences, editor); |
| } |
| |
| final BaseExpressionToFieldHandler.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("private int "); |
| pattern.append(fieldName); |
| if (local.getInitializer() == null) { |
| includeInitializer = false; |
| } |
| if (includeInitializer) { |
| pattern.append("=0"); |
| } |
| pattern.append(";"); |
| final Project project = local.getProject(); |
| PsiElementFactory factory = JavaPsiFacade.getInstance(project).getElementFactory(); |
| try { |
| PsiField field = factory.createFieldFromText(pattern.toString(), null); |
| |
| field.getTypeElement().replace(factory.createTypeElement(forcedType)); |
| if (includeInitializer) { |
| PsiExpression initializer = |
| RefactoringUtil.convertInitializerToNormalExpression(local.getInitializer(), forcedType); |
| field.getInitializer().replace(initializer); |
| } |
| |
| for (PsiAnnotation annotation : local.getModifierList().getAnnotations()) { |
| field.getModifierList().add(annotation.copy()); |
| } |
| return field; |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| return null; |
| } |
| } |
| |
| private static PsiExpressionStatement createAssignment(PsiLocalVariable local, String fieldname, PsiElementFactory factory) { |
| try { |
| String pattern = fieldname + "=0;"; |
| PsiExpressionStatement statement = (PsiExpressionStatement)factory.createStatementFromText(pattern, null); |
| statement = (PsiExpressionStatement)CodeStyleManager.getInstance(local.getProject()).reformat(statement); |
| |
| PsiAssignmentExpression expr = (PsiAssignmentExpression)statement.getExpression(); |
| final PsiExpression initializer = RefactoringUtil.convertInitializerToNormalExpression(local.getInitializer(), local.getType()); |
| expr.getRExpression().replace(initializer); |
| |
| return statement; |
| } |
| 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(factory.createConstructor()); |
| assignment = (PsiStatement)constructor.getBody().add(assignment); |
| } |
| } |
| |
| if (enclosingConstructor == null) local.delete(); |
| return assignment; |
| } |
| |
| static class IntroduceFieldRunnable implements Runnable { |
| 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 BaseExpressionToFieldHandler.Settings mySettings; |
| private final BaseExpressionToFieldHandler.InitializationPlace myInitializerPlace; |
| private final PsiExpression[] myOccurences; |
| private PsiField myField; |
| |
| public IntroduceFieldRunnable(boolean rebindNeeded, |
| PsiLocalVariable local, |
| PsiClass aClass, |
| BaseExpressionToFieldHandler.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 { |
| ChangeContextUtil.encodeContextInfo(myDestinationClass, true); |
| 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 PsiMethod enclosingConstructor = BaseExpressionToFieldHandler.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); |
| BaseExpressionToFieldHandler.setModifiers(myField, mySettings); |
| if (!mySettings.isIntroduceEnumConstant()) { |
| VisibilityUtil.fixVisibility(myOccurences, myField, mySettings.getFieldVisibility()); |
| } |
| |
| myLocal.normalizeDeclaration(); |
| PsiElement declarationStatement = myLocal.getParent(); |
| final BaseExpressionToFieldHandler.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(); |
| break; |
| |
| case IN_CURRENT_METHOD: |
| PsiExpressionStatement statement = createAssignment(myLocal, myFieldName, factory); |
| if (declarationStatement instanceof PsiDeclarationStatement) { |
| declarationStatement.replace(statement); |
| } else { |
| myLocal.replace(statement.getExpression()); |
| } |
| break; |
| |
| case IN_CONSTRUCTOR: |
| addInitializationToConstructors(myLocal, myField, enclosingConstructor, factory); |
| break; |
| case IN_SETUP_METHOD: |
| addInitializationToSetUp(myLocal, myField, factory); |
| } |
| |
| if (enclosingConstructor != null && myInitializerPlace == IN_CONSTRUCTOR) { |
| PsiStatement statement = createAssignment(myLocal, myFieldName, factory); |
| declarationStatement.replace(statement); |
| } |
| |
| if (rebindNeeded2) { |
| for (final PsiReference reference : refs) { |
| if (reference != null) { |
| //expr = RefactoringUtil.outermostParenthesizedExpression(expr); |
| RefactoringUtil.replaceOccurenceWithFieldRef((PsiExpression)reference, myField, myDestinationClass); |
| //replaceOccurenceWithFieldRef((PsiExpression)reference, field, aaClass); |
| } |
| } |
| //RefactoringUtil.renameVariableReferences(local, pPrefix + fieldName, GlobalSearchScope.projectScope(myProject)); |
| ChangeContextUtil.decodeContextInfo(myDestinationClass, myDestinationClass, null); |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| public PsiField getField() { |
| return myField; |
| } |
| } |
| } |