content: /*******************************************************************************
   * Copyright (c) 2000, 2009 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.wst.jsdt.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.resources.IFile;
  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.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
  import org.eclipse.wst.jsdt.core.Flags;
  import org.eclipse.wst.jsdt.core.IClassFile;
  import org.eclipse.wst.jsdt.core.IFunction;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IMember;
  import org.eclipse.wst.jsdt.core.IPackageFragment;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.ITypeHierarchy;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.dom.AST;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ASTParser;
  import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration;
  import org.eclipse.wst.jsdt.core.dom.AnonymousClassDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Block;
  import org.eclipse.wst.jsdt.core.dom.ChildListPropertyDescriptor;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.ExpressionStatement;
  import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
  import org.eclipse.wst.jsdt.core.dom.FunctionInvocation;
  import org.eclipse.wst.jsdt.core.dom.IBinding;
  import org.eclipse.wst.jsdt.core.dom.IFunctionBinding;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.JSdoc;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.Name;
  import org.eclipse.wst.jsdt.core.dom.PrimitiveType;
  import org.eclipse.wst.jsdt.core.dom.ReturnStatement;
  import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Statement;
  import org.eclipse.wst.jsdt.core.dom.SuperMethodInvocation;
  import org.eclipse.wst.jsdt.core.dom.ThisExpression;
  import org.eclipse.wst.jsdt.core.dom.Type;
  import org.eclipse.wst.jsdt.core.dom.Modifier.ModifierKeyword;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ListRewrite;
  import org.eclipse.wst.jsdt.core.refactoring.IJavaScriptRefactorings;
  import org.eclipse.wst.jsdt.core.refactoring.descriptors.JavaScriptRefactoringDescriptor;
  import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchConstants;
  import org.eclipse.wst.jsdt.core.search.IJavaScriptSearchScope;
  import org.eclipse.wst.jsdt.core.search.SearchMatch;
  import org.eclipse.wst.jsdt.core.search.SearchPattern;
  import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodeFactory;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes;
  import org.eclipse.wst.jsdt.internal.corext.dom.Bindings;
  import org.eclipse.wst.jsdt.internal.corext.dom.NodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JavaRefactoringArguments;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringAvailabilityTester;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringScopeFactory;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringSearchEngine;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.SearchResultGroup;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.base.JavaStatusContext;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.CompilationUnitChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.rename.RippleMethodFinder2;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.ASTNodeSearchUtil;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.MemberVisibilityAdjustor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.ResourceUtil;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.TextChangeManager;
  import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil;
  import org.eclipse.wst.jsdt.internal.corext.util.JdtFlags;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.corext.util.MethodOverrideTester;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.ui.CodeGeneration;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  /**
   * 
   * 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.
   * 
   * 
   * 
   */
  public class IntroduceIndirectionRefactoring extends ScriptableRefactoring {
  
  	/**
  	 * The compilation unit in which the user invoked this refactoring (if any)
  	 */
  	private IJavaScriptUnit 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 FunctionInvocation (if any). This field is used
  	 * to update this particular invocation in non-reference mode.
  	 */
  	private FunctionInvocation 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 IFunction fTargetMethod;
  	/**
  	 * The binding of the originally selected target method
  	 */
  	private IFunctionBinding 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/* <IJavaScriptUnit,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, IVisibilityAdjustment*/ fIntermediaryAdjustments;
  
  
  	private class NoOverrideProgressMonitor extends SubProgressMonitor {
  
  		public NoOverrideProgressMonitor(IProgressMonitor monitor, int ticks) {
  			super(monitor, ticks, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL);
  		}
  
  		public void setTaskName(String name) {
  			// do nothing
  		}
  	}
  
  	// ********* CONSTRUCTORS AND CLASS CREATION ************
  
  	public IntroduceIndirectionRefactoring(IJavaScriptUnit 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(IFunction method) {
  		fTargetMethod= method;
  		initialize(0, 0);
  	}
  
  	private void initialize(int offset, int length) {
  		fSelectionStart= offset;
  		fSelectionLength= length;
  		fUpdateReferences= true;
  	}
  
  	// ********* UI INTERACTION AND STARTUP OPTIONS ************
  
  	public String getName() {
  		return RefactoringCoreMessages.IntroduceIndirectionRefactoring_introduce_indirection_name;
  	}
  
  	public IJavaScriptProject getProject() {
  		if (fSelectionCompilationUnit != null)
  			return fSelectionCompilationUnit.getJavaScriptProject();
  		if (fSelectionClassFile != null)
  			return fSelectionClassFile.getJavaScriptProject();
  		if (fTargetMethod != null)
  			return fTargetMethod.getJavaScriptProject();
  		return null;
  	}
  
  	public IPackageFragment getInvocationPackage() {
  		return fSelectionCompilationUnit != null ? (IPackageFragment) fSelectionCompilationUnit.getAncestor(IJavaScriptElement.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;
  		RefactoringStatus stat= Checks.checkMethodName(newMethodName);
  		stat.merge(checkOverloading());
  		return stat;
  	}
  
  	private RefactoringStatus checkOverloading() {
  		try {
  			if (fIntermediaryClass != null) {
  				IFunction[] toCheck= fIntermediaryClass.getFunctions();
  				for (int i= 0; i < toCheck.length; i++) {
  					IFunction method= toCheck[i];
  					if (method.getElementName().equals(fIntermediaryMethodName))
  						return RefactoringStatus.createWarningStatus(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_duplicate_method_name_in_declaring_class_error,
  								fIntermediaryMethodName));
  				}
  			}
  		} catch (JavaScriptModelException e) {
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_could_not_parse_declaring_class_error);
  		}
  		return new RefactoringStatus();
  	}
  
  	public String getIntermediaryMethodName() {
  		return fIntermediaryMethodName;
  	}
  
  	/**
  	 * @param fullyQualifiedTypeName
  	 * @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, fullyQualifiedTypeName));
  		} catch (JavaScriptModelException 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 ? JavaModelUtil.getFullyQualifiedName(fIntermediaryClass) : ""; //$NON-NLS-1$
  	}
  
  	// ********** CONDITION CHECKING **********
  
  	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
  		try {
  			pm.beginTask(RefactoringCoreMessages.IntroduceIndirectionRefactoring_checking_activation, 1);
  			fRewrites= new HashMap();
  
  			// This refactoring has been invoked on 
  			// (1) a TextSelection inside an IJavaScriptUnit or inside an IClassFile (definitely with source), or 
  			// (2) an IFunction inside a IJavaScriptUnit 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.
  				JavaScriptUnit 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(AST.JLS3);
  					parser.setResolveBindings(true);
  					parser.setSource(fSelectionClassFile);
  					selectionCURoot= (JavaScriptUnit) parser.createAST(null);
  					selectionNode= getSelectedNode(null, selectionCURoot, fSelectionStart, fSelectionLength);
  				}
  
  				if (selectionNode == null)
  					return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_this_selection);
  
  				IFunctionBinding targetMethodBinding= null;
  
  				if (selectionNode.getNodeType() == ASTNode.FUNCTION_INVOCATION) {
  					targetMethodBinding= ((FunctionInvocation) selectionNode).resolveMethodBinding();
  				} else if (selectionNode.getNodeType() == ASTNode.FUNCTION_DECLARATION) {
  					targetMethodBinding= ((FunctionDeclaration) 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= (IFunction) fTargetMethodBinding.getJavaElement();
  
  				//allow single updating mode if an invocation was selected and the invocation can be updated
  				if (selectionNode instanceof FunctionInvocation && fSelectionCompilationUnit != null)
  					fSelectionMethodInvocation= (FunctionInvocation) selectionNode;
  
  			} else {
  				// (2) invoked on an IFunction: Source may not be available
  
  //				if (fTargetMethod.getDeclaringType().isAnnotation())
  //					return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.IntroduceIndirectionRefactoring_not_available_on_annotation);
  
  				if (fTargetMethod.getJavaScriptUnit() != null) {
  					// source method
  					JavaScriptUnit selectionCURoot= getCachedCURewrite(fTargetMethod.getJavaScriptUnit()).getRoot();
  					FunctionDeclaration 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(AST.JLS3);
  					parser.setProject(fTargetMethod.getJavaScriptProject());
  					IBinding[] bindings= parser.createBindings(new IJavaScriptElement[] { fTargetMethod }, null);
  					fTargetMethodBinding= ((IFunctionBinding) 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();
  		}
  	}
  
  	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException {
  
  		RefactoringStatus result= new RefactoringStatus();
  		fTextChangeManager= new TextChangeManager();
  		fIntermediaryFirstParameterType= null;
  		fIntermediaryClassBinding= null;
  		for (Iterator iter= fRewrites.values().iterator(); iter.hasNext();)
  			((CompilationUnitRewrite) 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));
  		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.getJavaScriptUnit());
  		fIntermediaryClassBinding= typeToBinding(fIntermediaryClass, imRewrite.getRoot());
  
  		fAdjustor= new MemberVisibilityAdjustor(fIntermediaryClass, fIntermediaryClass);
  		fIntermediaryAdjustments= new HashMap();
  		
  		// 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()==null || !fTargetMethod.getDeclaringType().equals(actualTargetType)) {
  			IFunction 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.getJavaScriptUnit());
  
  		result.merge(Checks.validateModifiesFiles(getAllFilesToModify(), getValidationContext()));
  		pm.done();
  
  		return result;
  	}
  
  	private RefactoringStatus updateTargetVisibility(IProgressMonitor monitor) throws JavaScriptModelException, 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
  
  			if (fTargetMethod.getDeclaringType()!=null) {
  				// 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++) {
  					IFunction 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 JavaScriptModelException {
  		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();
  
  		IFunction[] 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 FunctionInvocation, "Element of call should be a FunctionInvocation."); //$NON-NLS-1$
  
  				FunctionInvocation invocation= (FunctionInvocation) 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) throws JavaScriptModelException {
  
  		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= fIntermediaryFirstParameterType.getQualifiedName();
  			String type2= current.getQualifiedName();
  			status.addFatalError(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_open_hierarchy_error, new String[] { type1, type2 }));
  		}
  
  		return status;
  	}
  
  	// ******************** CHANGE CREATION ***********************
  
  	public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
  		final Map arguments= new HashMap();
  		String project= null;
  		IJavaScriptProject javaProject= fTargetMethod.getJavaScriptProject();
  		if (javaProject != null)
  			project= javaProject.getElementName();
  		int flags= JavaScriptRefactoringDescriptor.JAR_MIGRATION | JavaScriptRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE;
  		final IType declaring= fTargetMethod.getDeclaringType();
  		try {
  			if (declaring.isLocal() || declaring.isAnonymous())
  				flags|= JavaScriptRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
  		} catch (JavaScriptModelException exception) {
  			JavaScriptPlugin.log(exception);
  		}
  		final String description= Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_descriptor_description_short, fTargetMethod.getElementName());
  		final String header= Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_descriptor_description, new String[] { JavaScriptElementLabels.getTextLabel(fTargetMethod, JavaScriptElementLabels.ALL_FULLY_QUALIFIED), JavaScriptElementLabels.getTextLabel(declaring, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)});
  		final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
  		comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_original_pattern, JavaScriptElementLabels.getTextLabel(fTargetMethod, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)));
  		comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_method_pattern, fIntermediaryMethodName));
  		comment.addSetting(Messages.format(RefactoringCoreMessages.IntroduceIndirectionRefactoring_declaring_pattern, JavaScriptElementLabels.getTextLabel(fIntermediaryClass, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)));
  		if (fUpdateReferences)
  			comment.addSetting(RefactoringCoreMessages.JavaRefactoringDescriptor_update_references);
  		final JDTRefactoringDescriptor descriptor= new JDTRefactoringDescriptor(IJavaScriptRefactorings.INTRODUCE_INDIRECTION, project, description, comment.asString(), arguments, flags);
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fTargetMethod));
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_NAME, fIntermediaryMethodName);
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1, descriptor.elementToHandle(fIntermediaryClass));
  		arguments.put(JDTRefactoringDescriptor.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 JavaScriptModelException 
  	 */
  	private RefactoringStatus checkCanCreateIntermediaryMethod() throws JavaScriptModelException {
  		// check if method already exists:
  		List parameterBindings= new ArrayList();
  		if (!isStaticTarget())
  			parameterBindings.add(fIntermediaryFirstParameterType);
  		parameterBindings.addAll(Arrays.asList(fTargetMethodBinding.getParameterTypes()));
  		return Checks.checkMethodInType(fIntermediaryClassBinding, fIntermediaryMethodName, (ITypeBinding[]) parameterBindings.toArray(new ITypeBinding[parameterBindings.size()]));
  	}
  
  	private void createIntermediaryMethod() throws CoreException {
  
  		CompilationUnitRewrite imRewrite= getCachedCURewrite(fIntermediaryClass.getJavaScriptUnit());
  		AST ast= imRewrite.getAST();
  		FunctionDeclaration intermediary= ast.newFunctionDeclaration();
  
  		// Name
  		intermediary.setName(ast.newSimpleName(fIntermediaryMethodName));
  
  		// Flags
  		List 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());
  
  		if (!isStaticTarget()) {
  			// Add first param
  			SingleVariableDeclaration parameter= imRewrite.getAST().newSingleVariableDeclaration();
  			Type t= imRewrite.getImportRewrite().addImport(fIntermediaryFirstParameterType, imRewrite.getAST());
  			parameter.setType(t);
  			parameter.setName(imRewrite.getAST().newSimpleName(targetParameterName));
  			intermediary.parameters().add(parameter);
  		}
  		// Add other params
  		copyArguments(intermediary, imRewrite);
  
  		// Return type
  		intermediary.setReturnType2(imRewrite.getImportRewrite().addImport(fTargetMethodBinding.getReturnType(), ast));
  
  		// Body
  		FunctionInvocation invocation= imRewrite.getAST().newFunctionInvocation();
  		invocation.setName(imRewrite.getAST().newSimpleName(fTargetMethod.getElementName()));
  		if (isStaticTarget()) {
  			Type type= imRewrite.getImportRewrite().addImport(fTargetMethodBinding.getDeclaringClass(), ast);
  			invocation.setExpression(ASTNodeFactory.newName(ast, ASTNodes.asString(type)));
  		} 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
  		IJavaScriptUnit targetCU= imRewrite.getCu();
  		if (StubUtility.doAddComments(targetCU.getJavaScriptProject())) {
  			String comment= CodeGeneration.getMethodComment(targetCU, getIntermediaryClassName(), intermediary, null, StubUtility.getLineDelimiterUsed(targetCU));
  			if (comment != null) {
  				JSdoc javadoc= (JSdoc) imRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JSDOC);
  				intermediary.setJavadoc(javadoc);
  			}
  		}
  
  		// Add the completed method to the intermediary type:
  
  		// Intermediary class is non-anonymous
  		AbstractTypeDeclaration type= (AbstractTypeDeclaration) typeToDeclaration(fIntermediaryClass, imRewrite.getRoot());
  		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 Statement encapsulateInvocation(FunctionDeclaration declaration, FunctionInvocation 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(FunctionInvocation invocation, AST ast) throws JavaScriptModelException {
  		String[] names= fTargetMethod.getParameterNames();
  		for (int i= 0; i < names.length; i++)
  			invocation.arguments().add(ast.newSimpleName(names[i]));
  	}
  
  	private void copyArguments(FunctionDeclaration intermediary, CompilationUnitRewrite rew) throws JavaScriptModelException {
  		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);
  		}
  	}
  
  	// ******************* UPDATE CALLS **********************
  
  	private RefactoringStatus updateMethodInvocation(FunctionInvocation originalInvocation, IMember enclosing, CompilationUnitRewrite unitRewriter) throws JavaScriptModelException {
  
  		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);
  
  		FunctionInvocation newInvocation= unitRewriter.getAST().newFunctionInvocation();
  		List newInvocationArgs= newInvocation.arguments();
  		List 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 {
  				ASTNode expressionAsParam= 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= (Expression) originalInvocationArgs.get(i);
  			ASTNode movedArg= 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 
  	 * @param originalInvocation 
  	 * @param enclosing 
  	 * @param unitRewriter 
  	 * @return resulting status
  	 * 
  	 */
  	private RefactoringStatus qualifyThisExpression(ThisExpression expr, FunctionInvocation originalInvocation, IMember enclosing, CompilationUnitRewrite unitRewriter) {
  
  		RefactoringStatus status= new RefactoringStatus();
  
  		IFunctionBinding methodBinding= originalInvocation.resolveMethodBinding();
  		FunctionDeclaration methodDeclaration= (FunctionDeclaration) 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;
  		}
  
  		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 IFunction inside a binding hierarchy
  	 */
  	private IFunctionBinding findMethodBindingInHierarchy(ITypeBinding currentTypeBinding, IFunction methodDeclaration) {
  		IFunctionBinding[] 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) {
  			IFunctionBinding b= findMethodBindingInHierarchy(superClass, 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 result= new ArrayList();
  		collectSuperTypes(type, result);
  		return (ITypeBinding[]) result.toArray(new ITypeBinding[result.size()]);
  	}
  
  	private void collectSuperTypes(ITypeBinding curr, List list) {
  		if (list.add(curr.getTypeDeclaration())) {
  			ITypeBinding superClass= curr.getSuperclass();
  			if (superClass != null) {
  				collectSuperTypes(superClass, list);
  			}
  		}
  	}
  
  	private CompilationUnitRewrite getCachedCURewrite(IJavaScriptUnit unit) {
  		CompilationUnitRewrite rewrite= (CompilationUnitRewrite) fRewrites.get(unit);
  		if (rewrite == null) {
  			rewrite= new CompilationUnitRewrite(unit);
  			fRewrites.put(unit, rewrite);
  		}
  		return rewrite;
  	}
  
  	private boolean isRewriteKept(IJavaScriptUnit compilationUnit) {
  		return fIntermediaryClass.getJavaScriptUnit().equals(compilationUnit);
  	}
  
  	private void createChangeAndDiscardRewrite(IJavaScriptUnit compilationUnit) throws CoreException {
  		CompilationUnitRewrite rewrite= (CompilationUnitRewrite) fRewrites.get(compilationUnit);
  		if (rewrite != null) {
  			fTextChangeManager.manage(compilationUnit, rewrite.createChange());
  			fRewrites.remove(compilationUnit);
  		}
  	}
  
  	private SearchResultGroup[] getReferences(IFunction[] methods, IProgressMonitor pm, RefactoringStatus status) throws CoreException {
  		SearchPattern pattern= RefactoringSearchEngine.createOrPattern(methods, IJavaScriptSearchConstants.REFERENCES);
  		IJavaScriptSearchScope scope= RefactoringScopeFactory.create(fIntermediaryClass, false);
  		return RefactoringSearchEngine.search(pattern, scope, pm, status);
  	}
  
  	private ITypeBinding typeToBinding(IType type, JavaScriptUnit root) throws JavaScriptModelException {
  		ASTNode typeNode= typeToDeclaration(type, root);
  		if (type.isAnonymous()) {
  			return ((AnonymousClassDeclaration) typeNode).resolveBinding();
  		} else {
  			return ((AbstractTypeDeclaration) typeNode).resolveBinding();
  		}
  	}
  
  	private ASTNode typeToDeclaration(IType type, JavaScriptUnit root) throws JavaScriptModelException {
  		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, JavaScriptUnit root) throws JavaScriptModelException {
  		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= JavaScriptElementLabels.getElementLabel(enclosing, JavaScriptElementLabels.ALL_DEFAULT);
  		String container= JavaScriptElementLabels.getElementLabel(enclosing.getDeclaringType(), JavaScriptElementLabels.ALL_FULLY_QUALIFIED);
  		return RefactoringStatus.createWarningStatus(Messages.format(message, new String[] { name, container }), JavaStatusContext.create(enclosing.getJavaScriptUnit(), concreteNode));
  	}
  
  	private ITypeBinding getExpressionType(FunctionInvocation 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 cus= new ArrayList();
  		cus.addAll(Arrays.asList(fTextChangeManager.getAllCompilationUnits()));
  		return ResourceUtil.getFiles((IJavaScriptUnit[]) cus.toArray(new IJavaScriptUnit[cus.size()]));
  	}
  
  	private boolean isStaticTarget() throws JavaScriptModelException {
  		return Flags.isStatic(fTargetMethod.getFlags());
  	}
  
  	private IMember getEnclosingInitialSelectionMember() throws JavaScriptModelException {
  		return (IMember) fSelectionCompilationUnit.getElementAt(fSelectionStart);
  	}
  
  	private static ASTNode getSelectedNode(IJavaScriptUnit unit, JavaScriptUnit 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 (JavaScriptModelException 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.FUNCTION_INVOCATION:
  			case ASTNode.FUNCTION_DECLARATION:
  			case ASTNode.SUPER_METHOD_INVOCATION:
  				return node;
  		}
  		return null;
  	}
  
  	// ***************** VISIBILITY ********************
  
  	private ModifierKeyword getNeededVisibility(IMember whoToAdjust, IMember fromWhereToLook) throws JavaScriptModelException {
  		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 adjustments;
  		if (isRewriteKept(whoToAdjust.getJavaScriptUnit()))
  			adjustments= fIntermediaryAdjustments;
  		else
  			adjustments= new HashMap();
  		
  		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[] { JavaScriptElementLabels
  					.getElementLabel(whoToAdjust, JavaScriptElementLabels.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 rewrites;
  			if (!isRewriteKept(whoToAdjust.getJavaScriptUnit())) {
  				CompilationUnitRewrite rewrite= new CompilationUnitRewrite(whoToAdjust.getJavaScriptUnit());
  				rewrite.setResolveBindings(false);
  				rewrites= new HashMap();
  				rewrites.put(whoToAdjust.getJavaScriptUnit(), rewrite);
  				status.merge(rewriteVisibility(adjustments, rewrites, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL)));
  				rewrite.attachChange((CompilationUnitChange) fTextChangeManager.get(whoToAdjust.getJavaScriptUnit()), true, new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
  			}
  		} finally {
  			monitor.done();
  		}
  		return status;
  	}
  
  	private RefactoringStatus rewriteVisibility(Map adjustments, Map rewrites, IProgressMonitor monitor) throws JavaScriptModelException {
  		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 adjustments) throws JavaScriptModelException {
  		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))));
  	}
  
  	public RefactoringStatus initialize(final RefactoringArguments arguments) {
  		if (arguments instanceof JavaRefactoringArguments) {
  			final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
  			String handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_INPUT);
  			if (handle != null) {
  				final IJavaScriptElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
  				if (element == null || !element.exists() || element.getElementType() != IJavaScriptElement.METHOD)
  					return createInputFatalStatus(element, IJavaScriptRefactorings.INTRODUCE_INDIRECTION);
  				else
  					fTargetMethod= (IFunction) element;
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_INPUT));
  			handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1);
  			if (handle != null) {
  				final IJavaScriptElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
  				if (element == null || !element.exists() || element.getElementType() != IJavaScriptElement.TYPE)
  					return createInputFatalStatus(element, IJavaScriptRefactorings.INTRODUCE_INDIRECTION);
  				else
  					fIntermediaryClass= (IType) element;
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + 1));
  			final String references= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_REFERENCES);
  			if (references != null) {
  				fUpdateReferences= Boolean.valueOf(references).booleanValue();
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_REFERENCES));
  			final String name= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_NAME);
  			if (name != null && !"".equals(name)) //$NON-NLS-1$
  				return setIntermediaryMethodName(name);
  			else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_NAME));
  		} else
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
  	}
  }
  