blob: 8dabab01ad02a0adabfee5371246b35ba3be1553 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2011 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.corext.refactoring.code;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IFile;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.ParameterizedType;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeParameter;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
import org.eclipse.jdt.core.refactoring.descriptors.IntroduceIndirectionDescriptor;
import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.core.refactoring.descriptors.RefactoringSignatureDescriptorFactory;
import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.ASTNodes;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringDescriptorUtil;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringAvailabilityTester;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
import org.eclipse.jdt.internal.corext.refactoring.rename.RippleMethodFinder2;
import org.eclipse.jdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.jdt.internal.corext.refactoring.structure.MemberVisibilityAdjustor;
import org.eclipse.jdt.internal.corext.refactoring.structure.MemberVisibilityAdjustor.IncomingMemberVisibilityAdjustment;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.MethodOverrideTester;
import org.eclipse.jdt.ui.CodeGeneration;
import org.eclipse.jdt.ui.JavaElementLabels;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
/**
*
* This refactoring creates a wrapper around a certain method and redirects callers of the original
* method to the newly created method (called "intermediary method").
*
* If invoked on a method call, the user may select whether to only update the selected call or
* all other calls as well. If invoked on a method declaration, the user may select whether to
* update calls at all. An intermediary method will be created in both cases.
*
* Creating indirections is possible for both source and binary methods. Select an invocation of the method or
* the declaring method itself, for example in the outline view.
*
* Note that in case of methods inside generic types, the parameters of the declaring type of the selected method
* will be added to the method definition, rendering it generic as well.
*
* If any of the calls cannot see the intermediary method due to visibility problems with enclosing types
* of the intermediary method, visibility will be adjusted. If the intermediary method is not able to
* see the target method, this refactoring will try to adjust the visibility of the target method and
* enclosing types as well. However, the latter is only possible if the target method is from source.
*
* @since 3.2
*
*/
public class IntroduceIndirectionRefactoring extends Refactoring {
/**
* The compilation unit in which the user invoked this refactoring (if any)
*/
private ICompilationUnit fSelectionCompilationUnit;
/**
* The class file (with source) in which the user invoked this refactoring (if any)
*/
private IClassFile fSelectionClassFile;
/**
* The start of the user selection inside the selected
* compilation unit (if any)
*/
private int fSelectionStart;
/**
* The length of the user selection inside the selected
* compilation unit (if any)
*/
private int fSelectionLength;
/**
* The selected MethodInvocation (if any). This field is used
* to update this particular invocation in non-reference mode.
*/
private MethodInvocation fSelectionMethodInvocation;
// Intermediary information:
/**
* The class in which to place the intermediary method
*/
private IType fIntermediaryClass;
/**
* The binding of the intermediary class
*/
private ITypeBinding fIntermediaryClassBinding;
/**
* The name of the intermediary method
*/
private String fIntermediaryMethodName;
/**
* The type for the additional parameter for the intermediary. This
* type is determined from all known references.
*/
private ITypeBinding fIntermediaryFirstParameterType;
// Target information:
/**
* The originally selected target method (i.e., the one to be encapsulated)
*/
private IMethod fTargetMethod;
/**
* The binding of the originally selected target method
*/
private IMethodBinding fTargetMethodBinding;
// Other information:
/**
* If true, all references to the target method are replaced with calls to
* the intermediary.
*/
private boolean fUpdateReferences;
/**
* CompilationUnitRewrites for all affected cus
*/
private Map<ICompilationUnit, CompilationUnitRewrite> fRewrites;
/**
* Text change manager (actually a CompilationUnitChange manager) which
* manages all changes.
*/
private TextChangeManager fTextChangeManager;
// Visibility
/**
* The visibility adjustor
*/
private MemberVisibilityAdjustor fAdjustor;
/**
* Visibility adjustments for the intermediary
*/
private Map<IMember, IncomingMemberVisibilityAdjustment> fIntermediaryAdjustments;
private class NoOverrideProgressMonitor extends SubProgressMonitor {
public NoOverrideProgressMonitor(IProgressMonitor monitor, int ticks) {
super(monitor, ticks, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL);
}
@Override
public void setTaskName(String name) {
// do nothing
}
}
// ********* CONSTRUCTORS AND CLASS CREATION ************
public IntroduceIndirectionRefactoring(ICompilationUnit unit, int offset, int length) {
fSelectionCompilationUnit= unit;
initialize(offset, length);
}
public IntroduceIndirectionRefactoring(IClassFile file, int offset, int length) {
fSelectionClassFile= file;
initialize(offset, length);
}
public IntroduceIndirectionRefactoring(IMethod method) {
fTargetMethod= method;
initialize(0, 0);
}
public IntroduceIndirectionRefactoring(JavaRefactoringArguments arguments, RefactoringStatus status) {
this((ICompilationUnit) null, 0, 0);
RefactoringStatus initializeStatus= initialize(arguments);
status.merge(initializeStatus);
}
private void initialize(int offset, int length) {
fSelectionStart= offset;
fSelectionLength= length;
fUpdateReferences= true;
}
// ********* UI INTERACTION AND STARTUP OPTIONS ************
@Override
public String getName() {
return RefactoringCoreMessages.IntroduceIndirectionRefactoring_introduce_indirection_name;
}
public IJavaProject getProject() {
if (fSelectionCompilationUnit != null)
return fSelectionCompilationUnit.getJavaProject();
if (fSelectionClassFile != null)
return fSelectionClassFile.getJavaProject();
if (fTargetMethod != null)
return fTargetMethod.getJavaProject();
return null;
}
public IPackageFragment getInvocationPackage() {
return fSelectionCompilationUnit != null ? (IPackageFragment) fSelectionCompilationUnit.getAncestor(IJavaElement.PACKAGE_FRAGMENT) : null;
}
public boolean canEnableUpdateReferences() {
return true;
}
public void setEnableUpdateReferences(boolean updateReferences) {
fUpdateReferences= updateReferences;
}
public RefactoringStatus setIntermediaryMethodName(String newMethodName) {
Assert.isNotNull(newMethodName);
fIntermediaryMethodName= newMethodName;
IJavaElement context= fIntermediaryClass != null ? fIntermediaryClass : (IMember) fTargetMethod;
RefactoringStatus stat= Checks.checkMethodName(newMethodName, context);
stat.merge(checkOverloading());
return stat;
}
private RefactoringStatus checkOverloading() {
try {
if (fIntermediaryClass != null) {
IMethod[] toCheck= fIntermediaryClass.getMethods();
for (int i= 0; i < toCheck.length; i++) {
IMethod method= toCheck[i];
if (method.getElementName().equals(fIntermediaryMethodName))
return RefactoringStatus.createWarningStatus(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_duplicate_method_name_in_declaring_class_error,
BasicElementLabels.getJavaElementName(fIntermediaryMethodName)));
}
}
} catch (JavaModelException e) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_could_not_parse_declaring_class_error);
}
return new RefactoringStatus();
}
public String getIntermediaryMethodName() {
return fIntermediaryMethodName;
}
/**
* @param fullyQualifiedTypeName the fully qualified name of the intermediary method
* @return status for type name. Use {@link #setIntermediaryMethodName(String)} to check for overridden methods.
*/
public RefactoringStatus setIntermediaryClassName(String fullyQualifiedTypeName) {
IType target= null;
try {
if (fullyQualifiedTypeName.length() == 0)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_class_not_selected_error);
// find type (now including secondaries)
target= getProject().findType(fullyQualifiedTypeName, new NullProgressMonitor());
if (target == null || !target.exists())
return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_class_does_not_exist_error, BasicElementLabels.getJavaElementName(fullyQualifiedTypeName)));
if (target.isAnnotation())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_create_in_annotation);
if (target.isInterface())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_create_on_interface);
} catch (JavaModelException e) {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_unable_determine_declaring_type);
}
if (target.isReadOnly())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_create_in_readonly);
if (target.isBinary())
return RefactoringStatus.createErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_create_in_binary);
fIntermediaryClass= target;
return new RefactoringStatus();
}
/**
* Returns the class name of the intermediary class, or the empty string if none has been set yet.
* @return the intermediary class name or the empty string
*/
public String getIntermediaryClassName() {
return fIntermediaryClass != null ? fIntermediaryClass.getFullyQualifiedName('.') : ""; //$NON-NLS-1$
}
// ********** CONDITION CHECKING **********
@Override
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
try {
pm.beginTask(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_activation, 1);
fRewrites= new HashMap<ICompilationUnit, CompilationUnitRewrite>();
// This refactoring has been invoked on
// (1) a TextSelection inside an ICompilationUnit or inside an IClassFile (definitely with source), or
// (2) an IMethod inside a ICompilationUnit or inside an IClassFile (with or without source)
if (fTargetMethod == null) {
// (1) invoked on a text selection
if (fSelectionStart == 0)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_this_selection);
// if a text selection exists, source is available.
CompilationUnit selectionCURoot;
ASTNode selectionNode;
if (fSelectionCompilationUnit != null) {
// compilation unit - could use CuRewrite later on
selectionCURoot= getCachedCURewrite(fSelectionCompilationUnit).getRoot();
selectionNode= getSelectedNode(fSelectionCompilationUnit, selectionCURoot, fSelectionStart, fSelectionLength);
} else {
// binary class file - no cu rewrite
ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
parser.setResolveBindings(true);
parser.setSource(fSelectionClassFile);
selectionCURoot= (CompilationUnit) parser.createAST(null);
selectionNode= getSelectedNode(null, selectionCURoot, fSelectionStart, fSelectionLength);
}
if (selectionNode == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_this_selection);
IMethodBinding targetMethodBinding= null;
if (selectionNode.getNodeType() == ASTNode.METHOD_INVOCATION) {
targetMethodBinding= ((MethodInvocation) selectionNode).resolveMethodBinding();
} else if (selectionNode.getNodeType() == ASTNode.METHOD_DECLARATION) {
targetMethodBinding= ((MethodDeclaration) selectionNode).resolveBinding();
} else if (selectionNode.getNodeType() == ASTNode.SUPER_METHOD_INVOCATION) {
// Allow invocation on super methods calls. makes sense as other
// calls or even only the declaration can be updated.
targetMethodBinding= ((SuperMethodInvocation) selectionNode).resolveMethodBinding();
} else {
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_this_selection);
}
fTargetMethodBinding= targetMethodBinding.getMethodDeclaration(); // resolve generics
fTargetMethod= (IMethod) fTargetMethodBinding.getJavaElement();
//allow single updating mode if an invocation was selected and the invocation can be updated
if (selectionNode instanceof MethodInvocation && fSelectionCompilationUnit != null)
fSelectionMethodInvocation= (MethodInvocation) selectionNode;
} else {
// (2) invoked on an IMethod: Source may not be available
if (fTargetMethod.getDeclaringType().isAnnotation())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_annotation);
if (fTargetMethod.getCompilationUnit() != null) {
// source method
CompilationUnit selectionCURoot= getCachedCURewrite(fTargetMethod.getCompilationUnit()).getRoot();
MethodDeclaration declaration= ASTNodeSearchUtil.getMethodDeclarationNode(fTargetMethod, selectionCURoot);
fTargetMethodBinding= declaration.resolveBinding().getMethodDeclaration();
} else {
// binary method - no CURewrite available (and none needed as we cannot update the method anyway)
ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
parser.setProject(fTargetMethod.getJavaProject());
IBinding[] bindings= parser.createBindings(new IJavaElement[] { fTargetMethod }, null);
fTargetMethodBinding= ((IMethodBinding) bindings[0]).getMethodDeclaration();
}
}
if (fTargetMethod == null || fTargetMethodBinding == null || (!RefactoringAvailabilityTester.isIntroduceIndirectionAvailable(fTargetMethod)))
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_this_selection);
if (fTargetMethod.getDeclaringType().isLocal() || fTargetMethod.getDeclaringType().isAnonymous())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_for_local_or_anonymous_types);
if (fTargetMethod.isConstructor())
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_for_constructors);
if (fIntermediaryMethodName == null)
fIntermediaryMethodName= fTargetMethod.getElementName();
if (fIntermediaryClass == null) {
if (fSelectionCompilationUnit != null && !fSelectionCompilationUnit.isReadOnly())
fIntermediaryClass= getEnclosingInitialSelectionMember().getDeclaringType();
else if (!fTargetMethod.isBinary() && !fTargetMethod.isReadOnly())
fIntermediaryClass= fTargetMethod.getDeclaringType();
}
return new RefactoringStatus();
} finally {
pm.done();
}
}
@Override
public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
RefactoringStatus result= new RefactoringStatus();
fTextChangeManager= new TextChangeManager();
fIntermediaryFirstParameterType= null;
fIntermediaryClassBinding= null;
for (Iterator<CompilationUnitRewrite> iter= fRewrites.values().iterator(); iter.hasNext();)
iter.next().clearASTAndImportRewrites();
int startupTicks= 5;
int hierarchyTicks= 5;
int visibilityTicks= 5;
int referenceTicks= fUpdateReferences ? 30 : 5;
int creationTicks= 5;
pm.beginTask("", startupTicks + hierarchyTicks + visibilityTicks + referenceTicks + creationTicks); //$NON-NLS-1$
pm.setTaskName(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_conditions);
result.merge(Checks.checkMethodName(fIntermediaryMethodName, fIntermediaryClass));
if (result.hasFatalError())
return result;
if (fIntermediaryClass == null)
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_run_without_intermediary_type);
// intermediary class is already non binary/non-enum/non-interface.
CompilationUnitRewrite imRewrite= getCachedCURewrite(fIntermediaryClass.getCompilationUnit());
fIntermediaryClassBinding= typeToBinding(fIntermediaryClass, imRewrite.getRoot());
fAdjustor= new MemberVisibilityAdjustor(fIntermediaryClass, fIntermediaryClass);
fIntermediaryAdjustments= new HashMap<IMember, IncomingMemberVisibilityAdjustment>();
// check static method in non-static nested type
if (fIntermediaryClassBinding.isNested() && !Modifier.isStatic(fIntermediaryClassBinding.getModifiers()))
return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_create_in_nested_nonstatic, JavaStatusContext.create(fIntermediaryClass));
pm.worked(startupTicks);
if (pm.isCanceled())
throw new OperationCanceledException();
if (fUpdateReferences) {
pm.setTaskName(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_conditions + " " + RefactoringCoreMessages.IntroduceIndirectionRefactoring_looking_for_references); //$NON-NLS-1$
result.merge(updateReferences(new NoOverrideProgressMonitor(pm, referenceTicks)));
pm.setTaskName(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_conditions);
} else {
// only update the declaration and/or a selected method invocation
if (fSelectionMethodInvocation != null) {
fIntermediaryFirstParameterType= getExpressionType(fSelectionMethodInvocation);
final IMember enclosing= getEnclosingInitialSelectionMember();
// create an edit for this particular call
result.merge(updateMethodInvocation(fSelectionMethodInvocation, enclosing, getCachedCURewrite(fSelectionCompilationUnit)));
if (!isRewriteKept(fSelectionCompilationUnit))
createChangeAndDiscardRewrite(fSelectionCompilationUnit);
// does call see the intermediary method?
// => increase visibility of the type of the intermediary method.
result.merge(adjustVisibility(fIntermediaryClass, enclosing.getDeclaringType(), new NoOverrideProgressMonitor(pm, 0)));
}
pm.worked(referenceTicks);
}
if (pm.isCanceled())
throw new OperationCanceledException();
if (fIntermediaryFirstParameterType == null)
fIntermediaryFirstParameterType= fTargetMethodBinding.getDeclaringClass();
// The target type and method may have changed - update them
IType actualTargetType= (IType) fIntermediaryFirstParameterType.getJavaElement();
if (!fTargetMethod.getDeclaringType().equals(actualTargetType)) {
IMethod actualTargetMethod= new MethodOverrideTester(actualTargetType, actualTargetType.newSupertypeHierarchy(null)).findOverriddenMethodInHierarchy(actualTargetType, fTargetMethod);
fTargetMethod= actualTargetMethod;
fTargetMethodBinding= findMethodBindingInHierarchy(fIntermediaryFirstParameterType, actualTargetMethod);
Assert.isNotNull(fTargetMethodBinding);
}
result.merge(checkCanCreateIntermediaryMethod());
createIntermediaryMethod();
pm.worked(creationTicks);
pm.setTaskName(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_conditions + " " + RefactoringCoreMessages.IntroduceIndirectionRefactoring_adjusting_visibility); //$NON-NLS-1$
result.merge(updateTargetVisibility(new NoOverrideProgressMonitor(pm, 0)));
result.merge(updateIntermediaryVisibility(new NoOverrideProgressMonitor(pm, 0)));
pm.worked(visibilityTicks);
pm.setTaskName(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_conditions);
createChangeAndDiscardRewrite(fIntermediaryClass.getCompilationUnit());
result.merge(Checks.validateModifiesFiles(getAllFilesToModify(), getValidationContext()));
pm.done();
return result;
}
private RefactoringStatus updateTargetVisibility(IProgressMonitor monitor) throws JavaModelException, CoreException {
RefactoringStatus result= new RefactoringStatus();
// Adjust the visibility of the method and of the referenced type. Note that
// the target method may not be in the target type; and in this case, the type
// of the target method does not need a visibility adjustment.
// This method is called after all other changes have been
// created. Changes induced by this method will be attached to those changes.
result.merge(adjustVisibility((IType) fIntermediaryFirstParameterType.getJavaElement(), fIntermediaryClass, monitor));
if (result.hasError())
return result; // binary
ModifierKeyword neededVisibility= getNeededVisibility(fTargetMethod, fIntermediaryClass);
if (neededVisibility != null) {
result.merge(adjustVisibility(fTargetMethod, neededVisibility, monitor));
if (result.hasError())
return result; // binary
// Need to adjust the overridden methods of the target method.
ITypeHierarchy hierarchy= fTargetMethod.getDeclaringType().newTypeHierarchy(null);
MethodOverrideTester tester= new MethodOverrideTester(fTargetMethod.getDeclaringType(), hierarchy);
IType[] subtypes= hierarchy.getAllSubtypes(fTargetMethod.getDeclaringType());
for (int i= 0; i < subtypes.length; i++) {
IMethod method= tester.findOverridingMethodInType(subtypes[i], fTargetMethod);
if (method != null && method.exists()) {
result.merge(adjustVisibility(method, neededVisibility, monitor));
if (monitor.isCanceled())
throw new OperationCanceledException();
if (result.hasError())
return result; // binary
}
}
}
return result;
}
private RefactoringStatus updateIntermediaryVisibility(NoOverrideProgressMonitor monitor) throws JavaModelException {
return rewriteVisibility(fIntermediaryAdjustments, fRewrites, monitor);
}
private RefactoringStatus updateReferences(IProgressMonitor monitor) throws CoreException {
RefactoringStatus result= new RefactoringStatus();
monitor.beginTask("", 90); //$NON-NLS-1$
if (monitor.isCanceled())
throw new OperationCanceledException();
IMethod[] ripple= RippleMethodFinder2.getRelatedMethods(fTargetMethod, false, new NoOverrideProgressMonitor(monitor, 10), null);
if (monitor.isCanceled())
throw new OperationCanceledException();
SearchResultGroup[] references= Checks.excludeCompilationUnits(getReferences(ripple, new NoOverrideProgressMonitor(monitor, 10), result), result);
if (result.hasFatalError())
return result;
result.merge(Checks.checkCompileErrorsInAffectedFiles(references));
if (monitor.isCanceled())
throw new OperationCanceledException();
int ticksPerCU= references.length == 0 ? 0 : 70 / references.length;
for (int i= 0; i < references.length; i++) {
SearchResultGroup group= references[i];
SearchMatch[] searchResults= group.getSearchResults();
CompilationUnitRewrite currentCURewrite= getCachedCURewrite(group.getCompilationUnit());
for (int j= 0; j < searchResults.length; j++) {
SearchMatch match= searchResults[j];
if (match.isInsideDocComment())
continue;
IMember enclosingMember= (IMember) match.getElement();
ASTNode target= getSelectedNode(group.getCompilationUnit(), currentCURewrite.getRoot(), match.getOffset(), match.getLength());
if (target instanceof SuperMethodInvocation) {
// Cannot retarget calls to super - add a warning
result.merge(createWarningAboutCall(enclosingMember, target, RefactoringCoreMessages.IntroduceIndirectionRefactoring_call_warning_super_keyword));
continue;
}
Assert.isTrue(target instanceof MethodInvocation, "Element of call should be a MethodInvocation."); //$NON-NLS-1$
MethodInvocation invocation= (MethodInvocation) target;
ITypeBinding typeBinding= getExpressionType(invocation);
if (fIntermediaryFirstParameterType == null) {
// no highest type yet
fIntermediaryFirstParameterType= typeBinding.getTypeDeclaration();
} else {
// check if current type is higher
result.merge(findCommonParent(typeBinding.getTypeDeclaration()));
}
if (result.hasFatalError())
return result;
// create an edit for this particular call
result.merge(updateMethodInvocation(invocation, enclosingMember, currentCURewrite));
// does call see the intermediary method?
// => increase visibility of the type of the intermediary method.
result.merge(adjustVisibility(fIntermediaryClass, enclosingMember.getDeclaringType(), new NoOverrideProgressMonitor(monitor, 0)));
if (monitor.isCanceled())
throw new OperationCanceledException();
}
if (!isRewriteKept(group.getCompilationUnit()))
createChangeAndDiscardRewrite(group.getCompilationUnit());
monitor.worked(ticksPerCU);
}
monitor.done();
return result;
}
private RefactoringStatus findCommonParent(ITypeBinding typeBinding) {
RefactoringStatus status= new RefactoringStatus();
ITypeBinding highest= fIntermediaryFirstParameterType;
ITypeBinding current= typeBinding;
if (current.equals(highest) || Bindings.isSuperType(highest, current))
// current is the same as highest or highest is already a supertype of current in the same hierarchy => no change
return status;
// find lowest common supertype with the method
// search in bottom-up order
ITypeBinding[] currentAndSupers= getTypeAndAllSuperTypes(current);
ITypeBinding[] highestAndSupers= getTypeAndAllSuperTypes(highest);
ITypeBinding foundBinding= null;
for (int i1= 0; i1 < currentAndSupers.length; i1++) {
for (int i2= 0; i2 < highestAndSupers.length; i2++) {
if (highestAndSupers[i2].isEqualTo(currentAndSupers[i1])
&& (Bindings.findMethodInHierarchy(highestAndSupers[i2], fTargetMethodBinding.getName(), fTargetMethodBinding.getParameterTypes()) != null)) {
foundBinding= highestAndSupers[i2];
break;
}
}
if (foundBinding != null)
break;
}
if (foundBinding != null) {
fIntermediaryFirstParameterType= foundBinding;
} else {
String type1= BasicElementLabels.getJavaElementName(fIntermediaryFirstParameterType.getQualifiedName());
String type2= BasicElementLabels.getJavaElementName(current.getQualifiedName());
status.addFatalError(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_open_hierarchy_error, new String[] { type1, type2 }));
}
return status;
}
// ******************** CHANGE CREATION ***********************
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
final Map<String, String> arguments= new HashMap<String, String>();
String project= null;
IJavaProject javaProject= fTargetMethod.getJavaProject();
if (javaProject != null)
project= javaProject.getElementName();
int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE;
final IType declaring= fTargetMethod.getDeclaringType();
try {
if (declaring.isLocal() || declaring.isAnonymous())
flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
} catch (JavaModelException exception) {
JavaPlugin.log(exception);
}
final String description= Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_descriptor_description_short, BasicElementLabels.getJavaElementName(fTargetMethod.getElementName()));
final String header= Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_descriptor_description, new String[] { JavaElementLabels.getTextLabel(fTargetMethod, JavaElementLabels.ALL_FULLY_QUALIFIED), JavaElementLabels.getTextLabel(declaring, JavaElementLabels.ALL_FULLY_QUALIFIED)});
final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_original_pattern, JavaElementLabels.getTextLabel(fTargetMethod, JavaElementLabels.ALL_FULLY_QUALIFIED)));
comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_method_pattern, BasicElementLabels.getJavaElementName(fIntermediaryMethodName)));
comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_declaring_pattern, JavaElementLabels.getTextLabel(fIntermediaryClass, JavaElementLabels.ALL_FULLY_QUALIFIED)));
if (fUpdateReferences)
comment.addSetting(RefactoringCoreMessages.JavaRefactoringDescriptor_update_references);
final IntroduceIndirectionDescriptor descriptor= RefactoringSignatureDescriptorFactory.createIntroduceIndirectionDescriptor(project, description, comment.asString(), arguments, flags);
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT, JavaRefactoringDescriptorUtil.elementToHandle(project, fTargetMethod));
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME, fIntermediaryMethodName);
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1, JavaRefactoringDescriptorUtil.elementToHandle(project, fIntermediaryClass));
arguments.put(JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES, Boolean.valueOf(fUpdateReferences).toString());
return new DynamicValidationRefactoringChange(descriptor, RefactoringCoreMessages.IntroduceIndirectionRefactoring_introduce_indirection, fTextChangeManager.getAllChanges());
}
// ******************* CREATE INTERMEDIARY **********************
/**
* Checks whether the target method can be created. Note that this
* can only be done after fDelegateParameterType has been initialized.
* @return resulting status
* @throws JavaModelException should not happen
*/
private RefactoringStatus checkCanCreateIntermediaryMethod() throws JavaModelException {
// check if method already exists:
List<ITypeBinding> parameterBindings= new ArrayList<ITypeBinding>();
if (!isStaticTarget())
parameterBindings.add(fIntermediaryFirstParameterType);
parameterBindings.addAll(Arrays.asList(fTargetMethodBinding.getParameterTypes()));
return Checks.checkMethodInType(fIntermediaryClassBinding, fIntermediaryMethodName, parameterBindings.toArray(new ITypeBinding[parameterBindings.size()]));
}
private void createIntermediaryMethod() throws CoreException {
CompilationUnitRewrite imRewrite= getCachedCURewrite(fIntermediaryClass.getCompilationUnit());
AST ast= imRewrite.getAST();
MethodDeclaration intermediary= ast.newMethodDeclaration();
// Intermediary class is non-anonymous
AbstractTypeDeclaration type= (AbstractTypeDeclaration)typeToDeclaration(fIntermediaryClass, imRewrite.getRoot());
// Name
intermediary.setName(ast.newSimpleName(fIntermediaryMethodName));
// Flags
List<IExtendedModifier> modifiers= intermediary.modifiers();
modifiers.add(imRewrite.getAST().newModifier(ModifierKeyword.PUBLIC_KEYWORD));
modifiers.add(imRewrite.getAST().newModifier(ModifierKeyword.STATIC_KEYWORD));
// Parameters
String targetParameterName= StubUtility.suggestArgumentName(getProject(), fIntermediaryFirstParameterType.getName(), fTargetMethod.getParameterNames());
ImportRewriteContext context= new ContextSensitiveImportRewriteContext(type, imRewrite.getImportRewrite());
if (!isStaticTarget()) {
// Add first param
SingleVariableDeclaration parameter= imRewrite.getAST().newSingleVariableDeclaration();
Type t= imRewrite.getImportRewrite().addImport(fIntermediaryFirstParameterType, imRewrite.getAST(), context);
if (fIntermediaryFirstParameterType.isGenericType()) {
ParameterizedType parameterized= imRewrite.getAST().newParameterizedType(t);
ITypeBinding[] typeParameters= fIntermediaryFirstParameterType.getTypeParameters();
for (int i= 0; i < typeParameters.length; i++)
parameterized.typeArguments().add(imRewrite.getImportRewrite().addImport(typeParameters[i], imRewrite.getAST()));
t= parameterized;
}
parameter.setType(t);
parameter.setName(imRewrite.getAST().newSimpleName(targetParameterName));
intermediary.parameters().add(parameter);
}
// Add other params
copyArguments(intermediary, imRewrite);
// Add type parameters of declaring class (and enclosing classes)
if (!isStaticTarget() && fIntermediaryFirstParameterType.isGenericType())
addTypeParameters(imRewrite, intermediary.typeParameters(), fIntermediaryFirstParameterType);
// Add type params of method
copyTypeParameters(intermediary, imRewrite);
// Return type
intermediary.setReturnType2(imRewrite.getImportRewrite().addImport(fTargetMethodBinding.getReturnType(), ast, context));
// Exceptions
copyExceptions(intermediary, imRewrite);
// Body
MethodInvocation invocation= imRewrite.getAST().newMethodInvocation();
invocation.setName(imRewrite.getAST().newSimpleName(fTargetMethod.getElementName()));
if (isStaticTarget()) {
Type importedType= imRewrite.getImportRewrite().addImport(fTargetMethodBinding.getDeclaringClass(), ast, context);
invocation.setExpression(ASTNodeFactory.newName(ast, ASTNodes.asString(importedType)));
} else {
invocation.setExpression(imRewrite.getAST().newSimpleName(targetParameterName));
}
copyInvocationParameters(invocation, ast);
Statement call= encapsulateInvocation(intermediary, invocation);
final Block body= imRewrite.getAST().newBlock();
body.statements().add(call);
intermediary.setBody(body);
// method comment
ICompilationUnit targetCU= imRewrite.getCu();
if (StubUtility.doAddComments(targetCU.getJavaProject())) {
String comment= CodeGeneration.getMethodComment(targetCU, getIntermediaryClassName(), intermediary, null, StubUtility.getLineDelimiterUsed(targetCU));
if (comment != null) {
Javadoc javadoc= (Javadoc) imRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JAVADOC);
intermediary.setJavadoc(javadoc);
}
}
// Add the completed method to the intermediary type:
ChildListPropertyDescriptor typeBodyDeclarationsProperty= typeToBodyDeclarationProperty(fIntermediaryClass, imRewrite.getRoot());
ListRewrite bodyDeclarationsListRewrite= imRewrite.getASTRewrite().getListRewrite(type, typeBodyDeclarationsProperty);
bodyDeclarationsListRewrite.insertAt(intermediary, ASTNodes.getInsertionIndex(intermediary, type.bodyDeclarations()), imRewrite
.createGroupDescription(RefactoringCoreMessages.IntroduceIndirectionRefactoring_group_description_create_new_method));
}
private void addTypeParameters(CompilationUnitRewrite imRewrite, List<TypeParameter> list, ITypeBinding parent) {
ITypeBinding enclosing= parent.getDeclaringClass();
if (enclosing != null)
addTypeParameters(imRewrite, list, enclosing);
ITypeBinding[] typeParameters= parent.getTypeParameters();
for (int i= 0; i < typeParameters.length; i++) {
TypeParameter ntp= imRewrite.getAST().newTypeParameter();
ntp.setName(imRewrite.getAST().newSimpleName(typeParameters[i].getName()));
ITypeBinding[] bounds= typeParameters[i].getTypeBounds();
for (int j= 0; j < bounds.length; j++)
if (!"java.lang.Object".equals(bounds[j].getQualifiedName())) //$NON-NLS-1$
ntp.typeBounds().add(imRewrite.getImportRewrite().addImport(bounds[j], imRewrite.getAST()));
list.add(ntp);
}
}
private Statement encapsulateInvocation(MethodDeclaration declaration, MethodInvocation invocation) {
final Type type= declaration.getReturnType2();
if (type == null || (type instanceof PrimitiveType && PrimitiveType.VOID.equals( ((PrimitiveType) type).getPrimitiveTypeCode())))
return invocation.getAST().newExpressionStatement(invocation);
ReturnStatement statement= invocation.getAST().newReturnStatement();
statement.setExpression(invocation);
return statement;
}
private void copyInvocationParameters(MethodInvocation invocation, AST ast) throws JavaModelException {
String[] names= fTargetMethod.getParameterNames();
for (int i= 0; i < names.length; i++)
invocation.arguments().add(ast.newSimpleName(names[i]));
}
private void copyArguments(MethodDeclaration intermediary, CompilationUnitRewrite rew) throws JavaModelException {
String[] names= fTargetMethod.getParameterNames();
ITypeBinding[] types= fTargetMethodBinding.getParameterTypes();
for (int i= 0; i < names.length; i++) {
ITypeBinding typeBinding= types[i];
SingleVariableDeclaration newElement= rew.getAST().newSingleVariableDeclaration();
newElement.setName(rew.getAST().newSimpleName(names[i]));
if (i == (names.length - 1) && fTargetMethodBinding.isVarargs()) {
newElement.setVarargs(true);
if (typeBinding.isArray())
typeBinding= typeBinding.getComponentType();
}
newElement.setType(rew.getImportRewrite().addImport(typeBinding, rew.getAST()));
intermediary.parameters().add(newElement);
}
}
private void copyTypeParameters(MethodDeclaration intermediary, CompilationUnitRewrite rew) {
ITypeBinding[] typeParameters= fTargetMethodBinding.getTypeParameters();
for (int i= 0; i < typeParameters.length; i++) {
ITypeBinding current= typeParameters[i];
TypeParameter parameter= rew.getAST().newTypeParameter();
parameter.setName(rew.getAST().newSimpleName(current.getName()));
ITypeBinding[] bounds= current.getTypeBounds();
for (int j= 0; j < bounds.length; j++)
if (!"java.lang.Object".equals(bounds[j].getQualifiedName())) //$NON-NLS-1$
parameter.typeBounds().add(rew.getImportRewrite().addImport(bounds[j], rew.getAST()));
intermediary.typeParameters().add(parameter);
}
}
private void copyExceptions(MethodDeclaration intermediary, CompilationUnitRewrite imRewrite) {
ITypeBinding[] exceptionTypes= fTargetMethodBinding.getExceptionTypes();
for (int i= 0; i < exceptionTypes.length; i++) {
final String qualifiedName= imRewrite.getImportRewrite().addImport(exceptionTypes[i]);
intermediary.thrownExceptions().add(ASTNodeFactory.newName(imRewrite.getAST(), qualifiedName));
}
}
// ******************* UPDATE CALLS **********************
private RefactoringStatus updateMethodInvocation(MethodInvocation originalInvocation, IMember enclosing, CompilationUnitRewrite unitRewriter) throws JavaModelException {
RefactoringStatus status= new RefactoringStatus();
// If the method invocation utilizes type arguments, skip this
// call as the new target method may have additional parameters
if (originalInvocation.typeArguments().size() > 0)
return createWarningAboutCall(enclosing, originalInvocation, RefactoringCoreMessages.IntroduceIndirectionRefactoring_call_warning_type_arguments);
MethodInvocation newInvocation= unitRewriter.getAST().newMethodInvocation();
List<Expression> newInvocationArgs= newInvocation.arguments();
List<Expression> originalInvocationArgs= originalInvocation.arguments();
// static call => always use a qualifier
String qualifier= unitRewriter.getImportRewrite().addImport(fIntermediaryClassBinding);
newInvocation.setExpression(ASTNodeFactory.newName(unitRewriter.getAST(), qualifier));
newInvocation.setName(unitRewriter.getAST().newSimpleName(getIntermediaryMethodName()));
final Expression expression= originalInvocation.getExpression();
if (!isStaticTarget()) {
// Add the expression as the first parameter
if (expression == null) {
// There is no expression for this call. Use a (possibly qualified) "this" expression.
ThisExpression expr= unitRewriter.getAST().newThisExpression();
RefactoringStatus qualifierStatus= qualifyThisExpression(expr, originalInvocation, enclosing, unitRewriter);
status.merge(qualifierStatus);
if (qualifierStatus.hasEntries())
// warning means don't include this invocation
return status;
newInvocationArgs.add(expr);
} else {
Expression expressionAsParam= (Expression) unitRewriter.getASTRewrite().createMoveTarget(expression);
newInvocationArgs.add(expressionAsParam);
}
} else {
if (expression != null) {
// Check if expression is the class name. If not, there may
// be side effects (e.g. inside methods) -> don't update
if (! (expression instanceof Name) || ASTNodes.getTypeBinding((Name) expression) == null)
return createWarningAboutCall(enclosing, originalInvocation, RefactoringCoreMessages.IntroduceIndirectionRefactoring_call_warning_static_expression_access);
}
}
for (int i= 0; i < originalInvocationArgs.size(); i++) {
Expression originalInvocationArg= originalInvocationArgs.get(i);
Expression movedArg= (Expression) unitRewriter.getASTRewrite().createMoveTarget(originalInvocationArg);
newInvocationArgs.add(movedArg);
}
unitRewriter.getASTRewrite().replace(originalInvocation, newInvocation,
unitRewriter.createGroupDescription(RefactoringCoreMessages.IntroduceIndirectionRefactoring_group_description_replace_call));
return status;
}
/**
* Attempts to qualify a "this" expression for a method invocation with an appropriate qualifier.
* The invoked method is analyzed according to the following specs:
*
* 'this' must be qualified iff method is declared in an enclosing type or a supertype of an enclosing type
*
* 1) The method is declared somewhere outside of the cu of the invocation
* 1a) inside a supertype of the current type
* 1b) inside a supertype of an enclosing type
* 2) The method is declared inside of the cu of the invocation
* 2a) inside the type of the invocation
* 2b) outside the type of the invocation
*
* In case of 1a) and 2b), qualify with the enclosing type.
* @param expr a {@link ThisExpression}
* @param originalInvocation the original method invocation
* @param enclosing the enclosing member of the original method invocation
* @param unitRewriter the rewrite
* @return resulting status
*
*/
private RefactoringStatus qualifyThisExpression(ThisExpression expr, MethodInvocation originalInvocation, IMember enclosing, CompilationUnitRewrite unitRewriter) {
RefactoringStatus status= new RefactoringStatus();
IMethodBinding methodBinding= originalInvocation.resolveMethodBinding();
MethodDeclaration methodDeclaration= (MethodDeclaration) ASTNodes.findDeclaration(methodBinding, originalInvocation.getRoot());
ITypeBinding currentTypeBinding= null;
if (methodDeclaration != null) {
// Case 1) : Declaring class is inside this cu => use its name if it's declared in an enclosing type
if (ASTNodes.isParent(originalInvocation, methodDeclaration.getParent()))
currentTypeBinding= methodBinding.getDeclaringClass();
else
currentTypeBinding= ASTNodes.getEnclosingType(originalInvocation);
} else {
// Case 2) : Declaring class is outside of this cu => find subclass in this cu
ASTNode currentTypeDeclaration= getEnclosingTypeDeclaration(originalInvocation);
currentTypeBinding= ASTNodes.getEnclosingType(currentTypeDeclaration);
while (currentTypeDeclaration != null && (Bindings.findMethodInHierarchy(currentTypeBinding, methodBinding.getName(), methodBinding.getParameterTypes()) == null)) {
currentTypeDeclaration= getEnclosingTypeDeclaration(currentTypeDeclaration.getParent());
currentTypeBinding= ASTNodes.getEnclosingType(currentTypeDeclaration);
}
}
if (currentTypeBinding == null) {
status.merge(createWarningAboutCall(enclosing, originalInvocation, RefactoringCoreMessages.IntroduceIndirectionRefactoring_call_warning_declaring_type_not_found));
return status;
}
currentTypeBinding= currentTypeBinding.getTypeDeclaration();
ITypeBinding typeOfCall= ASTNodes.getEnclosingType(originalInvocation);
if (!typeOfCall.equals(currentTypeBinding)) {
if (currentTypeBinding.isAnonymous()) {
// Cannot qualify, see bug 115277
status.merge(createWarningAboutCall(enclosing, originalInvocation, RefactoringCoreMessages.IntroduceIndirectionRefactoring_call_warning_anonymous_cannot_qualify));
} else {
expr.setQualifier(unitRewriter.getAST().newSimpleName(currentTypeBinding.getName()));
}
} else {
// do not qualify, only use "this.".
}
return status;
}
// ********* SMALL HELPERS ********************
/*
* Helper method for finding an IMethod inside a binding hierarchy
*/
private IMethodBinding findMethodBindingInHierarchy(ITypeBinding currentTypeBinding, IMethod methodDeclaration) {
IMethodBinding[] bindings= currentTypeBinding.getDeclaredMethods();
for (int i= 0; i < bindings.length; i++)
if (methodDeclaration.equals(bindings[i].getJavaElement()))
return bindings[i];
ITypeBinding superClass= currentTypeBinding.getSuperclass();
if (superClass != null) {
IMethodBinding b= findMethodBindingInHierarchy(superClass, methodDeclaration);
if (b != null)
return b;
}
ITypeBinding[] interfaces= currentTypeBinding.getInterfaces();
for (int i= 0; i < interfaces.length; i++) {
IMethodBinding b= findMethodBindingInHierarchy(interfaces[i], methodDeclaration);
if (b != null)
return b;
}
return null;
}
/*
* Helper method for retrieving a *bottom-up* list of super type bindings
*/
private ITypeBinding[] getTypeAndAllSuperTypes(ITypeBinding type) {
List<ITypeBinding> result= new ArrayList<ITypeBinding>();
collectSuperTypes(type, result);
return result.toArray(new ITypeBinding[result.size()]);
}
private void collectSuperTypes(ITypeBinding curr, List<ITypeBinding> list) {
if (list.add(curr.getTypeDeclaration())) {
ITypeBinding[] interfaces= curr.getInterfaces();
for (int i= 0; i < interfaces.length; i++) {
collectSuperTypes(interfaces[i], list);
}
ITypeBinding superClass= curr.getSuperclass();
if (superClass != null) {
collectSuperTypes(superClass, list);
}
}
}
private CompilationUnitRewrite getCachedCURewrite(ICompilationUnit unit) {
CompilationUnitRewrite rewrite= fRewrites.get(unit);
if (rewrite == null) {
rewrite= new CompilationUnitRewrite(unit);
fRewrites.put(unit, rewrite);
}
return rewrite;
}
private boolean isRewriteKept(ICompilationUnit compilationUnit) {
return fIntermediaryClass.getCompilationUnit().equals(compilationUnit);
}
private void createChangeAndDiscardRewrite(ICompilationUnit compilationUnit) throws CoreException {
CompilationUnitRewrite rewrite= fRewrites.get(compilationUnit);
if (rewrite != null) {
fTextChangeManager.manage(compilationUnit, rewrite.createChange(true));
fRewrites.remove(compilationUnit);
}
}
private SearchResultGroup[] getReferences(IMethod[] methods, IProgressMonitor pm, RefactoringStatus status) throws CoreException {
SearchPattern pattern= RefactoringSearchEngine.createOrPattern(methods, IJavaSearchConstants.REFERENCES);
IJavaSearchScope scope= RefactoringScopeFactory.create(fIntermediaryClass, false);
return RefactoringSearchEngine.search(pattern, scope, pm, status);
}
private ITypeBinding typeToBinding(IType type, CompilationUnit root) throws JavaModelException {
ASTNode typeNode= typeToDeclaration(type, root);
if (type.isAnonymous()) {
return ((AnonymousClassDeclaration) typeNode).resolveBinding();
} else {
return ((AbstractTypeDeclaration) typeNode).resolveBinding();
}
}
private ASTNode typeToDeclaration(IType type, CompilationUnit root) throws JavaModelException {
Name intermediateName= (Name) NodeFinder.perform(root, type.getNameRange());
if (type.isAnonymous()) {
return ASTNodes.getParent(intermediateName, AnonymousClassDeclaration.class);
} else {
return ASTNodes.getParent(intermediateName, AbstractTypeDeclaration.class);
}
}
private ASTNode getEnclosingTypeDeclaration(ASTNode node) {
while (node != null) {
if (node instanceof AbstractTypeDeclaration) {
return node;
} else if (node instanceof AnonymousClassDeclaration) {
return node;
}
node= node.getParent();
}
return null;
}
private ChildListPropertyDescriptor typeToBodyDeclarationProperty(IType type, CompilationUnit root) throws JavaModelException {
ASTNode typeDeclaration= typeToDeclaration(type, root);
if (typeDeclaration instanceof AbstractTypeDeclaration)
return ((AbstractTypeDeclaration) typeDeclaration).getBodyDeclarationsProperty();
else if (typeDeclaration instanceof AnonymousClassDeclaration)
return AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY;
Assert.isTrue(false);
return null;
}
private RefactoringStatus createWarningAboutCall(IMember enclosing, ASTNode concreteNode, String message) {
String name= JavaElementLabels.getElementLabel(enclosing, JavaElementLabels.ALL_DEFAULT);
String container= JavaElementLabels.getElementLabel(enclosing.getDeclaringType(), JavaElementLabels.ALL_FULLY_QUALIFIED);
return RefactoringStatus.createWarningStatus(Messages.format(message, new String[] { name, container }), JavaStatusContext.create(enclosing.getCompilationUnit(), concreteNode));
}
private ITypeBinding getExpressionType(MethodInvocation invocation) {
Expression expression= invocation.getExpression();
ITypeBinding typeBinding= null;
if (expression == null) {
typeBinding= invocation.resolveMethodBinding().getDeclaringClass();
} else {
typeBinding= expression.resolveTypeBinding();
}
Assert.isNotNull(typeBinding, "Type binding of target expression may not be null"); //$NON-NLS-1$
return typeBinding;
}
private IFile[] getAllFilesToModify() {
List<ICompilationUnit> cus= new ArrayList<ICompilationUnit>();
cus.addAll(Arrays.asList(fTextChangeManager.getAllCompilationUnits()));
return ResourceUtil.getFiles(cus.toArray(new ICompilationUnit[cus.size()]));
}
private boolean isStaticTarget() throws JavaModelException {
return Flags.isStatic(fTargetMethod.getFlags());
}
private IMember getEnclosingInitialSelectionMember() throws JavaModelException {
return (IMember) fSelectionCompilationUnit.getElementAt(fSelectionStart);
}
private static ASTNode getSelectedNode(ICompilationUnit unit, CompilationUnit root, int offset, int length) {
ASTNode node= null;
try {
if (unit != null)
node= checkNode(NodeFinder.perform(root, offset, length, unit));
else
node= checkNode(NodeFinder.perform(root, offset, length));
} catch (JavaModelException e) {
// Do nothing
}
if (node != null)
return node;
return checkNode(NodeFinder.perform(root, offset, length));
}
private static ASTNode checkNode(ASTNode node) {
if (node == null)
return null;
if (node.getNodeType() == ASTNode.SIMPLE_NAME) {
node= node.getParent();
} else if (node.getNodeType() == ASTNode.EXPRESSION_STATEMENT) {
node= ((ExpressionStatement) node).getExpression();
}
switch (node.getNodeType()) {
case ASTNode.METHOD_INVOCATION:
case ASTNode.METHOD_DECLARATION:
case ASTNode.SUPER_METHOD_INVOCATION:
return node;
}
return null;
}
// ***************** VISIBILITY ********************
private ModifierKeyword getNeededVisibility(IMember whoToAdjust, IMember fromWhereToLook) throws JavaModelException {
return fAdjustor.getVisibilityThreshold(fromWhereToLook, whoToAdjust, new NullProgressMonitor());
}
private RefactoringStatus adjustVisibility(IMember whoToAdjust, IMember fromWhereToLook, IProgressMonitor monitor) throws CoreException {
return adjustVisibility(whoToAdjust, getNeededVisibility(whoToAdjust, fromWhereToLook), true, monitor);
}
private RefactoringStatus adjustVisibility(IMember whoToAdjust, ModifierKeyword neededVisibility, IProgressMonitor monitor) throws CoreException {
return adjustVisibility(whoToAdjust, neededVisibility, false, monitor);
}
private RefactoringStatus adjustVisibility(IMember whoToAdjust, ModifierKeyword neededVisibility, boolean alsoIncreaseEnclosing, IProgressMonitor monitor) throws CoreException {
Map<IMember, IncomingMemberVisibilityAdjustment> adjustments;
if (isRewriteKept(whoToAdjust.getCompilationUnit()))
adjustments= fIntermediaryAdjustments;
else
adjustments= new HashMap<IMember, IncomingMemberVisibilityAdjustment>();
int existingAdjustments= adjustments.size();
addAdjustment(whoToAdjust, neededVisibility, adjustments);
if (alsoIncreaseEnclosing)
while (whoToAdjust.getDeclaringType() != null) {
whoToAdjust= whoToAdjust.getDeclaringType();
addAdjustment(whoToAdjust, neededVisibility, adjustments);
}
boolean hasNewAdjustments= (adjustments.size() - existingAdjustments) > 0;
if (hasNewAdjustments && ( (whoToAdjust.isReadOnly() || whoToAdjust.isBinary())))
return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_cannot_update_binary_target_visibility, new String[] { JavaElementLabels
.getElementLabel(whoToAdjust, JavaElementLabels.ALL_DEFAULT) }), JavaStatusContext.create(whoToAdjust));
RefactoringStatus status= new RefactoringStatus();
// Don't create a rewrite if it is not necessary
if (!hasNewAdjustments)
return status;
try {
monitor.beginTask(RefactoringCoreMessages.MemberVisibilityAdjustor_adjusting, 2);
Map<ICompilationUnit, CompilationUnitRewrite> rewrites;
if (!isRewriteKept(whoToAdjust.getCompilationUnit())) {
CompilationUnitRewrite rewrite= new CompilationUnitRewrite(whoToAdjust.getCompilationUnit());
rewrite.setResolveBindings(false);
rewrites= new HashMap<ICompilationUnit, CompilationUnitRewrite>();
rewrites.put(whoToAdjust.getCompilationUnit(), rewrite);
status.merge(rewriteVisibility(adjustments, rewrites, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)));
rewrite.attachChange((CompilationUnitChange) fTextChangeManager.get(whoToAdjust.getCompilationUnit()), true, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
}
} finally {
monitor.done();
}
return status;
}
private RefactoringStatus rewriteVisibility(Map<IMember, IncomingMemberVisibilityAdjustment> adjustments, Map<ICompilationUnit, CompilationUnitRewrite> rewrites, IProgressMonitor monitor) throws JavaModelException {
RefactoringStatus status= new RefactoringStatus();
fAdjustor.setRewrites(rewrites);
fAdjustor.setAdjustments(adjustments);
fAdjustor.setStatus(status);
fAdjustor.rewriteVisibility(monitor);
return status;
}
private void addAdjustment(IMember whoToAdjust, ModifierKeyword neededVisibility, Map<IMember, IncomingMemberVisibilityAdjustment> adjustments) throws JavaModelException {
ModifierKeyword currentVisibility= ModifierKeyword.fromFlagValue(JdtFlags.getVisibilityCode(whoToAdjust));
if (MemberVisibilityAdjustor.hasLowerVisibility(currentVisibility, neededVisibility)
&& MemberVisibilityAdjustor.needsVisibilityAdjustments(whoToAdjust, neededVisibility, adjustments))
adjustments.put(whoToAdjust, new MemberVisibilityAdjustor.IncomingMemberVisibilityAdjustment(whoToAdjust, neededVisibility,
RefactoringStatus.createWarningStatus(Messages.format(MemberVisibilityAdjustor.getMessage(whoToAdjust), new String[] {
MemberVisibilityAdjustor.getLabel(whoToAdjust), MemberVisibilityAdjustor.getLabel(neededVisibility) }), JavaStatusContext
.create(whoToAdjust))));
}
private RefactoringStatus initialize(JavaRefactoringArguments arguments) {
String handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.METHOD)
return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_INDIRECTION);
else
fTargetMethod= (IMethod) element;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_INPUT));
handle= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1);
if (handle != null) {
final IJavaElement element= JavaRefactoringDescriptorUtil.handleToElement(arguments.getProject(), handle, false);
if (element == null || !element.exists() || element.getElementType() != IJavaElement.TYPE)
return JavaRefactoringDescriptorUtil.createInputFatalStatus(element, getName(), IJavaRefactorings.INTRODUCE_INDIRECTION);
else
fIntermediaryClass= (IType) element;
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_ELEMENT + 1));
final String references= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES);
if (references != null) {
fUpdateReferences= Boolean.valueOf(references).booleanValue();
} else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_REFERENCES));
final String name= arguments.getAttribute(JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME);
if (name != null && !"".equals(name)) //$NON-NLS-1$
return setIntermediaryMethodName(name);
else
return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JavaRefactoringDescriptorUtil.ATTRIBUTE_NAME));
}
}