/*******************************************************************************
 * Copyright (c) 2000, 2007 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.structure;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;

import org.eclipse.text.edits.TextEditGroup;

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.RefactoringDescriptor;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusContext;
import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;

import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeHierarchy;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.IProblem;
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.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Javadoc;
import org.eclipse.jdt.core.dom.MemberRef;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodRef;
import org.eclipse.jdt.core.dom.MethodRefParameter;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.TagElement;
import org.eclipse.jdt.core.dom.TextElement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.core.refactoring.IJavaRefactorings;
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.SearchPattern;

import org.eclipse.jdt.internal.corext.Corext;
import org.eclipse.jdt.internal.corext.SourceRange;
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.dom.ModifierRewrite;
import org.eclipse.jdt.internal.corext.dom.NodeFinder;
import org.eclipse.jdt.internal.corext.dom.Selection;
import org.eclipse.jdt.internal.corext.dom.SelectionAnalyzer;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.ExceptionInfo;
import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptor;
import org.eclipse.jdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
import org.eclipse.jdt.internal.corext.refactoring.JavaRefactoringArguments;
import org.eclipse.jdt.internal.corext.refactoring.ParameterInfo;
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.ReturnTypeInfo;
import org.eclipse.jdt.internal.corext.refactoring.SearchResultGroup;
import org.eclipse.jdt.internal.corext.refactoring.StubTypeContext;
import org.eclipse.jdt.internal.corext.refactoring.TypeContextChecker;
import org.eclipse.jdt.internal.corext.refactoring.TypeContextChecker.IProblemVerifier;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.base.JavaStringStatusContext;
import org.eclipse.jdt.internal.corext.refactoring.base.RefactoringStatusCodes;
import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange;
import org.eclipse.jdt.internal.corext.refactoring.code.ScriptableRefactoring;
import org.eclipse.jdt.internal.corext.refactoring.delegates.DelegateMethodCreator;
import org.eclipse.jdt.internal.corext.refactoring.rename.MethodChecks;
import org.eclipse.jdt.internal.corext.refactoring.rename.RefactoringAnalyzeUtil;
import org.eclipse.jdt.internal.corext.refactoring.rename.RippleMethodFinder2;
import org.eclipse.jdt.internal.corext.refactoring.rename.TempOccurrenceAnalyzer;
import org.eclipse.jdt.internal.corext.refactoring.tagging.IDelegateUpdating;
import org.eclipse.jdt.internal.corext.refactoring.util.JavaElementUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.JavadocUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager;
import org.eclipse.jdt.internal.corext.refactoring.util.TightSourceRangeComputer;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.internal.corext.util.JdtFlags;
import org.eclipse.jdt.internal.corext.util.Messages;

import org.eclipse.jdt.ui.JavaElementLabels;

import org.eclipse.jdt.internal.ui.JavaPlugin;

public class ChangeSignatureRefactoring extends ScriptableRefactoring implements IDelegateUpdating {
	
	private static final String ATTRIBUTE_RETURN= "return"; //$NON-NLS-1$
	private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$
	private static final String ATTRIBUTE_PARAMETER= "parameter"; //$NON-NLS-1$
	private static final String ATTRIBUTE_DEFAULT= "default"; //$NON-NLS-1$
	private static final String ATTRIBUTE_KIND= "kind"; //$NON-NLS-1$
	private static final String ATTRIBUTE_DELEGATE= "delegate"; //$NON-NLS-1$
	private static final String ATTRIBUTE_DEPRECATE= "deprecate"; //$NON-NLS-1$
	
	private List fParameterInfos;

	private CompilationUnitRewrite fBaseCuRewrite;
	private List fExceptionInfos;
	protected TextChangeManager fChangeManager;
	protected List/*<Change>*/ fOtherChanges;
	private IMethod fMethod;
	private IMethod fTopMethod;
	private IMethod[] fRippleMethods;
	private SearchResultGroup[] fOccurrences;
	private ReturnTypeInfo fReturnTypeInfo;
	private String fMethodName;
	private int fVisibility;
	private static final String CONST_CLASS_DECL = "class A{";//$NON-NLS-1$
	private static final String CONST_ASSIGN = " i=";		//$NON-NLS-1$
	private static final String CONST_CLOSE = ";}";			//$NON-NLS-1$

	private StubTypeContext fContextCuStartEnd;
	private int fOldVarargIndex; // initialized in checkVarargs()

	private BodyUpdater fBodyUpdater;
	private IDefaultValueAdvisor fDefaultValueAdvisor;

	private ITypeHierarchy fCachedTypeHierarchy= null;
	private boolean fDelegateUpdating;
	private boolean fDelegateDeprecation;

	/**
	 * Creates a new change signature refactoring.
	 * @param method the method, or <code>null</code> if invoked by scripting framework
	 * @throws JavaModelException
	 */
	public ChangeSignatureRefactoring(IMethod method) throws JavaModelException {
		fMethod= method;
		fOldVarargIndex= -1;
		fDelegateUpdating= false;
		fDelegateDeprecation= true;
		if (fMethod != null) {
			fParameterInfos= createParameterInfoList(method);
			// fExceptionInfos is created in checkInitialConditions
			fReturnTypeInfo= new ReturnTypeInfo(Signature.toString(Signature.getReturnType(fMethod.getSignature())));
			fMethodName= fMethod.getElementName();
			fVisibility= JdtFlags.getVisibilityCode(fMethod);
		}
	}

	private static List createParameterInfoList(IMethod method) {
		try {
			String[] typeNames= method.getParameterTypes();
			String[] oldNames= method.getParameterNames();
			List result= new ArrayList(typeNames.length);
			for (int i= 0; i < oldNames.length; i++){
				ParameterInfo parameterInfo;
				if (i == oldNames.length - 1 && Flags.isVarargs(method.getFlags())) {
					String varargSignature= typeNames[i];
					int arrayCount= Signature.getArrayCount(varargSignature);
					String baseSignature= Signature.getElementType(varargSignature);
					if (arrayCount > 1)
						baseSignature= Signature.createArraySignature(baseSignature, arrayCount - 1);
					parameterInfo= new ParameterInfo(Signature.toString(baseSignature) + ParameterInfo.ELLIPSIS, oldNames[i], i);
				} else {
					parameterInfo= new ParameterInfo(Signature.toString(typeNames[i]), oldNames[i], i);
				}
				result.add(parameterInfo);
			}
			return result;
		} catch(JavaModelException e) {
			JavaPlugin.log(e);
			return new ArrayList(0);
		}		
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.refactoring.base.IRefactoring#getName()
	 */
	public String getName() {
		return RefactoringCoreMessages.ChangeSignatureRefactoring_modify_Parameters; 
	}
	
	public IMethod getMethod() {
		return fMethod;
	}
	
	public String getMethodName() {
		return fMethodName;
	}
	
	public String getReturnTypeString() {
		return fReturnTypeInfo.getNewTypeName();
	}	

	public void setNewMethodName(String newMethodName){
		Assert.isNotNull(newMethodName);
		fMethodName= newMethodName;
	}
	
	public void setNewReturnTypeName(String newReturnTypeName){
		Assert.isNotNull(newReturnTypeName);
		fReturnTypeInfo.setNewTypeName(newReturnTypeName);
	}
	
	public boolean canChangeNameAndReturnType(){
		try {
			return ! fMethod.isConstructor();
		} catch (JavaModelException e) {
			JavaPlugin.log(e);
			return false;
		}
	}
	
	/**
	 * @return visibility
	 * @see org.eclipse.jdt.core.dom.Modifier
	 */
	public int getVisibility(){
		return fVisibility;
	}

	/**
	 * @param visibility new visibility
	 * @see org.eclipse.jdt.core.dom.Modifier
	 */	
	public void setVisibility(int visibility){
		Assert.isTrue(	visibility == Modifier.PUBLIC ||
		            	visibility == Modifier.PROTECTED ||
		            	visibility == Modifier.NONE ||
		            	visibility == Modifier.PRIVATE);  
		fVisibility= visibility;            	
	}
	
	/*
	 * @see JdtFlags
	 */	
	public int[] getAvailableVisibilities() throws JavaModelException{
		if (fTopMethod.getDeclaringType().isInterface())
			return new int[]{Modifier.PUBLIC};
		else if (fTopMethod.getDeclaringType().isEnum() && fTopMethod.isConstructor())
			return new int[]{	Modifier.NONE,
								Modifier.PRIVATE};
		else
			return new int[]{	Modifier.PUBLIC,
								Modifier.PROTECTED,
								Modifier.NONE,
								Modifier.PRIVATE};
	}
	
	/**
	 * 
	 * @return List of <code>ParameterInfo</code> objects.
	 */
	public List getParameterInfos(){
		return fParameterInfos;
	}
	
	/**
	 * @return List of <code>ExceptionInfo</code> objects.
	 */
	public List getExceptionInfos(){
		return fExceptionInfos;
	}
	
	public void setBodyUpdater(BodyUpdater bodyUpdater) {
		fBodyUpdater= bodyUpdater;
	}
	
	public CompilationUnitRewrite getBaseCuRewrite() {
		return fBaseCuRewrite;
	}
	
	//------------------- IDelegateUpdating ----------------------
	
	public boolean canEnableDelegateUpdating() {
		return true;
	}

	public boolean getDelegateUpdating() {
		return fDelegateUpdating;
	}

	public void setDelegateUpdating(boolean updating) {
		fDelegateUpdating= updating;
	}

	public void setDeprecateDelegates(boolean deprecate) {
		fDelegateDeprecation= deprecate;
	}

	public boolean getDeprecateDelegates() {
		return fDelegateDeprecation;
	}

	//------------------- /IDelegateUpdating ---------------------
	
	public RefactoringStatus checkSignature() {
		return checkSignature(false, doGetProblemVerifier());
	}
	
	private RefactoringStatus checkSignature(boolean resolveBindings, IProblemVerifier problemVerifier) {
		RefactoringStatus result= new RefactoringStatus();
		checkMethodName(result);
		if (result.hasFatalError())
			return result;
		
		checkParameterNamesAndValues(result);
		if (result.hasFatalError())
			return result;
		
		checkForDuplicateParameterNames(result);
		if (result.hasFatalError())
			return result;
		
		try {
			RefactoringStatus[] typeStati;
			if (resolveBindings)
				typeStati= TypeContextChecker.checkAndResolveMethodTypes(fMethod, getStubTypeContext(), getNotDeletedInfos(), fReturnTypeInfo, problemVerifier);
			else
				typeStati= TypeContextChecker.checkMethodTypesSyntax(fMethod, getNotDeletedInfos(), fReturnTypeInfo);
			for (int i= 0; i < typeStati.length; i++)
				result.merge(typeStati[i]);
			
			result.merge(checkVarargs());
		} catch (CoreException e) {
			//cannot do anything here
			throw new RuntimeException(e);
		}
		
		//checkExceptions() unnecessary (IType always ok)
		return result;
	}
    
	public boolean isSignatureSameAsInitial() throws JavaModelException {
		if (! isVisibilitySameAsInitial())
			return false;
		if (! isMethodNameSameAsInitial())
			return false;
		if (! isReturnTypeSameAsInitial())
			return false;
		if (! areExceptionsSameAsInitial())
			return false;
		
		if (fMethod.getNumberOfParameters() == 0 && fParameterInfos.isEmpty())
			return true;
		
		if (areNamesSameAsInitial() && isOrderSameAsInitial() && areParameterTypesSameAsInitial())
			return true;
		
		return false;
	}
	
	/**
	 * @return true if the new method cannot coexist with the old method since
	 *         the signatures are too much alike
	 * @throws JavaModelException
	 */
	public boolean isSignatureClashWithInitial() throws JavaModelException {

		if (!isMethodNameSameAsInitial())
			return false; // name has changed.

		if (fMethod.getNumberOfParameters() == 0 && fParameterInfos.isEmpty())
			return true; // name is equal and both parameter lists are empty

		// name is equal and there are some parameters.
		// check if there are more or less parameters than before

		int no= getNotDeletedInfos().size();

		if (fMethod.getNumberOfParameters() != no)
			return false;

		// name is equal and parameter count is equal.
		// check whether types remained the same
		
		if (isOrderSameAsInitial())
			return areParameterTypesSameAsInitial();
		else
			return false; // could be more specific here
	}

	private boolean areParameterTypesSameAsInitial() {
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (! info.isAdded() && ! info.isDeleted() && info.isTypeNameChanged())
				return false;
		}
		return true;
	}
	
	private boolean isReturnTypeSameAsInitial() throws JavaModelException {
		return ! fReturnTypeInfo.isTypeNameChanged();
	}
	
	private boolean isMethodNameSameAsInitial() {
		return fMethodName.equals(fMethod.getElementName());
	}
	
	private boolean areExceptionsSameAsInitial() {
		for (Iterator iter= fExceptionInfos.iterator(); iter.hasNext();) {
			ExceptionInfo info= (ExceptionInfo) iter.next();
			if (! info.isOld())
				return false;
		}
		return true;
	}
	
	private void checkParameterNamesAndValues(RefactoringStatus result) {
		int i= 1;
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext(); i++) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (info.isDeleted())
				continue;
			checkParameterName(result, info, i);
			if (result.hasFatalError())
				return;
			if (info.isAdded())	{
				checkParameterDefaultValue(result, info);
				if (result.hasFatalError())
					return;
			}
		}
	}
	
	private void checkParameterName(RefactoringStatus result, ParameterInfo info, int position) {
		if (info.getNewName().trim().length() == 0) {
			result.addFatalError(Messages.format(
					RefactoringCoreMessages.ChangeSignatureRefactoring_param_name_not_empty, Integer.toString(position))); 
		} else {
			result.merge(Checks.checkTempName(info.getNewName()));
		}
	}

	private void checkMethodName(RefactoringStatus result) {
		if (isMethodNameSameAsInitial() || ! canChangeNameAndReturnType())
			return;
		if ("".equals(fMethodName.trim())) { //$NON-NLS-1$
			String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_method_name_not_empty; 
			result.addFatalError(msg);
			return;
		}
		if (fMethodName.equals(fMethod.getDeclaringType().getElementName())) {
			String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_constructor_name; 
			result.addWarning(msg);
		}
		result.merge(Checks.checkMethodName(fMethodName));
	}

	private void checkParameterDefaultValue(RefactoringStatus result, ParameterInfo info) {
		if (fDefaultValueAdvisor != null)
			return;
		if (info.isNewVarargs()) {
			if (! isValidVarargsExpression(info.getDefaultValue())){
				String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_invalid_expression, new String[]{info.getDefaultValue()}); 
				result.addFatalError(msg);
			}	
			return;
		}
		
		if (info.getDefaultValue().trim().equals("")){ //$NON-NLS-1$
			String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_default_value, new String[]{info.getNewName()}); 
			result.addFatalError(msg);
			return;
		}	
		if (! isValidExpression(info.getDefaultValue())){
			String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_invalid_expression, new String[]{info.getDefaultValue()}); 
			result.addFatalError(msg);
		}	
	}
	
	private RefactoringStatus checkVarargs() throws JavaModelException {
		RefactoringStatus result= checkOriginalVarargs();
		if (result != null)
			return result;
			
		if (fRippleMethods != null) {
			for (int iRipple= 0; iRipple < fRippleMethods.length; iRipple++) {
				IMethod rippleMethod= fRippleMethods[iRipple];
				if (! JdtFlags.isVarargs(rippleMethod))
					continue;
				
				// Vararg method can override method that takes an array as last argument
				fOldVarargIndex= rippleMethod.getNumberOfParameters() - 1;
				List notDeletedInfos= getNotDeletedInfos();
				for (int i= 0; i < notDeletedInfos.size(); i++) {
					ParameterInfo info= (ParameterInfo) notDeletedInfos.get(i);
					if (fOldVarargIndex != -1 && info.getOldIndex() == fOldVarargIndex && ! info.isNewVarargs()) {
						String rippleMethodType= JavaModelUtil.getFullyQualifiedName(rippleMethod.getDeclaringType());
						String message= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_ripple_cannot_convert_vararg, new Object[] {info.getNewName(), rippleMethodType}); 
						return RefactoringStatus.createFatalErrorStatus(message, JavaStatusContext.create(rippleMethod));
					}
				}
			}
		}
		
		return null;
	}

	private RefactoringStatus checkOriginalVarargs() throws JavaModelException {
		if (JdtFlags.isVarargs(fMethod))
			fOldVarargIndex= fMethod.getNumberOfParameters() - 1;
		List notDeletedInfos= getNotDeletedInfos();
		for (int i= 0; i < notDeletedInfos.size(); i++) {
			ParameterInfo info= (ParameterInfo) notDeletedInfos.get(i);
			if (info.isOldVarargs() && ! info.isNewVarargs())
				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_cannot_convert_vararg, info.getNewName())); 
			if (i != notDeletedInfos.size() - 1) {
				// not the last parameter
				if (info.isNewVarargs())
					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_vararg_must_be_last, info.getNewName())); 
			}
		}
		return null;
	}
	
	private RefactoringStatus checkTypeVariables() throws JavaModelException {
		if (fRippleMethods.length == 1)
			return null;
		
		RefactoringStatus result= new RefactoringStatus();
		if (fReturnTypeInfo.isTypeNameChanged() && fReturnTypeInfo.getNewTypeBinding() != null) {
			HashSet typeVariablesCollector= new HashSet();
			collectTypeVariables(fReturnTypeInfo.getNewTypeBinding(), typeVariablesCollector);
			if (typeVariablesCollector.size() != 0) {
				ITypeBinding first= (ITypeBinding) typeVariablesCollector.iterator().next();
				String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_return_type_contains_type_variable, new String[] {fReturnTypeInfo.getNewTypeName(), first.getName()}); 
				result.addError(msg);
			}
		}
		
		for (Iterator iter= getNotDeletedInfos().iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (info.isTypeNameChanged() && info.getNewTypeBinding() != null) {
				HashSet typeVariablesCollector= new HashSet();
				collectTypeVariables(info.getNewTypeBinding(), typeVariablesCollector);
				if (typeVariablesCollector.size() != 0) {
					ITypeBinding first= (ITypeBinding) typeVariablesCollector.iterator().next();
					String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_parameter_type_contains_type_variable, new String[] {info.getNewTypeName(), info.getNewName(), first.getName()}); 
					result.addError(msg);
				}
			}
		}
		return result;
	}
	
	private void collectTypeVariables(ITypeBinding typeBinding, Set typeVariablesCollector) {
		if (typeBinding.isTypeVariable()) {
			typeVariablesCollector.add(typeBinding);
			ITypeBinding[] typeBounds= typeBinding.getTypeBounds();
			for (int i= 0; i < typeBounds.length; i++)
				collectTypeVariables(typeBounds[i], typeVariablesCollector);
			
		} else if (typeBinding.isArray()) {
			collectTypeVariables(typeBinding.getElementType(), typeVariablesCollector);
			
		} else if (typeBinding.isParameterizedType()) {
			ITypeBinding[] typeArguments= typeBinding.getTypeArguments();
			for (int i= 0; i < typeArguments.length; i++)
				collectTypeVariables(typeArguments[i], typeVariablesCollector);
		
		} else if (typeBinding.isWildcardType()) {
			ITypeBinding bound= typeBinding.getBound();
			if (bound != null) {
				collectTypeVariables(bound, typeVariablesCollector);
			}		
		}
	}
	
	public static boolean isValidExpression(String string){
		String trimmed= string.trim();
		if ("".equals(trimmed)) //speed up for a common case //$NON-NLS-1$
			return false;
		StringBuffer cuBuff= new StringBuffer();
		cuBuff.append(CONST_CLASS_DECL)
			  .append("Object") //$NON-NLS-1$
			  .append(CONST_ASSIGN);
		int offset= cuBuff.length();
		cuBuff.append(trimmed)
			  .append(CONST_CLOSE);
		ASTParser p= ASTParser.newParser(AST.JLS3);
		p.setSource(cuBuff.toString().toCharArray());
		CompilationUnit cu= (CompilationUnit) p.createAST(null);
		Selection selection= Selection.createFromStartLength(offset, trimmed.length());
		SelectionAnalyzer analyzer= new SelectionAnalyzer(selection, false);
		cu.accept(analyzer);
		ASTNode selected= analyzer.getFirstSelectedNode();
		return (selected instanceof Expression) && 
				trimmed.equals(cuBuff.substring(cu.getExtendedStartPosition(selected), cu.getExtendedStartPosition(selected) + cu.getExtendedLength(selected)));
	}
	
	public static boolean isValidVarargsExpression(String string) {
		String trimmed= string.trim();
		if ("".equals(trimmed)) //speed up for a common case //$NON-NLS-1$
			return true;
		StringBuffer cuBuff= new StringBuffer();
		cuBuff.append("class A{ {m("); //$NON-NLS-1$
		int offset= cuBuff.length();
		cuBuff.append(trimmed)
			  .append(");}}"); //$NON-NLS-1$
		ASTParser p= ASTParser.newParser(AST.JLS3);
		p.setSource(cuBuff.toString().toCharArray());
		CompilationUnit cu= (CompilationUnit) p.createAST(null);
		Selection selection= Selection.createFromStartLength(offset, trimmed.length());
		SelectionAnalyzer analyzer= new SelectionAnalyzer(selection, false);
		cu.accept(analyzer);
		ASTNode[] selectedNodes= analyzer.getSelectedNodes();
		if (selectedNodes.length == 0)
			return false;
		for (int i= 0; i < selectedNodes.length; i++) {
			if (! (selectedNodes[i] instanceof Expression))
				return false;
		}
		return true;
	}

	public StubTypeContext getStubTypeContext() {
		try {
			if (fContextCuStartEnd == null)
				fContextCuStartEnd= TypeContextChecker.createStubTypeContext(getCu(), fBaseCuRewrite.getRoot(), fMethod.getSourceRange().getOffset());
		} catch (CoreException e) {
			//cannot do anything here
			throw new RuntimeException(e);
		}
		return fContextCuStartEnd;
	}

	private ITypeHierarchy getCachedTypeHierarchy(IProgressMonitor monitor) throws JavaModelException {
		if (fCachedTypeHierarchy == null)
			fCachedTypeHierarchy= fMethod.getDeclaringType().newTypeHierarchy(new SubProgressMonitor(monitor, 1));
		return fCachedTypeHierarchy;
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkActivation(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public RefactoringStatus checkInitialConditions(IProgressMonitor monitor) throws CoreException {
		try {
			monitor.beginTask("", 5); //$NON-NLS-1$
			RefactoringStatus result= Checks.checkIfCuBroken(fMethod);
			if (result.hasFatalError())
				return result;
			if (fMethod == null || !fMethod.exists()) {
				String message= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_method_deleted, getCu().getElementName());
				return RefactoringStatus.createFatalErrorStatus(message);
			}
			if (fMethod.getDeclaringType().isInterface()) {
				fTopMethod= MethodChecks.overridesAnotherMethod(fMethod, fMethod.getDeclaringType().newSupertypeHierarchy(new SubProgressMonitor(monitor, 1)));
				monitor.worked(1);
			} else if (MethodChecks.isVirtual(fMethod)) {
				ITypeHierarchy hierarchy= getCachedTypeHierarchy(new SubProgressMonitor(monitor, 1));
				fTopMethod= MethodChecks.isDeclaredInInterface(fMethod, hierarchy, new SubProgressMonitor(monitor, 1));
				if (fTopMethod == null)
					fTopMethod= MethodChecks.overridesAnotherMethod(fMethod, hierarchy);
			}
			if (fTopMethod == null)
				fTopMethod= fMethod;
			if (! fTopMethod.equals(fMethod)) {
				if (fTopMethod.getDeclaringType().isInterface()) {
					RefactoringStatusContext context= JavaStatusContext.create(fTopMethod);
					String message= Messages.format(RefactoringCoreMessages.MethodChecks_implements, 
							new String[]{JavaElementUtil.createMethodSignature(fTopMethod), JavaModelUtil.getFullyQualifiedName(fTopMethod.getDeclaringType())});
					return RefactoringStatus.createStatus(RefactoringStatus.FATAL, message, context, Corext.getPluginId(), RefactoringStatusCodes.METHOD_DECLARED_IN_INTERFACE, fTopMethod);
				} else {
					RefactoringStatusContext context= JavaStatusContext.create(fTopMethod);
					String message= Messages.format(RefactoringCoreMessages.MethodChecks_overrides, 
							new String[]{JavaElementUtil.createMethodSignature(fTopMethod), JavaModelUtil.getFullyQualifiedName(fTopMethod.getDeclaringType())});
					return RefactoringStatus.createStatus(RefactoringStatus.FATAL, message, context, Corext.getPluginId(), RefactoringStatusCodes.OVERRIDES_ANOTHER_METHOD, fTopMethod);
				}
			}

			if (monitor.isCanceled())
				throw new OperationCanceledException();

			if (fBaseCuRewrite == null || !fBaseCuRewrite.getCu().equals(getCu())) {
				fBaseCuRewrite= new CompilationUnitRewrite(getCu());
				fBaseCuRewrite.getASTRewrite().setTargetSourceRangeComputer(new TightSourceRangeComputer());
			}
			monitor.worked(1);
			result.merge(createExceptionInfoList());
			monitor.worked(1);
			return result;
		} finally {
			monitor.done();
		}
	}
	
	private RefactoringStatus createExceptionInfoList() {
		if (fExceptionInfos == null || fExceptionInfos.isEmpty()) {
			fExceptionInfos= new ArrayList(0);
			try {
				ASTNode nameNode= NodeFinder.perform(fBaseCuRewrite.getRoot(), fMethod.getNameRange());
				if (nameNode == null || !(nameNode instanceof Name) || !(nameNode.getParent() instanceof MethodDeclaration))
					return null;
				MethodDeclaration methodDeclaration= (MethodDeclaration) nameNode.getParent();
				List exceptions= methodDeclaration.thrownExceptions();
				List result= new ArrayList(exceptions.size());
				for (int i= 0; i < exceptions.size(); i++) {
					Name name= (Name) exceptions.get(i);
					ITypeBinding typeBinding= name.resolveTypeBinding();
					if (typeBinding == null)
						return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_no_exception_binding);
					IType type= (IType) typeBinding.getJavaElement();
					result.add(ExceptionInfo.createInfoForOldException(type, typeBinding));
				}
				fExceptionInfos= result;
			} catch (JavaModelException e) {
				JavaPlugin.log(e);
			}
		}
		return null;
	}

	/*
	 * @see org.eclipse.jdt.internal.corext.refactoring.base.Refactoring#checkInput(org.eclipse.core.runtime.IProgressMonitor)
	 */
	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
		try {
			pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_checking_preconditions, 8); 
			RefactoringStatus result= new RefactoringStatus();
			clearManagers();
			fBaseCuRewrite.clearASTAndImportRewrites();
			fBaseCuRewrite.getASTRewrite().setTargetSourceRangeComputer(new TightSourceRangeComputer());

			if (isSignatureSameAsInitial())
				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_unchanged); 
			result.merge(checkSignature(true, doGetProblemVerifier()));
			if (result.hasFatalError())
				return result;
			
			if (fDelegateUpdating && isSignatureClashWithInitial()) 
				result.merge(RefactoringStatus.createErrorStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_old_and_new_signatures_not_sufficiently_different ));

			fRippleMethods= RippleMethodFinder2.getRelatedMethods(fMethod, new SubProgressMonitor(pm, 1), null);
			result.merge(checkVarargs());
			if (result.hasFatalError())
				return result;
			
			fOccurrences= findOccurrences(new SubProgressMonitor(pm, 1), result);
			
			result.merge(checkVisibilityChanges());
			result.merge(checkTypeVariables());
			
			//TODO:
			// We need a common way of dealing with possible compilation errors for all occurrences,
			// including visibility problems, shadowing and missing throws declarations.
			
			if (! isOrderSameAsInitial())
				result.merge(checkReorderings(new SubProgressMonitor(pm, 1)));
			else
				pm.worked(1);
			
			//TODO (bug 58616): check whether changed signature already exists somewhere in the ripple,
			// - error if exists
			// - warn if exists with different parameter types (may cause overloading)
			
			if (! areNamesSameAsInitial())
				result.merge(checkRenamings(new SubProgressMonitor(pm, 1)));
			else
				pm.worked(1);
			if (result.hasFatalError())
				return result;
			
