| /* |
| * Copyright 2000-2014 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 org.jetbrains.plugins.groovy.refactoring.move; |
| |
| import com.intellij.lang.ASTNode; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Comparing; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.source.resolve.JavaResolveUtil; |
| import com.intellij.psi.javadoc.PsiDocComment; |
| import com.intellij.psi.tree.IElementType; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.move.moveMembers.MoveMemberHandler; |
| import com.intellij.refactoring.move.moveMembers.MoveMembersOptions; |
| import com.intellij.refactoring.move.moveMembers.MoveMembersProcessor; |
| import com.intellij.refactoring.util.*; |
| import com.intellij.util.ArrayUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.VisibilityUtil; |
| import com.intellij.util.containers.MultiMap; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFile; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrEnumTypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstantList; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMember; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.api.toplevel.imports.GrImportStatement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement; |
| import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; |
| import org.jetbrains.plugins.groovy.refactoring.GroovyChangeContextUtil; |
| |
| import java.util.*; |
| |
| /** |
| * @author Maxim.Medvedev |
| */ |
| public class MoveGroovyMemberHandler implements MoveMemberHandler { |
| @Override |
| public boolean changeExternalUsage(@NotNull MoveMembersOptions options, @NotNull MoveMembersProcessor.MoveMembersUsageInfo usage) { |
| final PsiElement element = usage.getElement(); |
| if (element == null || !element.isValid()) return true; |
| |
| if (usage.reference instanceof GrReferenceExpression) { |
| GrReferenceExpression refExpr = (GrReferenceExpression)usage.reference; |
| GrExpression qualifier = refExpr.getQualifierExpression(); |
| if (qualifier != null) { |
| if (usage.qualifierClass != null) { |
| changeQualifier(refExpr, usage.qualifierClass, usage.member); |
| } |
| else { |
| refExpr.setQualifier(null); |
| } |
| } |
| else { // no qualifier |
| if (usage.qualifierClass != null) { |
| changeQualifier(refExpr, usage.qualifierClass, usage.member); |
| } |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| @NotNull |
| public PsiMember doMove(@NotNull MoveMembersOptions options, @NotNull PsiMember member, PsiElement anchor, @NotNull PsiClass targetClass) { |
| GroovyChangeContextUtil.encodeContextInfo(member); |
| |
| final PsiDocComment docComment; |
| if (member instanceof PsiDocCommentOwner) { |
| docComment = ((PsiDocCommentOwner)member).getDocComment(); |
| } |
| else { |
| docComment = null; |
| } |
| |
| PsiMember moved; |
| if (options.makeEnumConstant() && |
| member instanceof GrVariable && |
| EnumConstantsUtil.isSuitableForEnumConstant(((PsiVariable)member).getType(), targetClass)) { |
| final GrEnumConstant prototype = createEnumConstant(member.getName(), ((GrVariable)member).getInitializerGroovy(), member.getProject()); |
| moved = (PsiMember)addEnumConstant(targetClass, prototype, anchor); |
| member.delete(); |
| } |
| else if (member instanceof GrEnumConstant) { |
| moved = (PsiMember)addEnumConstant(targetClass, (GrEnumConstant)member, null); |
| } |
| else if (member instanceof GrField) { |
| if (anchor != null) anchor = anchor.getParent(); |
| |
| final GrVariableDeclaration parent = (GrVariableDeclaration)member.getParent(); |
| GrVariableDeclaration movedDeclaration = (GrVariableDeclaration)targetClass.addAfter(parent, anchor); |
| |
| int number = ArrayUtil.find(parent.getMembers(), member); |
| final GrMember[] members = movedDeclaration.getMembers(); |
| for (int i = 0; i < number; i++) { |
| members[i].delete(); |
| } |
| for (int i = number + 1; i < members.length; i++) { |
| members[i].delete(); |
| } |
| |
| if (member.getContainingClass().isInterface() && !targetClass.isInterface()) { |
| //might need to make modifiers explicit, see IDEADEV-11416 |
| final PsiModifierList list = movedDeclaration.getModifierList(); |
| VisibilityUtil.setVisibility(list, VisibilityUtil.getVisibilityModifier(member.getModifierList())); |
| list.setModifierProperty(PsiModifier.STATIC, member.hasModifierProperty(PsiModifier.STATIC)); |
| list.setModifierProperty(PsiModifier.FINAL, member.hasModifierProperty(PsiModifier.FINAL)); |
| } |
| |
| moved = movedDeclaration.getMembers()[0]; |
| } |
| else if (member instanceof GrMethod) { |
| moved = (PsiMember)targetClass.addAfter(member, anchor); |
| if (member.getContainingClass().isInterface() && !targetClass.isInterface()) { |
| //might need to make modifiers explicit, see IDEADEV-11416 |
| final PsiModifierList list = moved.getModifierList(); |
| assert list != null; |
| list.setModifierProperty(PsiModifier.STATIC, member.hasModifierProperty(PsiModifier.STATIC)); |
| list.setModifierProperty(PsiModifier.FINAL, member.hasModifierProperty(PsiModifier.FINAL)); |
| VisibilityUtil.setVisibility(list, VisibilityUtil.getVisibilityModifier(member.getModifierList())); |
| } |
| |
| } |
| else { |
| moved = (PsiMember)targetClass.addAfter(member, anchor); |
| } |
| |
| if (docComment != null) { |
| PsiElement insertedDocComment = targetClass.addBefore(docComment, moved); |
| PsiElement prevSibling = insertedDocComment.getPrevSibling(); |
| addLineFeedIfNeeded(prevSibling); |
| docComment.delete(); |
| } |
| member.delete(); |
| return moved; |
| } |
| |
| private static void addLineFeedIfNeeded(PsiElement prevSibling) { |
| if (prevSibling == null) return; |
| ASTNode node = prevSibling.getNode(); |
| IElementType type = node.getElementType(); |
| |
| if (type == GroovyTokenTypes.mNLS) { |
| String text = prevSibling.getText(); |
| int lfCount = StringUtil.countChars(text, '\n'); |
| if (lfCount < 2) { |
| ASTNode parent = node.getTreeParent(); |
| parent.addLeaf(GroovyTokenTypes.mNLS, text + "\n ", node); |
| parent.removeChild(node); |
| } |
| } |
| else { |
| node.getTreeParent().addLeaf(GroovyTokenTypes.mNLS, "\n\n ", node.getTreeNext()); |
| } |
| } |
| |
| @Override |
| public void decodeContextInfo(@NotNull PsiElement scope) { |
| GroovyChangeContextUtil.decodeContextInfo(scope, null, null); |
| } |
| |
| private static void changeQualifier(GrReferenceExpression refExpr, PsiClass aClass, PsiMember member) throws IncorrectOperationException { |
| if (hasOnDemandStaticImport(refExpr, aClass)) { |
| refExpr.setQualifier(null); |
| } |
| else if (!hasStaticImport(refExpr, member)) { |
| GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(refExpr.getProject()); |
| |
| refExpr.setQualifier(factory.createReferenceExpressionFromText(aClass.getName())); |
| ((GrReferenceExpression)refExpr.getQualifierExpression()).bindToElement(aClass); |
| } |
| } |
| |
| private static boolean hasStaticImport(GrReferenceExpression refExpr, PsiMember member) { |
| if (!(refExpr.getContainingFile() instanceof GroovyFile)) return false; |
| |
| final GrImportStatement[] imports = ((GroovyFile)refExpr.getContainingFile()).getImportStatements(); |
| for (GrImportStatement stmt : imports) { |
| if (!stmt.isOnDemand() && stmt.resolveTargetClass() == member.getContainingClass() && |
| Comparing.strEqual(stmt.getImportReference().getReferenceName(), member.getName())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean hasOnDemandStaticImport(final PsiElement element, final PsiClass aClass) { |
| if (element.getContainingFile() instanceof GroovyFile) { |
| final GrImportStatement[] importStatements = ((GroovyFile)element.getContainingFile()).getImportStatements(); |
| for (GrImportStatement stmt : importStatements) { |
| final GrCodeReferenceElement ref = stmt.getImportReference(); |
| if (ref != null && stmt.isStatic() && stmt.isOnDemand() && ref.resolve() == aClass) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| @Nullable |
| public PsiElement getAnchor(@NotNull final PsiMember member, @NotNull final PsiClass targetClass, Set<PsiMember> membersToMove) { |
| if (member instanceof GrField && member.hasModifierProperty(PsiModifier.STATIC)) { |
| final List<PsiField> referencedFields = new ArrayList<PsiField>(); |
| final GrExpression psiExpression = ((GrField)member).getInitializerGroovy(); |
| if (psiExpression != null) { |
| psiExpression.accept(new GroovyRecursiveElementVisitor() { |
| @Override |
| public void visitReferenceExpression(final GrReferenceExpression expression) { |
| super.visitReferenceExpression(expression); |
| final PsiElement psiElement = expression.resolve(); |
| if (psiElement instanceof GrField) { |
| final GrField grField = (GrField)psiElement; |
| if (grField.getContainingClass() == targetClass && !referencedFields.contains(grField)) { |
| referencedFields.add(grField); |
| } |
| } |
| } |
| }); |
| } |
| if (!referencedFields.isEmpty()) { |
| Collections.sort(referencedFields, new Comparator<PsiField>() { |
| @Override |
| public int compare(final PsiField o1, final PsiField o2) { |
| return -PsiUtilCore.compareElementsByPosition(o1, o2); |
| } |
| }); |
| return referencedFields.get(0); |
| } |
| } |
| return null; |
| } |
| |
| private static GrEnumConstant createEnumConstant(String constantName, GrExpression initializerExpr, Project project) |
| throws IncorrectOperationException { |
| final GroovyPsiElementFactory elementFactory = GroovyPsiElementFactory.getInstance(project); |
| final String enumConstantText = initializerExpr != null ? constantName + "(" + initializerExpr.getText() + ")" : constantName; |
| return elementFactory.createEnumConstantFromText(enumConstantText); |
| } |
| |
| private static PsiElement addEnumConstant(PsiClass targetClass, GrEnumConstant constant, @Nullable PsiElement anchor) { |
| if (targetClass instanceof GrEnumTypeDefinition) { |
| final GrEnumTypeDefinition enumeration = (GrEnumTypeDefinition)targetClass; |
| final GrEnumConstantList constantList = enumeration.getEnumConstantList(); |
| if (constantList != null) { |
| ASTNode node = constantList.getNode(); |
| node.addLeaf(GroovyTokenTypes.mCOMMA, ",", node.getFirstChildNode()); |
| return constantList.addBefore(constant, constantList.getFirstChild()); |
| } |
| else { |
| final PsiElement parent = constant.getParent(); |
| assert parent instanceof GrEnumConstantList; |
| final GrEnumConstantList constListCopy = ((GrEnumConstantList)targetClass.add(parent)); |
| return constListCopy.getEnumConstants()[0]; |
| } |
| } |
| return (anchor != null ? targetClass.addAfter(constant, anchor) : targetClass.add(constant)); |
| } |
| |
| @Override |
| public MoveMembersProcessor.MoveMembersUsageInfo getUsage(@NotNull PsiMember member, |
| @NotNull PsiReference psiReference, |
| @NotNull Set<PsiMember> membersToMove, |
| @NotNull PsiClass targetClass) { |
| PsiElement ref = psiReference.getElement(); |
| if (ref instanceof GrReferenceExpression) { |
| GrReferenceExpression refExpr = (GrReferenceExpression)ref; |
| GrExpression qualifier = refExpr.getQualifier(); |
| if (RefactoringHierarchyUtil.willBeInTargetClass(refExpr, membersToMove, targetClass, true)) { |
| // both member and the reference to it will be in target class |
| if (!RefactoringUtil.isInMovedElement(refExpr, membersToMove)) { |
| if (qualifier != null) { |
| return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, null, qualifier, psiReference); // remove qualifier |
| } |
| } |
| else if (qualifier instanceof GrReferenceExpression && ((GrReferenceExpression)qualifier).isReferenceTo(member.getContainingClass())) { |
| return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, null, qualifier, psiReference); // change qualifier |
| } |
| } |
| else { |
| // member in target class, the reference will be outside target class |
| if (qualifier == null) { |
| return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, refExpr, psiReference); // add qualifier |
| } |
| else { |
| return new MoveMembersProcessor.MoveMembersUsageInfo(member, refExpr, targetClass, qualifier, psiReference); // change qualifier |
| } |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void checkConflictsOnUsage(@NotNull MoveMembersProcessor.MoveMembersUsageInfo usageInfo, |
| @Nullable String newVisibility, |
| @Nullable PsiModifierList modifierListCopy, |
| @NotNull PsiClass targetClass, |
| @NotNull Set<PsiMember> membersToMove, |
| @NotNull MultiMap<PsiElement, String> conflicts) { |
| final PsiElement element = usageInfo.getElement(); |
| if (element == null) return; |
| |
| final PsiMember member = usageInfo.member; |
| if (element instanceof GrReferenceExpression) { |
| GrExpression qualifier = ((GrReferenceExpression)element).getQualifier(); |
| PsiClass accessObjectClass = null; |
| if (qualifier != null) { |
| accessObjectClass = (PsiClass)PsiUtil.getAccessObjectClass(qualifier).getElement(); |
| } |
| |
| if (!JavaResolveUtil.isAccessible(member, targetClass, modifierListCopy, element, accessObjectClass, null)) { |
| String visibility = newVisibility != null ? newVisibility : VisibilityUtil.getVisibilityStringToDisplay(member); |
| String message = RefactoringBundle.message("0.with.1.visibility.is.not.accessible.from.2", |
| RefactoringUIUtil.getDescription(member, false), |
| visibility, |
| RefactoringUIUtil.getDescription(ConflictsUtil.getContainer(element), true)); |
| conflicts.putValue(member, CommonRefactoringUtil.capitalize(message)); |
| } |
| } |
| } |
| |
| @Override |
| public void checkConflictsOnMember(@NotNull PsiMember member, |
| @Nullable String newVisibility, |
| @Nullable PsiModifierList modifierListCopy, |
| @NotNull PsiClass targetClass, |
| @NotNull Set<PsiMember> membersToMove, |
| @NotNull MultiMap<PsiElement, String> conflicts) { |
| } |
| } |