//			resolveTypesWithoutBindings(new SubProgressMonitor(pm, 1)); // already done in checkSignature(true)

			createChangeManager(new SubProgressMonitor(pm, 1), result);
			fCachedTypeHierarchy= null;

			if (mustAnalyzeAstOfDeclaringCu()) 
				result.merge(checkCompilationofDeclaringCu()); //TODO: should also check in ripple methods (move into createChangeManager)
			if (result.hasFatalError())
				return result;

			result.merge(validateModifiesFiles());
			return result;
		} finally {
			pm.done();
		}
	}

	protected IProblemVerifier doGetProblemVerifier() {
		return null;
	}

	private void clearManagers() {
		fChangeManager= null;
		fOtherChanges= new ArrayList();
	}
	
	private RefactoringStatus checkVisibilityChanges() throws JavaModelException {
		if (isVisibilitySameAsInitial())
			return null;
	    if (fRippleMethods.length == 1)
	    	return null;
	    Assert.isTrue(JdtFlags.getVisibilityCode(fMethod) != Modifier.PRIVATE);
	    if (fVisibility == Modifier.PRIVATE)
	    	return RefactoringStatus.createWarningStatus(RefactoringCoreMessages.ChangeSignatureRefactoring_non_virtual); 
		return null;
	}

	public String getOldMethodSignature() throws JavaModelException{
		StringBuffer buff= new StringBuffer();
		
		int flags= getMethod().getFlags();
		buff.append(getVisibilityString(flags));
		if (Flags.isStatic(flags))
			buff.append("static "); //$NON-NLS-1$
		if (! getMethod().isConstructor())
			buff.append(fReturnTypeInfo.getOldTypeName())
				.append(' ');

		buff.append(JavaElementLabels.getElementLabel(fMethod.getParent(), JavaElementLabels.ALL_FULLY_QUALIFIED));
		buff.append('.');
		buff.append(fMethod.getElementName())
			.append(Signature.C_PARAM_START)
			.append(getOldMethodParameters())
			.append(Signature.C_PARAM_END);
		
		buff.append(getOldMethodThrows());
		
		return buff.toString();
	}

	public String getNewMethodSignature() throws JavaModelException{
		StringBuffer buff= new StringBuffer();
		
		buff.append(getVisibilityString(fVisibility));
		if (Flags.isStatic(getMethod().getFlags()))
			buff.append("static "); //$NON-NLS-1$
		if (! getMethod().isConstructor())
			buff.append(getReturnTypeString())
				.append(' ');

		buff.append(getMethodName())
			.append(Signature.C_PARAM_START)
			.append(getMethodParameters())
			.append(Signature.C_PARAM_END);
		
		buff.append(getMethodThrows());
		
		return buff.toString();
	}

	private String getVisibilityString(int visibility) {
		String visibilityString= JdtFlags.getVisibilityString(visibility);
		if ("".equals(visibilityString)) //$NON-NLS-1$
			return visibilityString;
		return visibilityString + ' ';
	}

	private String getMethodThrows() {
		final String throwsString= " throws "; //$NON-NLS-1$
		StringBuffer buff= new StringBuffer(throwsString);
		for (Iterator iter= fExceptionInfos.iterator(); iter.hasNext(); ) {
			ExceptionInfo info= (ExceptionInfo) iter.next();
			if (! info.isDeleted()) {
				buff.append(info.getType().getElementName());
				buff.append(", "); //$NON-NLS-1$
			}
		}
		if (buff.length() == throwsString.length())
			return ""; //$NON-NLS-1$
		buff.delete(buff.length() - 2, buff.length());
		return buff.toString();
	}

	private String getOldMethodThrows() {
		final String throwsString= " throws "; //$NON-NLS-1$
		StringBuffer buff= new StringBuffer(throwsString);
		for (Iterator iter= fExceptionInfos.iterator(); iter.hasNext(); ) {
			ExceptionInfo info= (ExceptionInfo) iter.next();
			if (! info.isAdded()) {
				buff.append(info.getType().getElementName());
				buff.append(", "); //$NON-NLS-1$
			}
		}
		if (buff.length() == throwsString.length())
			return ""; //$NON-NLS-1$
		buff.delete(buff.length() - 2, buff.length());
		return buff.toString();
	}

	private void checkForDuplicateParameterNames(RefactoringStatus result){
		Set found= new HashSet();
		Set doubled= new HashSet();
		for (Iterator iter = getNotDeletedInfos().iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo)iter.next();
			String newName= info.getNewName();
			if (found.contains(newName) && !doubled.contains(newName)){
				result.addFatalError(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_duplicate_name, newName));
				doubled.add(newName);
			} else {
				found.add(newName);
			}	
		}
	}
	
	private ICompilationUnit getCu() {
		return fMethod.getCompilationUnit();
	}
	
	private boolean mustAnalyzeAstOfDeclaringCu() throws JavaModelException{
		if (JdtFlags.isAbstract(getMethod()))
			return false;
		else if (JdtFlags.isNative(getMethod()))
			return false;
		else if (getMethod().getDeclaringType().isInterface())
			return false;
		else 
			return true;
	}
	
	private RefactoringStatus checkCompilationofDeclaringCu() throws CoreException {
		ICompilationUnit cu= getCu();
		TextChange change= fChangeManager.get(cu);
		String newCuSource= change.getPreviewContent(new NullProgressMonitor());
		CompilationUnit newCUNode= new RefactoringASTParser(AST.JLS3).parse(newCuSource, cu, true, false, null);
		IProblem[] problems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, fBaseCuRewrite.getRoot());
		RefactoringStatus result= new RefactoringStatus();
		for (int i= 0; i < problems.length; i++) {
			IProblem problem= problems[i];
			if (shouldReport(problem, newCUNode))
				result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, new SourceRange(problem))));
		}
		return result;
	}
		
	private boolean shouldReport(IProblem problem, CompilationUnit cu) {
		if (! problem.isError())
			return false;
		if (problem.getID() == IProblem.UndefinedType) //reported when trying to import
			return false;
		ASTNode node= ASTNodeSearchUtil.getAstNode(cu, problem.getSourceStart(), problem.getSourceEnd() - problem.getSourceStart());
		IProblemVerifier verifier= doGetProblemVerifier();
		if (verifier != null)
			return verifier.isError(problem, node);
		return true;	
	}

	private String getOldMethodParameters() {
		StringBuffer buff= new StringBuffer();
		int i= 0;
		for (Iterator iter= getNotAddedInfos().iterator(); iter.hasNext(); i++) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (i != 0 )
				buff.append(", ");  //$NON-NLS-1$
			buff.append(createDeclarationString(info));
		}
		return buff.toString();
	}
		
	private String getMethodParameters() {
		StringBuffer buff= new StringBuffer();
		int i= 0;
		for (Iterator iter= getNotDeletedInfos().iterator(); iter.hasNext(); i++) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (i != 0 )
				buff.append(", ");  //$NON-NLS-1$
			buff.append(createDeclarationString(info));
		}
		return buff.toString();
	}

	private List getAddedInfos(){
		List result= new ArrayList(1);
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (info.isAdded())
				result.add(info);
		}
		return result;
	}

	private List getDeletedInfos(){
		List result= new ArrayList(1);
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (info.isDeleted())
				result.add(info);
		}
		return result;
	}

	private List getNotAddedInfos(){
		List all= new ArrayList(fParameterInfos);
		all.removeAll(getAddedInfos());
		return all;
	}
	
	private List getNotDeletedInfos(){
		List all= new ArrayList(fParameterInfos);
		all.removeAll(getDeletedInfos());
		return all;
	}
	
	private boolean areNamesSameAsInitial() {
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (info.isRenamed())
				return false;
		}
		return true;
	}

	private boolean isOrderSameAsInitial(){
		int i= 0;
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext(); i++) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (info.getOldIndex() != i) // includes info.isAdded()
				return false;
			if (info.isDeleted())
				return false;
		}
		return true;
	}

	private RefactoringStatus checkReorderings(IProgressMonitor pm) throws JavaModelException {
		try{
			pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_checking_preconditions, 1); 
			return checkNativeMethods();
		} finally{
			pm.done();
		}	
	}
	
	private RefactoringStatus checkRenamings(IProgressMonitor pm) throws JavaModelException {
		try{
			pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_checking_preconditions, 1); 
			return checkParameterNamesInRippleMethods();
		} finally{
			pm.done();
		}	
	}
	
	private RefactoringStatus checkParameterNamesInRippleMethods() throws JavaModelException {
		RefactoringStatus result= new RefactoringStatus();
		Set newParameterNames= getNewParameterNamesList();
		for (int i= 0; i < fRippleMethods.length; i++) {
			String[] paramNames= fRippleMethods[i].getParameterNames();
			for (int j= 0; j < paramNames.length; j++) {
				if (newParameterNames.contains(paramNames[j])){
					String[] args= new String[]{JavaElementUtil.createMethodSignature(fRippleMethods[i]), paramNames[j]};
					String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_already_has, args); 
					RefactoringStatusContext context= JavaStatusContext.create(fRippleMethods[i].getCompilationUnit(), fRippleMethods[i].getNameRange());
					result.addError(msg, context);
				}	
			}
		}
		return result;
	}
	
	private Set getNewParameterNamesList() {
		Set oldNames= getOriginalParameterNames();
		Set currentNames= getNamesOfNotDeletedParameters();
		currentNames.removeAll(oldNames);
		return currentNames;
	}
	
	private Set getNamesOfNotDeletedParameters() {
		Set result= new HashSet();
		for (Iterator iter= getNotDeletedInfos().iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			result.add(info.getNewName());
		}
		return result;
	}
	
	private Set getOriginalParameterNames() {
		Set result= new HashSet();
		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
			ParameterInfo info= (ParameterInfo) iter.next();
			if (! info.isAdded())
				result.add(info.getOldName());
		}
		return result;
	}
	
	private RefactoringStatus checkNativeMethods() throws JavaModelException{
		RefactoringStatus result= new RefactoringStatus();
		for (int i= 0; i < fRippleMethods.length; i++) {
			if (JdtFlags.isNative(fRippleMethods[i])){
				String message= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_native, 
					new String[]{JavaElementUtil.createMethodSignature(fRippleMethods[i]), JavaModelUtil.getFullyQualifiedName(fRippleMethods[i].getDeclaringType())});
				result.addError(message, JavaStatusContext.create(fRippleMethods[i]));			
			}								
		}
		return result;
	}

	private IFile[] getAllFilesToModify(){
		return ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits());
	}
	
	private RefactoringStatus validateModifiesFiles(){
		return Checks.validateModifiesFiles(getAllFilesToModify(), getValidationContext());
	}

	public Change createChange(IProgressMonitor pm) {
		pm.beginTask("", 1); //$NON-NLS-1$
		try {
			final TextChange[] changes= fChangeManager.getAllChanges();
			final List list= new ArrayList(changes.length);
			list.addAll(fOtherChanges);
			list.addAll(Arrays.asList(changes));
			final Map arguments= new HashMap();
			String project= null;
			IJavaProject javaProject= fMethod.getJavaProject();
			if (javaProject != null)
				project= javaProject.getElementName();
			int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE;
			try {
				if (!Flags.isPrivate(fMethod.getFlags()))
					flags|= RefactoringDescriptor.MULTI_CHANGE;
				final IType declaring= fMethod.getDeclaringType();
				if (declaring.isAnonymous() || declaring.isLocal())
					flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
			} catch (JavaModelException exception) {
				JavaPlugin.log(exception);
			}
			JDTRefactoringDescriptor descriptor= null;
			try {
				final String description= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_descriptor_description_short, fMethod.getElementName());
				final String header= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_descriptor_description, new String[] { getOldMethodSignature(), getNewMethodSignature()});
				final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
				if (!fMethod.getElementName().equals(fMethodName))
					comment.addSetting(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_new_name_pattern, fMethodName));
				if (!isVisibilitySameAsInitial()) {
					String visibility= JdtFlags.getVisibilityString(fVisibility);
					if ("".equals(visibility)) //$NON-NLS-1$
						visibility= RefactoringCoreMessages.ChangeSignatureRefactoring_default_visibility;
					comment.addSetting(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_new_visibility_pattern, visibility));
				}
				if (fReturnTypeInfo.isTypeNameChanged())
					comment.addSetting(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_new_return_type_pattern, fReturnTypeInfo.getNewTypeName()));
				List deleted= new ArrayList();
				List added= new ArrayList();
				List changed= new ArrayList();
				for (final Iterator iterator= fParameterInfos.iterator(); iterator.hasNext();) {
					final ParameterInfo info= (ParameterInfo) iterator.next();
					if (info.isDeleted())
						deleted.add(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_deleted_parameter_pattern, new String[] { info.getOldTypeName(), info.getOldName()}));
					else if (info.isAdded())
						added.add(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_added_parameter_pattern, new String[] { info.getNewTypeName(), info.getNewName()}));
					else if (info.isRenamed() || info.isTypeNameChanged() || info.isVarargChanged())
						changed.add(Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_changed_parameter_pattern, new String[] { info.getOldTypeName(), info.getOldName()}));
				}
				if (!added.isEmpty())
					comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_added_parameters, (String[]) added.toArray(new String[added.size()])));
				if (!deleted.isEmpty())
					comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_removed_parameters, (String[]) deleted.toArray(new String[deleted.size()])));
				if (!changed.isEmpty())
					comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_changed_parameters, (String[]) changed.toArray(new String[changed.size()])));
				added.clear();
				deleted.clear();
				changed.clear();
				for (final Iterator iterator= fExceptionInfos.iterator(); iterator.hasNext();) {
					final ExceptionInfo info= (ExceptionInfo) iterator.next();
					if (info.isAdded())
						added.add(info.getType().getElementName());
					else if (info.isDeleted())
						deleted.add(info.getType().getElementName());
				}
				if (!added.isEmpty())
					comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_added_exceptions, (String[]) added.toArray(new String[added.size()])));
				if (!deleted.isEmpty())
					comment.addSetting(JDTRefactoringDescriptorComment.createCompositeSetting(RefactoringCoreMessages.ChangeSignatureRefactoring_removed_exceptions, (String[]) deleted.toArray(new String[deleted.size()])));
				descriptor= new JDTRefactoringDescriptor(IJavaRefactorings.CHANGE_METHOD_SIGNATURE, project, description, comment.asString(), arguments, flags);
				arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fMethod));
				arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_NAME, fMethodName);
				arguments.put(ATTRIBUTE_DELEGATE, Boolean.valueOf(fDelegateUpdating).toString());
				arguments.put(ATTRIBUTE_DEPRECATE, Boolean.valueOf(fDelegateDeprecation).toString());
				if (fReturnTypeInfo.isTypeNameChanged())
					arguments.put(ATTRIBUTE_RETURN, fReturnTypeInfo.getNewTypeName());
				try {
					if (!isVisibilitySameAsInitial())
						arguments.put(ATTRIBUTE_VISIBILITY, new Integer(fVisibility).toString());
				} catch (JavaModelException exception) {
					JavaPlugin.log(exception);
				}
				int count= 1;
				for (final Iterator iterator= fParameterInfos.iterator(); iterator.hasNext();) {
					final ParameterInfo info= (ParameterInfo) iterator.next();
					final StringBuffer buffer= new StringBuffer(64);
					buffer.append(info.getOldTypeName());
					buffer.append(" "); //$NON-NLS-1$
					buffer.append(info.getOldName());
					buffer.append(" "); //$NON-NLS-1$
					buffer.append(info.getOldIndex());
					buffer.append(" "); //$NON-NLS-1$
					buffer.append(info.getNewTypeName());
					buffer.append(" "); //$NON-NLS-1$
					buffer.append(info.getNewName());
					buffer.append(" "); //$NON-NLS-1$
					buffer.append(info.isDeleted());
					arguments.put(ATTRIBUTE_PARAMETER + count, buffer.toString());
					final String value= info.getDefaultValue();
					if (value != null && !"".equals(value)) //$NON-NLS-1$
						arguments.put(ATTRIBUTE_DEFAULT + count, value);
					count++;
				}
				count= 1;
				for (final Iterator iterator= fExceptionInfos.iterator(); iterator.hasNext();) {
					final ExceptionInfo info= (ExceptionInfo) iterator.next();
					arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + count, descriptor.elementToHandle(info.getType()));
					arguments.put(ATTRIBUTE_KIND + count, new Integer(info.getKind()).toString());
					count++;
				}
			} catch (JavaModelException exception) {
				JavaPlugin.log(exception);
				return null;
			}
			return new DynamicValidationRefactoringChange(descriptor, doGetRefactoringChangeName(), (Change[]) list.toArray(new Change[list.size()]));
		} finally {
			pm.done();
			clearManagers();
		}
	}

	protected String doGetRefactoringChangeName() {
		return RefactoringCoreMessages.ChangeSignatureRefactoring_restructure_parameters;
	}

	private TextChangeManager createChangeManager(IProgressMonitor pm, RefactoringStatus result) throws CoreException {
		pm.beginTask(RefactoringCoreMessages.ChangeSignatureRefactoring_preview, 2); 
		fChangeManager= new TextChangeManager();
		boolean isNoArgConstructor= isNoArgConstructor();
		Map namedSubclassMapping= null;
		if (isNoArgConstructor){
			//create only when needed;
			namedSubclassMapping= createNamedSubclassMapping(new SubProgressMonitor(pm, 1));
		}else{
			pm.worked(1);
		}
		for (int i= 0; i < fOccurrences.length; i++) {
			if (pm.isCanceled())
				throw new OperationCanceledException();
			SearchResultGroup group= fOccurrences[i];
			ICompilationUnit cu= group.getCompilationUnit();
			if (cu == null)
				continue;
			CompilationUnitRewrite cuRewrite;
			if (cu.equals(getCu())) {
				cuRewrite= fBaseCuRewrite;
			} else {
				cuRewrite= new CompilationUnitRewrite(cu);
				cuRewrite.getASTRewrite().setTargetSourceRangeComputer(new TightSourceRangeComputer());
			}
			ASTNode[] nodes= ASTNodeSearchUtil.findNodes(group.getSearchResults(), cuRewrite.getRoot());
			
			//IntroduceParameterObjectRefactoring needs to update declarations first:
			List/*<OccurrenceUpdate>*/ deferredUpdates= new ArrayList();
			for (int j= 0; j < nodes.length; j++) {
				OccurrenceUpdate update= createOccurrenceUpdate(nodes[j], cuRewrite, result);
				if (update instanceof DeclarationUpdate) {
					update.updateNode();
				} else {
					deferredUpdates.add(update);
				}
			}
			for (Iterator iter= deferredUpdates.iterator(); iter.hasNext();) {
				((OccurrenceUpdate) iter.next()).updateNode();
			}
			
			if (isNoArgConstructor && namedSubclassMapping.containsKey(cu)){
				//only non-anonymous subclasses may have noArgConstructors to modify - see bug 43444
				Set subtypes= (Set)namedSubclassMapping.get(cu);
				for (Iterator iter= subtypes.iterator(); iter.hasNext();) {
					IType subtype= (IType) iter.next();
					AbstractTypeDeclaration subtypeNode= ASTNodeSearchUtil.getAbstractTypeDeclarationNode(subtype, cuRewrite.getRoot());
					if (subtypeNode != null)
						modifyImplicitCallsToNoArgConstructor(subtypeNode, cuRewrite);
				}
			}
			TextChange change= cuRewrite.createChange();
			if (change != null)
				fChangeManager.manage(cu, change);
		}
		
		pm.done();
		return fChangeManager;
	}
	
	//Map<ICompilationUnit, Set<IType>>
	private Map createNamedSubclassMapping(IProgressMonitor pm) throws JavaModelException{
		IType[] subclasses= getCachedTypeHierarchy(new SubProgressMonitor(pm, 1)).getSubclasses(fMethod.getDeclaringType());
		Map result= new HashMap();
		for (int i= 0; i < subclasses.length; i++) {
			IType subclass= subclasses[i];
			if (subclass.isAnonymous())
				continue;
			ICompilationUnit cu= subclass.getCompilationUnit();
			if (! result.containsKey(cu))
				result.put(cu, new HashSet());
			((Set)result.get(cu)).add(subclass);
		}
		return result;
	}
	
	private void modifyImplicitCallsToNoArgConstructor(AbstractTypeDeclaration subclass, CompilationUnitRewrite cuRewrite) {
		MethodDeclaration[] constructors= getAllConstructors(subclass);
		if (constructors.length == 0){
			addNewConstructorToSubclass(subclass, cuRewrite);
		} else {
			for (int i= 0; i < constructors.length; i++) {
				if (! containsImplicitCallToSuperConstructor(constructors[i]))
					continue;
				addExplicitSuperConstructorCall(constructors[i], cuRewrite);
			}
		}
	}
	
	private void addExplicitSuperConstructorCall(MethodDeclaration constructor, CompilationUnitRewrite cuRewrite) {
		SuperConstructorInvocation superCall= constructor.getAST().newSuperConstructorInvocation();
		addArgumentsToNewSuperConstructorCall(superCall, cuRewrite);
		String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_add_super_call; 
		TextEditGroup description= cuRewrite.createGroupDescription(msg);
		cuRewrite.getASTRewrite().getListRewrite(constructor.getBody(), Block.STATEMENTS_PROPERTY).insertFirst(superCall, description);
	}
	
	private void addArgumentsToNewSuperConstructorCall(SuperConstructorInvocation superCall, CompilationUnitRewrite cuRewrite) {
		int i= 0;
		for (Iterator iter= getNotDeletedInfos().iterator(); iter.hasNext(); i++) {
			ParameterInfo info= (ParameterInfo) iter.next();
			Expression newExpression= createNewExpression(info, getParameterInfos(), superCall.arguments(), cuRewrite, (MethodDeclaration) ASTNodes.getParent(superCall, MethodDeclaration.class));
			if (newExpression != null)
				superCall.arguments().add(newExpression);
		}
	}
	
	private static boolean containsImplicitCallToSuperConstructor(MethodDeclaration constructor) {
		Assert.isTrue(constructor.isConstructor());
		Block body= constructor.getBody();
		if (body == null)
			return false;
		if (body.statements().size() == 0)
			return true;
		if (body.statements().get(0) instanceof ConstructorInvocation)
			return false;
		if (body.statements().get(0) instanceof SuperConstructorInvocation)
			return false;
		return true;
	}
	
	private void addNewConstructorToSubclass(AbstractTypeDeclaration subclass, CompilationUnitRewrite cuRewrite) {
		AST ast= subclass.getAST();
		MethodDeclaration newConstructor= ast.newMethodDeclaration();
		newConstructor.setName(ast.newSimpleName(subclass.getName().getIdentifier()));
		newConstructor.setConstructor(true);
		newConstructor.setExtraDimensions(0);
		newConstructor.setJavadoc(null);
		newConstructor.modifiers().addAll(ASTNodeFactory.newModifiers(ast, getAccessModifier(subclass)));
		newConstructor.setReturnType2(ast.newPrimitiveType(PrimitiveType.VOID));
		Block body= ast.newBlock();
		newConstructor.setBody(body);
		SuperConstructorInvocation superCall= ast.newSuperConstructorInvocation();
		addArgumentsToNewSuperConstructorCall(superCall, cuRewrite);
		body.statements().add(superCall);
		
		String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_add_constructor; 
		TextEditGroup description= cuRewrite.createGroupDescription(msg);
		cuRewrite.getASTRewrite().getListRewrite(subclass, subclass.getBodyDeclarationsProperty()).insertFirst(newConstructor, description);
		
		// TODO use AbstractTypeDeclaration
	}
	
	private static int getAccessModifier(AbstractTypeDeclaration subclass) {
		int modifiers= subclass.getModifiers();
		if (Modifier.isPublic(modifiers))
			return Modifier.PUBLIC;
		else if (Modifier.isProtected(modifiers))
			return Modifier.PROTECTED;
		else if (Modifier.isPrivate(modifiers))
			return Modifier.PRIVATE;
		else
			return Modifier.NONE;
	}
	
	private MethodDeclaration[] getAllConstructors(AbstractTypeDeclaration typeDeclaration) {
		BodyDeclaration decl;
		List result= new ArrayList(1);
		for (Iterator it = typeDeclaration.bodyDeclarations().listIterator(); it.hasNext(); ) {
			decl= (BodyDeclaration) it.next();
			if (decl instanceof MethodDeclaration && ((MethodDeclaration) decl).isConstructor())
				result.add(decl);
		}
		return (MethodDeclaration[]) result.toArray(new MethodDeclaration[result.size()]);
	}
	
	private boolean isNoArgConstructor() throws JavaModelException {
		return fMethod.isConstructor() && fMethod.getNumberOfParameters() == 0;
	}
	
	private Expression createNewExpression(ParameterInfo info, List parameterInfos, List nodes, CompilationUnitRewrite cuRewrite, MethodDeclaration method) {
		if (info.isNewVarargs() && info.getDefaultValue().trim().length() == 0)
			return null;
		else {
			if (fDefaultValueAdvisor == null)
				return (Expression) cuRewrite.getASTRewrite().createStringPlaceholder(info.getDefaultValue(), ASTNode.METHOD_INVOCATION);
			else
				return fDefaultValueAdvisor.createDefaultExpression(nodes, info, parameterInfos, method, false, cuRewrite);
		}
	}

	private boolean isVisibilitySameAsInitial() throws JavaModelException {
		return fVisibility == JdtFlags.getVisibilityCode(fMethod);
	}
	
	private IJavaSearchScope createRefactoringScope()  throws JavaModelException{
		return RefactoringScopeFactory.create(fMethod);
	}
	
	private SearchResultGroup[] findOccurrences(IProgressMonitor pm, RefactoringStatus status) throws JavaModelException{
		if (fMethod.isConstructor()){
			// workaround for bug 27236:
			return ConstructorReferenceFinder.getConstructorOccurrences(fMethod, pm, status);
		}else{	
			SearchPattern pattern= RefactoringSearchEngine.createOrPattern(fRippleMethods, IJavaSearchConstants.ALL_OCCURRENCES);
			return RefactoringSearchEngine.search(pattern, createRefactoringScope(), pm, status);
		}
	}
	
	private static String createDeclarationString(ParameterInfo info) {
		String newTypeName= info.getNewTypeName();
		int index= newTypeName.indexOf('.');
		if (index != -1){
			newTypeName= newTypeName.substring(index+1);
		}
		return newTypeName + " " + info.getNewName(); //$NON-NLS-1$
	}
	
	private static final boolean BUG_89686= true; //see bug 83693: Search for References to methods/constructors: do ranges include parameter lists?
	
	private OccurrenceUpdate createOccurrenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) {
		if (BUG_89686 && node instanceof SimpleName && node.getParent() instanceof EnumConstantDeclaration)
			node= node.getParent();
		
		if (isReferenceNode(node))
			return new ReferenceUpdate(node, cuRewrite, result);
		
		else if (node instanceof SimpleName && node.getParent() instanceof MethodDeclaration)
			return new DeclarationUpdate((MethodDeclaration) node.getParent(), cuRewrite, result);
		
		else if (node instanceof MemberRef || node instanceof MethodRef)
			return new DocReferenceUpdate(node, cuRewrite, result);
		
		else if (ASTNodes.getParent(node, ImportDeclaration.class) != null)
			return new StaticImportUpdate((ImportDeclaration) ASTNodes.getParent(node, ImportDeclaration.class), cuRewrite, result);
		
		else
			return new NullOccurrenceUpdate(node, cuRewrite, result);
	}

	private static boolean isReferenceNode(ASTNode node){
		switch (node.getNodeType()) {
			case ASTNode.METHOD_INVOCATION :
			case ASTNode.SUPER_METHOD_INVOCATION :
			case ASTNode.CLASS_INSTANCE_CREATION :
			case ASTNode.CONSTRUCTOR_INVOCATION :
			case ASTNode.SUPER_CONSTRUCTOR_INVOCATION :
			case ASTNode.ENUM_CONSTANT_DECLARATION :
				return true;

			default :
				return false;
		}
	}

	abstract class OccurrenceUpdate {
		protected final CompilationUnitRewrite fCuRewrite;
		protected final TextEditGroup fDescription;
		protected RefactoringStatus fResult;
		
		protected OccurrenceUpdate(CompilationUnitRewrite cuRewrite, TextEditGroup description, RefactoringStatus result) {
			fCuRewrite= cuRewrite;
			fDescription= description;
			fResult= result;
		}
		
		protected final ASTRewrite getASTRewrite() {
			return fCuRewrite.getASTRewrite();
		}

		protected final ImportRewrite getImportRewrite() {
			return fCuRewrite.getImportRewrite();
		}
		
		protected final ImportRemover getImportRemover() {
			return fCuRewrite.getImportRemover();
		}
		
		protected final CompilationUnitRewrite getCompilationUnitRewrite() {
			return fCuRewrite;
		}
		
		public abstract void updateNode() throws CoreException;
		
		protected void registerImportRemoveNode(ASTNode node) {
			getImportRemover().registerRemovedNode(node);
		}

		protected final void reshuffleElements() {
			if (isOrderSameAsInitial())
				return;
			
			//varargs; method(p1, p2, .., pn), call(a1, a2, .., ax) :
			// if (method_was_vararg) {
			//     assert fOldVarargIndex != -1
			//     if (vararg_retained) {
			//         assert vararg_is_last_non_deleted (pn)
			//         assert no_other_varargs
			//         => reshuffle [1..n-1] then append remaining nodes [n..x], possibly none
			//         
			//     } else (vararg_deleted) {
			//         assert all_are_non_vararg
			//         => reshuffle [1..n-1], drop all remaining nodes [n..x], possibly none
			//     }
			// 
			// } else if (method_became_vararg) {
			//     assert n == x
			//     assert fOldVarargIndex == -1
			//     => reshuffle [1..n]
			// 
			// } else (JLS2_case) {
			//     assert n == x
			//     assert fOldVarargIndex == -1
			//     => reshuffle [1..n]
			// }
			
			ListRewrite listRewrite= getParamgumentsRewrite();
			Map newOldMap= new LinkedHashMap();
			List nodes= listRewrite.getRewrittenList();
			Iterator rewriteIter= nodes.iterator();
			List original= listRewrite.getOriginalList();
			for (Iterator iter= original.iterator(); iter.hasNext();) {
				newOldMap.put(rewriteIter.next(),iter.next());
			}
			List newNodes= new ArrayList();
			// register removed nodes, and collect nodes in new sequence:
			for (int i= 0; i < fParameterInfos.size(); i++) {
				ParameterInfo info= (ParameterInfo) fParameterInfos.get(i);
				int oldIndex= info.getOldIndex();
				
				if (info.isDeleted()) {
					if (oldIndex != fOldVarargIndex) {
						registerImportRemoveNode((ASTNode) nodes.get(oldIndex));
					} else {
						//vararg deleted -> remove all remaining nodes:
						for (int n= oldIndex; n < nodes.size(); n++) {
							registerImportRemoveNode((ASTNode) nodes.get(n));
						}
					}
					
				} else if (info.isAdded()) {
					ASTNode newParamgument= createNewParamgument(info, fParameterInfos, nodes);
					if (newParamgument != null)
						newNodes.add(newParamgument);
					
				} else /* parameter stays */ {
					if (oldIndex != fOldVarargIndex) {
						ASTNode oldNode= (ASTNode) nodes.get(oldIndex);
						ASTNode movedNode= moveNode(oldNode, getASTRewrite());
						newNodes.add(movedNode);
					} else {
						//vararg stays and is last parameter -> copy all remaining nodes:
						for (int n= oldIndex; n < nodes.size(); n++) {
							ASTNode oldNode= (ASTNode) nodes.get(n);
							ASTNode movedNode= moveNode(oldNode, getASTRewrite());
							newNodes.add(movedNode);
						}
					}
				}
			}
			
			Iterator nodesIter= nodes.iterator();
			Iterator newIter= newNodes.iterator();
			//replace existing nodes with new ones:
			while (nodesIter.hasNext() && newIter.hasNext()) {
				ASTNode node= (ASTNode) nodesIter.next();
				ASTNode newNode= (ASTNode) newIter.next();
				if (!ASTNodes.isExistingNode(node)) //XXX:should better be addressed in ListRewriteEvent.replaceEntry(ASTNode, ASTNode)
					listRewrite.replace((ASTNode) newOldMap.get(node), newNode, fDescription);
				else
					listRewrite.replace(node, newNode, fDescription);
			}
			//remove remaining existing nodes:
			while (nodesIter.hasNext()) {
				ASTNode node= (ASTNode) nodesIter.next();
				if (!ASTNodes.isExistingNode(node))
					listRewrite.remove((ASTNode) newOldMap.get(node), fDescription);
				else
					listRewrite.remove(node, fDescription);
			}
			//add additional new nodes:
			while (newIter.hasNext()) {
				ASTNode node= (ASTNode) newIter.next();
				listRewrite.insertLast(node, fDescription);
			}
		}

		/**
		 * @return ListRewrite of parameters or arguments
		 */
		protected abstract ListRewrite getParamgumentsRewrite();
		
		protected final void changeParamguments() {
			for (Iterator iter= getParameterInfos().iterator(); iter.hasNext();) {
				ParameterInfo info= (ParameterInfo) iter.next();
				if (info.isAdded() || info.isDeleted())
					continue;
				
				if (info.isRenamed())
					changeParamgumentName(info);
		
				if (info.isTypeNameChanged())
					changeParamgumentType(info);
			}
		}

		protected void changeParamgumentName(ParameterInfo info) {
			// no-op
		}

		protected void changeParamgumentType(ParameterInfo info) {
			// no-op
		}

		protected final void replaceTypeNode(Type typeNode, String newTypeName, ITypeBinding newTypeBinding){
			Type newTypeNode= createNewTypeNode(newTypeName, newTypeBinding);
			getASTRewrite().replace(typeNode, newTypeNode, fDescription);
			registerImportRemoveNode(typeNode);
			getTightSourceRangeComputer().addTightSourceNode(typeNode);
		}
	
		/**
		 * @param info
		 * @param parameterInfos TODO
		 * @param nodes TODO
		 * @return a new method parameter or argument, or <code>null</code> for an empty vararg argument
		 */
		protected abstract ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes);

		protected abstract SimpleName getMethodNameNode();

		protected final void changeMethodName() {
			if (! isMethodNameSameAsInitial()) {
				SimpleName nameNode= getMethodNameNode();
				SimpleName newNameNode= nameNode.getAST().newSimpleName(fMethodName);
				getASTRewrite().replace(nameNode, newNameNode, fDescription);
				registerImportRemoveNode(nameNode);
				getTightSourceRangeComputer().addTightSourceNode(nameNode);
			}
		}

		protected final Type createNewTypeNode(String newTypeName, ITypeBinding newTypeBinding) {
			Type newTypeNode;
			if (newTypeBinding == null) {
				if (fDefaultValueAdvisor != null)
					newTypeNode= fDefaultValueAdvisor.createType(newTypeName, getMethodNameNode().getStartPosition(), getCompilationUnitRewrite());
				else
					newTypeNode= (Type) getASTRewrite().createStringPlaceholder(newTypeName, ASTNode.SIMPLE_TYPE);
				//Don't import if not resolved.
			} else {
				newTypeNode= getImportRewrite().addImport(newTypeBinding, fCuRewrite.getAST());
				getImportRemover().registerAddedImports(newTypeNode);
			}
			return newTypeNode;
		}
		
		protected final TightSourceRangeComputer getTightSourceRangeComputer() {
			return (TightSourceRangeComputer) fCuRewrite.getASTRewrite().getExtendedSourceRangeComputer();
		}
	}
	
	class ReferenceUpdate extends OccurrenceUpdate {
		/** isReferenceNode(fNode) */
		private ASTNode fNode;

		protected ReferenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) {
			super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_update_reference), result); 
			fNode= node; //holds: Assert.isTrue(isReferenceNode(node));
		}

		public void updateNode() {
			reshuffleElements();
			changeMethodName();
		}
		
		/** @return {@inheritDoc} (element type: Expression) */
		protected ListRewrite getParamgumentsRewrite() {
			if (fNode instanceof MethodInvocation)	
				return getASTRewrite().getListRewrite(fNode, MethodInvocation.ARGUMENTS_PROPERTY);
				
			if (fNode instanceof SuperMethodInvocation)	
				return getASTRewrite().getListRewrite(fNode, SuperMethodInvocation.ARGUMENTS_PROPERTY);
				
			if (fNode instanceof ClassInstanceCreation)	
				return getASTRewrite().getListRewrite(fNode, ClassInstanceCreation.ARGUMENTS_PROPERTY);
				
			if (fNode instanceof ConstructorInvocation)	
				return getASTRewrite().getListRewrite(fNode, ConstructorInvocation.ARGUMENTS_PROPERTY);
				
			if (fNode instanceof SuperConstructorInvocation)	
				return getASTRewrite().getListRewrite(fNode, SuperConstructorInvocation.ARGUMENTS_PROPERTY);
			
			if (fNode instanceof EnumConstantDeclaration)	
				return getASTRewrite().getListRewrite(fNode, EnumConstantDeclaration.ARGUMENTS_PROPERTY);
			
			return null;
		}
		
		protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) {
			CompilationUnitRewrite cuRewrite= getCompilationUnitRewrite();
			MethodDeclaration declaration= (MethodDeclaration) ASTNodes.getParent(fNode, MethodDeclaration.class);
			if (isRecursiveReference()) {
				return createNewExpressionRecursive(info, parameterInfos, nodes, cuRewrite, declaration);
			} else
				return createNewExpression(info, parameterInfos, nodes, cuRewrite, declaration);
		}

		private Expression createNewExpressionRecursive(ParameterInfo info, List parameterInfos, List nodes, CompilationUnitRewrite cuRewrite, MethodDeclaration methodDeclaration) {
			if (fDefaultValueAdvisor != null && info.isAdded()) {
				return fDefaultValueAdvisor.createDefaultExpression(nodes, info, parameterInfos, methodDeclaration, true, cuRewrite);
			}
			return (Expression) getASTRewrite().createStringPlaceholder(info.getNewName(), ASTNode.METHOD_INVOCATION);
		}

		protected SimpleName getMethodNameNode() {
			if (fNode instanceof MethodInvocation)	
				return ((MethodInvocation)fNode).getName();
				
			if (fNode instanceof SuperMethodInvocation)	
				return ((SuperMethodInvocation)fNode).getName();
				
			return null;	
		}
		
		private boolean isRecursiveReference() {
			MethodDeclaration enclosingMethodDeclaration= (MethodDeclaration) ASTNodes.getParent(fNode, MethodDeclaration.class);
			if (enclosingMethodDeclaration == null)
				return false;
			
			IMethodBinding enclosingMethodBinding= enclosingMethodDeclaration.resolveBinding();
			if (enclosingMethodBinding == null)
				return false;
			
			if (fNode instanceof MethodInvocation)	
				return enclosingMethodBinding == ((MethodInvocation)fNode).resolveMethodBinding();
				
			if (fNode instanceof SuperMethodInvocation) {
				IMethodBinding methodBinding= ((SuperMethodInvocation)fNode).resolveMethodBinding();
				return isSameMethod(methodBinding, enclosingMethodBinding);
			}
				
			if (fNode instanceof ClassInstanceCreation)	
				return enclosingMethodBinding == ((ClassInstanceCreation)fNode).resolveConstructorBinding();
				
			if (fNode instanceof ConstructorInvocation)	
				return enclosingMethodBinding == ((ConstructorInvocation)fNode).resolveConstructorBinding();
				
			if (fNode instanceof SuperConstructorInvocation) {
				return false; //Constructors don't override -> enclosing has not been changed -> no recursion
			}
			
			if (fNode instanceof EnumConstantDeclaration) {
				return false; //cannot define enum constant inside enum constructor
			}

			Assert.isTrue(false);
			return false;
		}
		
		/**
		 * @param m1 method 1
		 * @param m2 method 2
		 * @return true iff
		 * 		<ul><li>the methods are both constructors with same argument types, or</li>
		 *	 		<li>the methods have the same name and the same argument types</li></ul>
		 */
		private boolean isSameMethod(IMethodBinding m1, IMethodBinding m2) {
			if (m1.isConstructor()) {
				if (! m2.isConstructor())
					return false;
			} else {
				if (! m1.getName().equals(m2.getName()))
					return false;
			}
			
			ITypeBinding[] m1Parameters= m1.getParameterTypes();
			ITypeBinding[] m2Parameters= m2.getParameterTypes();
			if (m1Parameters.length != m2Parameters.length)
				return false;
			for (int i= 0; i < m1Parameters.length; i++) {
				if (m1Parameters[i].getErasure() != m2Parameters[i].getErasure())
					return false;
			}
			return true;
		}

	}

	class DeclarationUpdate extends OccurrenceUpdate {
		private MethodDeclaration fMethDecl;

		protected DeclarationUpdate(MethodDeclaration decl, CompilationUnitRewrite cuRewrite, RefactoringStatus result) {
			super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_change_signature), result); 
			fMethDecl= decl;
		}
		
		// Prevent import removing if delegate is created.
		protected void registerImportRemoveNode(ASTNode node) {
			if (!fDelegateUpdating)
				super.registerImportRemoveNode(node);
		}

		public void updateNode() throws CoreException {
			changeParamguments();
			
			if (canChangeNameAndReturnType()) {
				changeMethodName();
				changeReturnType();
			}
					
			if (needsVisibilityUpdate())
				changeVisibility();
			reshuffleElements();
			changeExceptions();
			
			changeJavadocTags();
			
			if (fBodyUpdater == null || fBodyUpdater.needsParameterUsedCheck())
				checkIfDeletedParametersUsed();
			
			if (fBodyUpdater != null)
				fBodyUpdater.updateBody(fMethDecl, fCuRewrite, fResult);
			
			if (fDelegateUpdating)
				addDelegate();
		}

		private void addDelegate() throws JavaModelException {

			DelegateMethodCreator creator= new DelegateMethodCreator();
			creator.setDeclaration(fMethDecl);
			creator.setDeclareDeprecated(fDelegateDeprecation);
			creator.setSourceRewrite(fCuRewrite);
			creator.prepareDelegate();
			
			/*
			 * The delegate now contains a call and a javadoc reference to the
			 * old method (i.e., to itself).
			 * 
			 * Use ReferenceUpdate() / DocReferenceUpdate() to update these
			 * references like any other reference.
			 */
			final ASTNode delegateInvocation= creator.getDelegateInvocation();
			if (delegateInvocation != null)
				// may be null if the delegate is an interface method or
				// abstract -> no body
				new ReferenceUpdate(delegateInvocation, creator.getDelegateRewrite(), fResult).updateNode();
			new DocReferenceUpdate(creator.getJavadocReference(), creator.getDelegateRewrite(), fResult).updateNode();
			
			creator.createEdit();
		}
		
		/** @return {@inheritDoc} (element type: SingleVariableDeclaration) */
		protected ListRewrite getParamgumentsRewrite() {
			return getASTRewrite().getListRewrite(fMethDecl, MethodDeclaration.PARAMETERS_PROPERTY);
		}

		protected void changeParamgumentName(ParameterInfo info) {
			SingleVariableDeclaration param= (SingleVariableDeclaration) fMethDecl.parameters().get(info.getOldIndex());
			if (! info.getOldName().equals(param.getName().getIdentifier()))
				return; //don't change if original parameter name != name in rippleMethod
			
			String msg= RefactoringCoreMessages.ChangeSignatureRefactoring_update_parameter_references; 
			TextEditGroup description= fCuRewrite.createGroupDescription(msg);
			TempOccurrenceAnalyzer analyzer= new TempOccurrenceAnalyzer(param, false);
			analyzer.perform();
			SimpleName[] paramOccurrences= analyzer.getReferenceAndDeclarationNodes(); // @param tags are updated in changeJavaDocTags()
			for (int j= 0; j < paramOccurrences.length; j++) {
				SimpleName occurence= paramOccurrences[j];
				getASTRewrite().set(occurence, SimpleName.IDENTIFIER_PROPERTY, info.getNewName(), description);
			}
		}
		
		protected void changeParamgumentType(ParameterInfo info) {
			SingleVariableDeclaration oldParam= (SingleVariableDeclaration) fMethDecl.parameters().get(info.getOldIndex());
			getASTRewrite().set(oldParam, SingleVariableDeclaration.VARARGS_PROPERTY, Boolean.valueOf(info.isNewVarargs()), fDescription);
			replaceTypeNode(oldParam.getType(), ParameterInfo.stripEllipsis(info.getNewTypeName()), info.getNewTypeBinding());
			removeExtraDimensions(oldParam);
		}

		private void removeExtraDimensions(SingleVariableDeclaration oldParam) {
			if (oldParam.getExtraDimensions() != 0) {		
				getASTRewrite().set(oldParam, SingleVariableDeclaration.EXTRA_DIMENSIONS_PROPERTY, new Integer(0), fDescription);
			}
		}
	
		private void changeReturnType() throws JavaModelException {
		    if (isReturnTypeSameAsInitial())
		    	return;
			replaceTypeNode(fMethDecl.getReturnType2(), fReturnTypeInfo.getNewTypeName(), fReturnTypeInfo.getNewTypeBinding());
	        removeExtraDimensions(fMethDecl);
	    	//Remove expression from return statement when changed to void? No, would lose information!
	    	//Could add return statement with default value and add todo comment, but compile error is better.
		}
	
		private void removeExtraDimensions(MethodDeclaration methDecl) {
			if (methDecl.getExtraDimensions() != 0)
				getASTRewrite().set(methDecl, MethodDeclaration.EXTRA_DIMENSIONS_PROPERTY, new Integer(0), fDescription);
		}

		private boolean needsVisibilityUpdate() throws JavaModelException {
			if (isVisibilitySameAsInitial())
				return false;
			if (isIncreasingVisibility())
				return JdtFlags.isHigherVisibility(fVisibility, JdtFlags.getVisibilityCode(fMethDecl));
			else
				return JdtFlags.isHigherVisibility(JdtFlags.getVisibilityCode(fMethDecl), fVisibility);
		}
		
		private boolean isIncreasingVisibility() throws JavaModelException{
			return JdtFlags.isHigherVisibility(fVisibility, JdtFlags.getVisibilityCode(fMethod));
		}
		
		private void changeVisibility() {
			ModifierRewrite.create(getASTRewrite(), fMethDecl).setVisibility(fVisibility, fDescription);
		}
	
		private void changeExceptions() {
			for (Iterator iter= fExceptionInfos.iterator(); iter.hasNext();) {
				ExceptionInfo info= (ExceptionInfo) iter.next();
				if (info.isOld())
					continue;
				if (info.isDeleted())
					removeExceptionFromNodeList(info, fMethDecl.thrownExceptions());
				else
					addExceptionToNodeList(info, getASTRewrite().getListRewrite(fMethDecl, MethodDeclaration.THROWN_EXCEPTIONS_PROPERTY));
			}
		}
		
		private void removeExceptionFromNodeList(ExceptionInfo toRemove, List exceptionsNodeList) {
			ITypeBinding typeToRemove= toRemove.getTypeBinding();
			for (Iterator iter= exceptionsNodeList.iterator(); iter.hasNext(); ) {
				Name currentName= (Name) iter.next();
				ITypeBinding currentType= currentName.resolveTypeBinding();
				/* Maybe remove all subclasses of typeToRemove too.
				 * Problem:
				 * - B extends A;
				 * - A.m() throws IOException, Exception;
				 * - B.m() throws IOException, AWTException;
				 * Removing Exception should remove AWTException,
				 * but NOT remove IOException (or a subclass of JavaModelException). */
				 // if (Bindings.isSuperType(typeToRemove, currentType))
				if (currentType == null)
					continue; // newly added or unresolvable type
				if (Bindings.equals(currentType, typeToRemove) || toRemove.getType().getElementName().equals(currentType.getName())) {
					getASTRewrite().remove(currentName, fDescription);
					registerImportRemoveNode(currentName);
				}
			}
		}
	
		private void addExceptionToNodeList(ExceptionInfo exceptionInfo, ListRewrite exceptionListRewrite) {
			String fullyQualified= JavaModelUtil.getFullyQualifiedName(exceptionInfo.getType());
			for (Iterator iter= exceptionListRewrite.getOriginalList().iterator(); iter.hasNext(); ) {
				Name exName= (Name) iter.next();
				//XXX: existing superclasses of the added exception are redundant and could be removed
				ITypeBinding typeBinding= exName.resolveTypeBinding();
				if (typeBinding == null)
					continue; // newly added or unresolvable type
				if (typeBinding.getQualifiedName().equals(fullyQualified))
					return; // don't add it again
			}
			String importedType= getImportRewrite().addImport(JavaModelUtil.getFullyQualifiedName(exceptionInfo.getType()));
			getImportRemover().registerAddedImport(importedType);
			ASTNode exNode= getASTRewrite().createStringPlaceholder(importedType, ASTNode.SIMPLE_NAME);
			exceptionListRewrite.insertLast(exNode, fDescription);
		}
		
		private void changeJavadocTags() throws JavaModelException {
			//update tags in javadoc: @param, @return, @exception, @throws, ...
			Javadoc javadoc= fMethDecl.getJavadoc();
			if (javadoc == null)
				return;

			ITypeBinding typeBinding= Bindings.getBindingOfParentType(fMethDecl);
			if (typeBinding == null)
				return;
			IMethodBinding methodBinding= fMethDecl.resolveBinding();
			if (methodBinding == null)
				return;
				
			boolean isTopOfRipple= (Bindings.findOverriddenMethod(methodBinding, false) == null);
			//add tags: only iff top of ripple; change and remove: always.
			//TODO: should have preference for adding tags in (overriding) methods (with template: todo, inheritDoc, ...)
			
			List tags= javadoc.tags(); // List of TagElement
			ListRewrite tagsRewrite= getASTRewrite().getListRewrite(javadoc, Javadoc.TAGS_PROPERTY);

			if (! isReturnTypeSameAsInitial()) {
				if (PrimitiveType.VOID.toString().equals(fReturnTypeInfo.getNewTypeName())) {
					for (int i = 0; i < tags.size(); i++) {
						TagElement tag= (TagElement) tags.get(i);
						if (TagElement.TAG_RETURN.equals(tag.getTagName())) {
							getASTRewrite().remove(tag, fDescription);
							registerImportRemoveNode(tag);
						}
					}
				} else if (isTopOfRipple && Signature.SIG_VOID.equals(fMethod.getReturnType())){
					TagElement returnNode= createReturnTag();
					TagElement previousTag= findTagElementToInsertAfter(tags, TagElement.TAG_RETURN);
					insertTag(returnNode, previousTag, tagsRewrite);
					tags= tagsRewrite.getRewrittenList();
				}
			}
			
			if (! (areNamesSameAsInitial() && isOrderSameAsInitial())) {
				ArrayList paramTags= new ArrayList(); // <TagElement>, only not deleted tags with simpleName
				// delete & rename:
				for (Iterator iter = tags.iterator(); iter.hasNext(); ) {
					TagElement tag = (TagElement) iter.next();
					String tagName= tag.getTagName();
					List fragments= tag.fragments();
					if (! (TagElement.TAG_PARAM.equals(tagName) && fragments.size() > 0 && fragments.get(0) instanceof SimpleName))
						continue;
					SimpleName simpleName= (SimpleName) fragments.get(0);
					String identifier= simpleName.getIdentifier();
					boolean removed= false;
					for (int i= 0; i < fParameterInfos.size(); i++) {
						ParameterInfo info= (ParameterInfo) fParameterInfos.get(i);
						if (identifier.equals(info.getOldName())) {
							if (info.isDeleted()) {
								getASTRewrite().remove(tag, fDescription);
								registerImportRemoveNode(tag);
								removed= true;
							} else if (info.isRenamed()) {
								SimpleName newName= simpleName.getAST().newSimpleName(info.getNewName());
								getASTRewrite().replace(simpleName, newName, fDescription);
								registerImportRemoveNode(tag);
							}
							break;
						}
					}
					if (! removed)
						paramTags.add(tag);
				}
				tags= tagsRewrite.getRewrittenList();

				if (! isOrderSameAsInitial()) {
					// reshuffle (sort in declaration sequence) & add (only add to top of ripple):
					TagElement previousTag= findTagElementToInsertAfter(tags, TagElement.TAG_PARAM);
					boolean first= true; // workaround for bug 92111: preserve first tag if possible
					// reshuffle:
					for (Iterator infoIter= fParameterInfos.iterator(); infoIter.hasNext();) {
						ParameterInfo info= (ParameterInfo) infoIter.next();
						String oldName= info.getOldName();
						String newName= info.getNewName();
						if (info.isAdded()) {
							first= false;
							if (! isTopOfRipple)
								continue;
							TagElement paramNode= JavadocUtil.createParamTag(newName, fCuRewrite.getRoot().getAST(), fCuRewrite.getCu().getJavaProject());
							insertTag(paramNode, previousTag, tagsRewrite);
							previousTag= paramNode;
						} else {
							for (Iterator tagIter= paramTags.iterator(); tagIter.hasNext();) {
								TagElement tag= (TagElement) tagIter.next();
								SimpleName tagName= (SimpleName) tag.fragments().get(0);
								if (oldName.equals(tagName.getIdentifier())) {
									tagIter.remove();
									if (first) {
										previousTag= tag;
									} else {
										TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag);
										getASTRewrite().remove(tag, fDescription);
										insertTag(movedTag, previousTag, tagsRewrite);
										previousTag= movedTag;
									}
								}
								first= false;
							}
						}
					}
					// params with bad names:
					for (Iterator iter= paramTags.iterator(); iter.hasNext();) {
						TagElement tag= (TagElement) iter.next();
						TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag);
						getASTRewrite().remove(tag, fDescription);
						insertTag(movedTag, previousTag, tagsRewrite);
						previousTag= movedTag;
					}
				}
				tags= tagsRewrite.getRewrittenList();
			}
			
			if (! areExceptionsSameAsInitial()) {
				// collect exceptionTags and remove deleted:
				ArrayList exceptionTags= new ArrayList(); // <TagElement>, only not deleted tags with name
				for (int i= 0; i < tags.size(); i++) {
					TagElement tag= (TagElement) tags.get(i);
					if (! TagElement.TAG_THROWS.equals(tag.getTagName()) && ! TagElement.TAG_EXCEPTION.equals(tag.getTagName()))
						continue;
					if (! (tag.fragments().size() > 0 && tag.fragments().get(0) instanceof Name))
						continue;
					boolean tagDeleted= false;
					Name name= (Name) tag.fragments().get(0);
					for (int j= 0; j < fExceptionInfos.size(); j++) {
						ExceptionInfo info= (ExceptionInfo) fExceptionInfos.get(j);
						if (info.isDeleted()) {
							boolean remove= false;
							final ITypeBinding nameBinding= name.resolveTypeBinding();
							if (nameBinding != null) {
								final ITypeBinding infoBinding= info.getTypeBinding();
								if (infoBinding != null && Bindings.equals(infoBinding, nameBinding))
									remove= true;
								else if (info.getType().getElementName().equals(nameBinding.getName()))
									remove= true;
								if (remove) {
									getASTRewrite().remove(tag, fDescription);
									registerImportRemoveNode(tag);
									tagDeleted= true;
									break;
								}
							}
						}
					}
					if (! tagDeleted)
						exceptionTags.add(tag);
				}
				// reshuffle:
				tags= tagsRewrite.getRewrittenList();
				TagElement previousTag= findTagElementToInsertAfter(tags, TagElement.TAG_THROWS);
				for (Iterator infoIter= fExceptionInfos.iterator(); infoIter.hasNext();) {
					ExceptionInfo info= (ExceptionInfo) infoIter.next();
					if (info.isAdded()) {
						if (!isTopOfRipple)
							continue;
						TagElement excptNode= createExceptionTag(info.getType().getElementName());
						insertTag(excptNode, previousTag, tagsRewrite);
						previousTag= excptNode;
					} else {
						for (Iterator tagIter= exceptionTags.iterator(); tagIter.hasNext();) {
							TagElement tag= (TagElement) tagIter.next();
							Name tagName= (Name) tag.fragments().get(0);
							final ITypeBinding nameBinding= tagName.resolveTypeBinding();
							if (nameBinding != null) {
								boolean process= false;
								final ITypeBinding infoBinding= info.getTypeBinding();
								if (infoBinding != null && Bindings.equals(infoBinding, nameBinding))
									process= true;
								else if (info.getType().getElementName().equals(nameBinding.getName()))
									process= true;
								if (process) {
									tagIter.remove();
									TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag);
									getASTRewrite().remove(tag, fDescription);
									insertTag(movedTag, previousTag, tagsRewrite);
									previousTag= movedTag;
								}
							}
						}
					}
				}
				// exceptions with bad names:
				for (Iterator iter= exceptionTags.iterator(); iter.hasNext();) {
					TagElement tag= (TagElement) iter.next();
					TagElement movedTag= (TagElement) getASTRewrite().createMoveTarget(tag);
					getASTRewrite().remove(tag, fDescription);
					insertTag(movedTag, previousTag, tagsRewrite);
					previousTag= movedTag;
				}
			}
		}

		private TagElement createReturnTag() {
			TagElement returnNode= getASTRewrite().getAST().newTagElement();
			returnNode.setTagName(TagElement.TAG_RETURN);
			
			TextElement textElement= getASTRewrite().getAST().newTextElement();
			String text= StubUtility.getTodoTaskTag(fCuRewrite.getCu().getJavaProject());
			if (text != null)
				textElement.setText(text); //TODO: use template with {@todo} ...
			returnNode.fragments().add(textElement);
			
			return returnNode;
		}

		private TagElement createExceptionTag(String simpleName) {
			TagElement excptNode= getASTRewrite().getAST().newTagElement();
			excptNode.setTagName(TagElement.TAG_THROWS);

			SimpleName nameNode= getASTRewrite().getAST().newSimpleName(simpleName);
			excptNode.fragments().add(nameNode);

			TextElement textElement= getASTRewrite().getAST().newTextElement();
			String text= StubUtility.getTodoTaskTag(fCuRewrite.getCu().getJavaProject());
			if (text != null)
				textElement.setText(text); //TODO: use template with {@todo} ...
			excptNode.fragments().add(textElement);
			
			return excptNode;
		}

		private void insertTag(TagElement tag, TagElement previousTag, ListRewrite tagsRewrite) {
			if (previousTag == null)
				tagsRewrite.insertFirst(tag, fDescription);
			else
				tagsRewrite.insertAfter(tag, previousTag, fDescription);
		}

		/**
		 * @param tags existing tags
		 * @param tagName name of tag to add
		 * @return the <code>TagElement<code> just before a new <code>TagElement</code> with name <code>tagName</code>,
		 *   or <code>null</code>.
		 */
		private TagElement findTagElementToInsertAfter(List tags, String tagName) {
			List tagOrder= Arrays.asList(new String[] {
					TagElement.TAG_AUTHOR,
					TagElement.TAG_VERSION,
					TagElement.TAG_PARAM,
					TagElement.TAG_RETURN,
					TagElement.TAG_THROWS,
					TagElement.TAG_EXCEPTION,
					TagElement.TAG_SEE,
					TagElement.TAG_SINCE,
					TagElement.TAG_SERIAL,
					TagElement.TAG_SERIALFIELD,
					TagElement.TAG_SERIALDATA,
					TagElement.TAG_DEPRECATED,
					TagElement.TAG_VALUE
			});
			int goalOrdinal= tagOrder.indexOf(tagName);
			if (goalOrdinal == -1) // unknown tag -> to end
				return (tags.size() == 0) ? null : (TagElement) tags.get(tags.size());
			for (int i= 0; i < tags.size(); i++) {
				int tagOrdinal= tagOrder.indexOf(((TagElement) tags.get(i)).getTagName());
				if (tagOrdinal >= goalOrdinal)
					return (i == 0) ? null : (TagElement) tags.get(i-1);
			}
			return (tags.size() == 0) ? null : (TagElement) tags.get(tags.size()-1);
		}

		//TODO: already reported as compilation error -> don't report there?
		private void checkIfDeletedParametersUsed() {
			for (Iterator iter= getDeletedInfos().iterator(); iter.hasNext();) {
				ParameterInfo info= (ParameterInfo) iter.next();
				SingleVariableDeclaration paramDecl= (SingleVariableDeclaration) fMethDecl.parameters().get(info.getOldIndex());
				TempOccurrenceAnalyzer analyzer= new TempOccurrenceAnalyzer(paramDecl, false);
				analyzer.perform();
				SimpleName[] paramRefs= analyzer.getReferenceNodes();

				if (paramRefs.length > 0){
					RefactoringStatusContext context= JavaStatusContext.create(fCuRewrite.getCu(), paramRefs[0]);
					String typeName= getFullTypeName(fMethDecl);
					Object[] keys= new String[]{paramDecl.getName().getIdentifier(),
												fMethDecl.getName().getIdentifier(),
												typeName};
					String msg= Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_parameter_used, keys); 
					fResult.addError(msg, context);
				}
			}	
		}
		
		private String getFullTypeName(MethodDeclaration decl) {
			ASTNode node= decl;
			while (true) {
				node= node.getParent();
				if (node instanceof AbstractTypeDeclaration) {
					return ((AbstractTypeDeclaration) node).getName().getIdentifier();
				} else if (node instanceof ClassInstanceCreation) {
					ClassInstanceCreation cic= (ClassInstanceCreation) node;
					return Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_anonymous_subclass, new String[]{ASTNodes.asString(cic.getType())}); 
				} else if (node instanceof EnumConstantDeclaration) {
					EnumDeclaration ed= (EnumDeclaration) node.getParent();
					return Messages.format(RefactoringCoreMessages.ChangeSignatureRefactoring_anonymous_subclass, new String[]{ASTNodes.asString(ed.getName())}); 
				}
			}
		}
		
		protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) {
			return createNewSingleVariableDeclaration(info);	
		}
	
		private SingleVariableDeclaration createNewSingleVariableDeclaration(ParameterInfo info) {
			SingleVariableDeclaration newP= getASTRewrite().getAST().newSingleVariableDeclaration();
			newP.setName(getASTRewrite().getAST().newSimpleName(info.getNewName()));
			newP.setType(createNewTypeNode(ParameterInfo.stripEllipsis(info.getNewTypeName()), info.getNewTypeBinding()));
			newP.setVarargs(info.isNewVarargs());
			return newP;
		}
		
		protected SimpleName getMethodNameNode() {
			return fMethDecl.getName();
		}
	
	}

	class DocReferenceUpdate extends OccurrenceUpdate {
		/** instanceof MemberRef || MethodRef */
		private ASTNode fNode;

		protected DocReferenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) {
			super(cuRewrite, cuRewrite.createGroupDescription(RefactoringCoreMessages.ChangeSignatureRefactoring_update_javadoc_reference), result); 
			fNode= node;
		}

		public void updateNode() {
			if (fNode instanceof MethodRef) {
				changeParamguments();
				reshuffleElements();
			}
			if (canChangeNameAndReturnType())
				changeMethodName();
		}
		
		protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) {
			return createNewMethodRefParameter(info);
		}
		
		private MethodRefParameter createNewMethodRefParameter(ParameterInfo info) {
			MethodRefParameter newP= getASTRewrite().getAST().newMethodRefParameter();
			
			// only add name iff first parameter already has a name:
			List parameters= getParamgumentsRewrite().getOriginalList();
			if (parameters.size() > 0)
				if (((MethodRefParameter) parameters.get(0)).getName() != null)
					newP.setName(getASTRewrite().getAST().newSimpleName(info.getNewName()));
			
			newP.setType(createNewDocRefType(info));
			newP.setVarargs(info.isNewVarargs());
			return newP;
		}

		private Type createNewDocRefType(ParameterInfo info) {
			String newTypeName= ParameterInfo.stripEllipsis(info.getNewTypeName());
			ITypeBinding newTypeBinding= info.getNewTypeBinding();
			if (newTypeBinding != null)
				newTypeBinding= newTypeBinding.getErasure(); //see bug 83127: Javadoc references are raw (erasures)
			return createNewTypeNode(newTypeName, newTypeBinding);
		}

		protected SimpleName getMethodNameNode() {
			if (fNode instanceof MemberRef)
				return ((MemberRef) fNode).getName();
			
			if (fNode instanceof MethodRef)
				return ((MethodRef) fNode).getName();
			
			return null;	
		}
		
		/** @return {@inheritDoc} (element type: MethodRefParameter) */
		protected ListRewrite getParamgumentsRewrite() {
			return getASTRewrite().getListRewrite(fNode, MethodRef.PARAMETERS_PROPERTY);
		}

		protected void changeParamgumentName(ParameterInfo info) {
			if (! (fNode instanceof MethodRef))
				return;

			MethodRefParameter oldParam= (MethodRefParameter) ((MethodRef) fNode).parameters().get(info.getOldIndex());
			SimpleName oldParamName= oldParam.getName();
			if (oldParamName != null)
				getASTRewrite().set(oldParamName, SimpleName.IDENTIFIER_PROPERTY, info.getNewName(), fDescription);
		}
		
		protected void changeParamgumentType(ParameterInfo info) {
			if (! (fNode instanceof MethodRef))
				return;
			
			MethodRefParameter oldParam= (MethodRefParameter) ((MethodRef) fNode).parameters().get(info.getOldIndex());
			Type oldTypeNode= oldParam.getType();
			Type newTypeNode= createNewDocRefType(info);
			if (info.isNewVarargs()) {
				if (info.isOldVarargs() && ! oldParam.isVarargs()) {
					// leave as array reference of old reference was not vararg
					newTypeNode= getASTRewrite().getAST().newArrayType(newTypeNode);
				} else {
					getASTRewrite().set(oldParam, MethodRefParameter.VARARGS_PROPERTY, Boolean.TRUE, fDescription);
				}
			} else {
				if (oldParam.isVarargs()) {
					getASTRewrite().set(oldParam, MethodRefParameter.VARARGS_PROPERTY, Boolean.FALSE, fDescription);
				}
			}
			
			getASTRewrite().replace(oldTypeNode, newTypeNode, fDescription);
			registerImportRemoveNode(oldTypeNode);
		}
	}
	
	class StaticImportUpdate extends OccurrenceUpdate {

		private final ImportDeclaration fImportDecl;

		public StaticImportUpdate(ImportDeclaration importDecl, CompilationUnitRewrite cuRewrite, RefactoringStatus result) {
			super(cuRewrite, null, result);
			fImportDecl= importDecl;
		}

		public void updateNode() throws JavaModelException {
			ImportRewrite importRewrite= fCuRewrite.getImportRewrite();
			QualifiedName name= (QualifiedName) fImportDecl.getName();
			//will be removed by importRemover if not used elsewhere ... importRewrite.removeStaticImport(name.getFullyQualifiedName());
			importRewrite.addStaticImport(name.getQualifier().getFullyQualifiedName(), fMethodName, false);
		}

		protected ListRewrite getParamgumentsRewrite() {
			return null;
		}

		protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) {
			return null;
		}

		protected SimpleName getMethodNameNode() {
			return null;
		}
	}
	
	class NullOccurrenceUpdate extends OccurrenceUpdate {
		private ASTNode fNode;
		protected NullOccurrenceUpdate(ASTNode node, CompilationUnitRewrite cuRewrite, RefactoringStatus result) {
			super(cuRewrite, null, result);
			fNode= node;
		}
		public void updateNode() throws JavaModelException {
			int start= fNode.getStartPosition();
			int length= fNode.getLength();
			String msg= "Cannot update found node: nodeType=" + fNode.getNodeType() + "; "  //$NON-NLS-1$//$NON-NLS-2$
					+ fNode.toString() + "[" + start + ", " + length + "]";  //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$
			JavaPlugin.log(new Exception(msg + ":\n" + fCuRewrite.getCu().getSource().substring(start, start + length))); //$NON-NLS-1$
			fResult.addError(msg, JavaStatusContext.create(fCuRewrite.getCu(), fNode));
		}
		protected ListRewrite getParamgumentsRewrite() {
			return null;
		}
		protected ASTNode createNewParamgument(ParameterInfo info, List parameterInfos, List nodes) {
			return null;
		}
		protected SimpleName getMethodNameNode() {
			return null;
		}
	}

	public RefactoringStatus initialize(final RefactoringArguments arguments) {
		if (arguments instanceof JavaRefactoringArguments) {
			final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
			final String handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_INPUT);
			if (handle != null) {
				final IJavaElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
				if (element == null || !element.exists() || element.getElementType() != IJavaElement.METHOD)
					return createInputFatalStatus(element, IJavaRefactorings.CHANGE_METHOD_SIGNATURE);
				else {
					fMethod= (IMethod) element;
					fMethodName= fMethod.getElementName();
					try {
						fVisibility= JdtFlags.getVisibilityCode(fMethod);
						fReturnTypeInfo= new ReturnTypeInfo(Signature.toString(Signature.getReturnType(fMethod.getSignature())));
					} catch (JavaModelException exception) {
						return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, ATTRIBUTE_VISIBILITY));
					}
				}
			} else
				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_INPUT));
			final String name= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_NAME);
			if (name != null) {
				fMethodName= name;
				final RefactoringStatus status= Checks.checkMethodName(fMethodName);
				if (status.hasError())
					return status;
			} else
				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_NAME));
			final String type= extended.getAttribute(ATTRIBUTE_RETURN);
			if (type != null && !"".equals(type)) //$NON-NLS-1$
				fReturnTypeInfo= new ReturnTypeInfo(type);
			final String visibility= extended.getAttribute(ATTRIBUTE_VISIBILITY);
			if (visibility != null && !"".equals(visibility)) {//$NON-NLS-1$
				int flag= 0;
				try {
					flag= Integer.parseInt(visibility);
				} catch (NumberFormatException exception) {
					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_VISIBILITY));
				}
				fVisibility= flag;
			}
			int count= 1;
			String attribute= ATTRIBUTE_PARAMETER + count;
			String value= null;
			fParameterInfos= new ArrayList(3);
			while ((value= extended.getAttribute(attribute)) != null) {
				StringTokenizer tokenizer= new StringTokenizer(value);
				if (tokenizer.countTokens() < 6)
					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, ATTRIBUTE_PARAMETER));
				String oldTypeName= tokenizer.nextToken();
				String oldName= tokenizer.nextToken();
				String oldIndex= tokenizer.nextToken();
				String newTypeName= tokenizer.nextToken();
				String newName= tokenizer.nextToken();
				String deleted= tokenizer.nextToken();
				ParameterInfo info= null;
				try {
					info= new ParameterInfo(oldTypeName, oldName, Integer.valueOf(oldIndex).intValue());
					info.setNewTypeName(newTypeName);
					info.setNewName(newName);
					if (Boolean.valueOf(deleted).booleanValue())
						info.markAsDeleted();
					fParameterInfos.add(info);
				} catch (NumberFormatException exception) {
					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, ATTRIBUTE_PARAMETER));
				}
				final String result= extended.getAttribute(ATTRIBUTE_DEFAULT + count);
				if (result != null && !"".equals(result)) //$NON-NLS-1$
					info.setDefaultValue(result);
				count++;
				attribute= ATTRIBUTE_PARAMETER + count;
			}
			count= 1;
			fExceptionInfos= new ArrayList(2);
			attribute= JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + count;
			while ((value= extended.getAttribute(attribute)) != null) {
				ExceptionInfo info= null;
				final String kind= extended.getAttribute(ATTRIBUTE_KIND + count);
				if (kind != null) {
					final IJavaElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), value, false);
					if (element == null || !element.exists())
						return createInputFatalStatus(element, IJavaRefactorings.CHANGE_METHOD_SIGNATURE);
					else {
						try {
							info= new ExceptionInfo((IType) element, Integer.valueOf(kind).intValue(), null);
						} catch (NumberFormatException exception) {
							return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, ATTRIBUTE_KIND));
						}
					}
				} else
					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, ATTRIBUTE_KIND));
				fExceptionInfos.add(info);
				count++;
				attribute= JDTRefactoringDescriptor.ATTRIBUTE_ELEMENT + count;
			}
			final String deprecate= extended.getAttribute(ATTRIBUTE_DEPRECATE);
			if (deprecate != null) {
				fDelegateDeprecation= Boolean.valueOf(deprecate).booleanValue();
			} else
				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DEPRECATE));
			final String delegate= extended.getAttribute(ATTRIBUTE_DELEGATE);
			if (delegate != null) {
				fDelegateUpdating= Boolean.valueOf(delegate).booleanValue();
			} else
				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DELEGATE));
		} else
			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
		return new RefactoringStatus();
	}

	/**
	 * If this occurrence update is called from within a declaration update
	 * (i.e., to update the call inside the newly created delegate), the old
	 * node does not yet exist and therefore cannot be a move target.
	 * 
	 * Normally, always use createMoveTarget as this has the advantage of
	 * being able to add changes inside changed nodes (for example, a method
	 * call within a method call, see test case #4) and preserving comments
	 * inside calls.
	 * @param oldNode original node
	 * @param rewrite an AST rewrite
	 * @return the node to insert at the target location
	 */
	protected ASTNode moveNode(ASTNode oldNode, ASTRewrite rewrite) {
		ASTNode movedNode;
		if (ASTNodes.isExistingNode(oldNode))
			movedNode= rewrite.createMoveTarget(oldNode); //node must be one of ast
		else
			movedNode= ASTNode.copySubtree(rewrite.getAST(), oldNode);
		return movedNode;
	}
	
	public String getDelegateUpdatingTitle(boolean plural) {
		if (plural)
			return RefactoringCoreMessages.DelegateCreator_keep_original_changed_plural;
		else
			return RefactoringCoreMessages.DelegateCreator_keep_original_changed_singular;
	}

	public IDefaultValueAdvisor getDefaultValueAdvisor() {
		return fDefaultValueAdvisor;
	}

	public void setDefaultValueAdvisor(IDefaultValueAdvisor defaultValueAdvisor) {
		fDefaultValueAdvisor= defaultValueAdvisor;
	}
}
