content: /*******************************************************************************
   * Copyright (c) 2000, 2009 IBM Corporation and others.
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Eclipse Public License v1.0
   * which accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   *
   * Contributors:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.corext.refactoring.code;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.StringTokenizer;
  
  import org.eclipse.core.filebuffers.FileBuffers;
  import org.eclipse.core.filebuffers.ITextFileBufferManager;
  import org.eclipse.core.filebuffers.LocationKind;
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IPath;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.core.runtime.SubProgressMonitor;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.RefactoringChangeDescriptor;
  import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.TextFileChange;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
  import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker;
  import org.eclipse.text.edits.MultiTextEdit;
  import org.eclipse.text.edits.TextEdit;
  import org.eclipse.text.edits.TextEditGroup;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.dom.AST;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ASTVisitor;
  import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration;
  import org.eclipse.wst.jsdt.core.dom.AnonymousClassDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Assignment;
  import org.eclipse.wst.jsdt.core.dom.Block;
  import org.eclipse.wst.jsdt.core.dom.BodyDeclaration;
  import org.eclipse.wst.jsdt.core.dom.ChildListPropertyDescriptor;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.ExpressionStatement;
  import org.eclipse.wst.jsdt.core.dom.FieldAccess;
  import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
  import org.eclipse.wst.jsdt.core.dom.FunctionInvocation;
  import org.eclipse.wst.jsdt.core.dom.IFunctionBinding;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.IVariableBinding;
  import org.eclipse.wst.jsdt.core.dom.JSdoc;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.Modifier;
  import org.eclipse.wst.jsdt.core.dom.QualifiedName;
  import org.eclipse.wst.jsdt.core.dom.ReturnStatement;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Type;
  import org.eclipse.wst.jsdt.core.dom.TypeDeclaration;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationStatement;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ImportRewrite;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ListRewrite;
  import org.eclipse.wst.jsdt.core.refactoring.IJavaScriptRefactorings;
  import org.eclipse.wst.jsdt.core.refactoring.descriptors.JavaScriptRefactoringDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTFlattener;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodeFactory;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodes;
  import org.eclipse.wst.jsdt.internal.corext.dom.Bindings;
  import org.eclipse.wst.jsdt.internal.corext.dom.BodyDeclarationRewrite;
  import org.eclipse.wst.jsdt.internal.corext.dom.LinkedNodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.dom.Selection;
  import org.eclipse.wst.jsdt.internal.corext.dom.StatementRewrite;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JavaRefactoringArguments;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.ParameterInfo;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.CompilationUnitChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.ResourceUtil;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.SelectionAwareSourceRangeComputer;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.BindingLabelProvider;
  import org.eclipse.wst.jsdt.ui.CodeGeneration;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  /**
   * Extracts a method in a compilation unit based on a text selection range.
   */
  public class ExtractMethodRefactoring extends ScriptableRefactoring {
  
  	private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$
  	private static final String ATTRIBUTE_DESTINATION= "destination"; //$NON-NLS-1$
  	private static final String ATTRIBUTE_COMMENTS= "comments"; //$NON-NLS-1$
  	private static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$
  	private static final String ATTRIBUTE_EXCEPTIONS= "exceptions"; //$NON-NLS-1$
  
  	private IJavaScriptUnit fCUnit;
  	private JavaScriptUnit fRoot;
  	private ImportRewrite fImportRewriter;
  	private int fSelectionStart;
  	private int fSelectionLength;
  	private AST fAST;
  	private ASTRewrite fRewriter;
  	private IDocument fDocument;
  	private ExtractMethodAnalyzer fAnalyzer;
  	private int fVisibility;
  	private String fMethodName;
  	private boolean fThrowRuntimeExceptions;
  	private List fParameterInfos;
  	private Set fUsedNames;
  	private boolean fGenerateJavadoc;
  	private boolean fReplaceDuplicates;
  	private SnippetFinder.Match[] fDuplicates;
  	private int fDestinationIndex= 0;
  	// either of type TypeDeclaration or AnonymousClassDeclaration
  	private ASTNode fDestination;
  	// either of type TypeDeclaration or AnonymousClassDeclaration
  	private ASTNode[] fDestinations;
  
  	private static final String EMPTY= ""; //$NON-NLS-1$
  	
  	private static class UsedNamesCollector extends ASTVisitor {
  		private Set result= new HashSet();
  		private Set fIgnore= new HashSet();
  		public static Set perform(ASTNode[] nodes) {
  			UsedNamesCollector collector= new UsedNamesCollector();
  			for (int i= 0; i < nodes.length; i++) {
  				nodes[i].accept(collector);
  			}
  			return collector.result;
  		}
  		public boolean visit(FieldAccess node) {
  			Expression exp= node.getExpression();
  			if (exp != null)
  				fIgnore.add(node.getName());
  			return true;
  		}
  		public void endVisit(FieldAccess node) {
  			fIgnore.remove(node.getName());
  		}
  		public boolean visit(FunctionInvocation node) {
  			Expression exp= node.getExpression();
  			if (exp != null) {
  				SimpleName name = node.getName();
  				if (name!=null)
  					fIgnore.add(name);
  			}
  			return true;
  		}
  		public void endVisit(FunctionInvocation node) {
  			SimpleName name = node.getName();
  			if (name!=null)
  				fIgnore.remove(name);
  		}
  		public boolean visit(QualifiedName node) {
  			fIgnore.add(node.getName());
  			return true;
  		}
  		public void endVisit(QualifiedName node) {
  			fIgnore.remove(node.getName());
  		}
  		public boolean visit(SimpleName node) {
  			if (!fIgnore.contains(node))
  				result.add(node.getIdentifier());
  			return true;
  		}
  		public boolean visit(TypeDeclaration node) {
  			return visitType(node);
  		}
  		private boolean visitType(AbstractTypeDeclaration node) {
  			result.add(node.getName().getIdentifier());
  			// don't dive into type declaration since they open a new
  			// context.
  			return false;
  		}
  	}
  
  	/**
  	 * Creates a new extract method refactoring
  	 * @param unit the compilation unit, or <code>null</code> if invoked by scripting
  	 * @param selectionStart
  	 * @param selectionLength
  	 */
  	public ExtractMethodRefactoring(IJavaScriptUnit unit, int selectionStart, int selectionLength) throws CoreException {
  		fCUnit= unit;
  		fMethodName= "extracted"; //$NON-NLS-1$
  		fSelectionStart= selectionStart;
  		fSelectionLength= selectionLength;
  		fVisibility= -1;
  		if (unit != null)
  			initialize(unit);
  	}
  
  	private void initialize(IJavaScriptUnit cu) throws CoreException {
  		fImportRewriter= StubUtility.createImportRewrite(cu, true);
  	}
  
  	 public String getName() {
  	 	return RefactoringCoreMessages.ExtractMethodRefactoring_name; 
  	 }
  
  	/**
  	 * Checks if the refactoring can be activated. Activation typically means, if a
  	 * corresponding menu entry can be added to the UI.
  	 *
  	 * @param pm a progress monitor to report progress during activation checking.
  	 * @return the refactoring status describing the result of the activation check.	 
  	 */
  	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
  		RefactoringStatus result= new RefactoringStatus();
  		pm.beginTask("", 100); //$NON-NLS-1$
  		
  		if (fSelectionStart < 0 || fSelectionLength == 0)
  			return mergeTextSelectionStatus(result);
  		
  		IFile[] changedFiles= ResourceUtil.getFiles(new IJavaScriptUnit[]{fCUnit});
  		result.merge(Checks.validateModifiesFiles(changedFiles, getValidationContext()));
  		if (result.hasFatalError())
  			return result;
  		result.merge(ResourceChangeChecker.checkFilesToBeChanged(changedFiles, new SubProgressMonitor(pm, 1)));
  		
  		fRoot= RefactoringASTParser.parseWithASTProvider(fCUnit, true, new SubProgressMonitor(pm, 99));
  		fAST= fRoot.getAST();
  		fRoot.accept(createVisitor());
  		
  		result.merge(fAnalyzer.checkInitialConditions(fImportRewriter));
  		if (result.hasFatalError())
  			return result;
  		if (fVisibility == -1) {
  //			setVisibility(Modifier.PRIVATE);
  			fVisibility=0;
  		}
  		initializeParameterInfos();
  		initializeUsedNames();
  		initializeDuplicates();
  		initializeDestinations();
  		return result;
  	}
  	
  	private ASTVisitor createVisitor() throws JavaScriptModelException {
  		fAnalyzer= new ExtractMethodAnalyzer(fCUnit, Selection.createFromStartLength(fSelectionStart, fSelectionLength));
  		return fAnalyzer;
  	}
  	
  	/**
  	 * Sets the method name to be used for the extracted method.
  	 *
  	 * @param name the new method name.
  	 */	
  	public void setMethodName(String name) {
  		fMethodName= name;
  	}
  	
  	/**
  	 * Returns the method name to be used for the extracted method.
  	 * @return the method name to be used for the extracted method.
  	 */
  	public String getMethodName() {
  		return fMethodName;
  	} 
  	
  	/**
  	 * Sets the visibility of the new method.
  	 * 
  	 * @param visibility the visibility of the new method. Valid values are
  	 *  "public", "protected", "", and "private"
  	 */
  	public void setVisibility(int visibility) {
  		fVisibility= visibility;
  	}
  	
  	/**
  	 * Returns the visibility of the new method.
  	 * 
  	 * @return the visibility of the new method
  	 */
  	public int getVisibility() {
  		return fVisibility;
  	}
  	
  	/**
  	 * Returns the parameter infos.
  	 * @return a list of parameter infos.
  	 */
  	public List getParameterInfos() {
  		return fParameterInfos;
  	}
  	
  	/**
  	 * Sets whether the new method signature throws runtime exceptions.
  	 * 
  	 * @param throwRuntimeExceptions flag indicating if the new method
  	 * 	throws runtime exceptions
  	 */
  	public void setThrowRuntimeExceptions(boolean throwRuntimeExceptions) {
  		fThrowRuntimeExceptions= throwRuntimeExceptions;
  	}
  	
  	/**
  	 * Checks if the new method name is a valid method name. This method doesn't
  	 * check if a method with the same name already exists in the hierarchy. This
  	 * check is done in <code>checkInput</code> since it is expensive.
  	 */
  	public RefactoringStatus checkMethodName() {
  		return Checks.checkMethodName(fMethodName);
  	}
  	
  	public ASTNode[] getDestinations() {
  		return fDestinations;
  	}
  	
  	public void setDestination(int index) {
  		fDestination= fDestinations[index];
  		fDestinationIndex= index;
  	}
  	
  	/**
  	 * Checks if the parameter names are valid.
  	 */
  	public RefactoringStatus checkParameterNames() {
  		RefactoringStatus result= new RefactoringStatus();
  		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
  			ParameterInfo parameter= (ParameterInfo)iter.next();
  			result.merge(Checks.checkIdentifier(parameter.getNewName()));
  			for (Iterator others= fParameterInfos.iterator(); others.hasNext();) {
  				ParameterInfo other= (ParameterInfo) others.next();
  				if (parameter != other && other.getNewName().equals(parameter.getNewName())) {
  					result.addError(Messages.format(
  						RefactoringCoreMessages.ExtractMethodRefactoring_error_sameParameter, 
  						other.getNewName()));
  					return result;
  				}
  			}
  			if (parameter.isRenamed() && fUsedNames.contains(parameter.getNewName())) {
  				result.addError(Messages.format(
  					RefactoringCoreMessages.ExtractMethodRefactoring_error_nameInUse, 
  					parameter.getNewName()));
  				return result;
  			}
  		}
  		return result;
  	}
  	
  	/**
  	 * Checks if varargs are ordered correctly.
  	 */
  	public RefactoringStatus checkVarargOrder() {
  		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
  			ParameterInfo info= (ParameterInfo)iter.next();
  			if (info.isOldVarargs() && iter.hasNext()) {
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(
  					 RefactoringCoreMessages.ExtractMethodRefactoring_error_vararg_ordering,
  					 info.getOldName()));
  			}
  		}
  		return new RefactoringStatus();
  	}
  	
  	/**
  	 * Returns the names already in use in the selected statements/expressions.
  	 * 
  	 * @return names already in use.
  	 */
  	public Set getUsedNames() {
  		return fUsedNames;
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared in Refactoring
  	 */
  	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
  		pm.beginTask(RefactoringCoreMessages.ExtractMethodRefactoring_checking_new_name, 2); 
  		pm.subTask(EMPTY);
  		
  		RefactoringStatus result= checkMethodName();
  		result.merge(checkParameterNames());
  		result.merge(checkVarargOrder());
  		pm.worked(1);
  		if (pm.isCanceled())
  			throw new OperationCanceledException();
  
  		BodyDeclaration node= fAnalyzer.getEnclosingBodyDeclaration();
  		if (node != null) {
  			fAnalyzer.checkInput(result, fMethodName, fAST);
  			pm.worked(1);
  		}
  		pm.done();
  		return result;
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared in IRefactoring
  	 */
  	public Change createChange(IProgressMonitor pm) throws CoreException {
  		if (fMethodName == null)
  			return null;
  		pm.beginTask("", 2); //$NON-NLS-1$
  		fAnalyzer.aboutToCreateChange();
  		BodyDeclaration declaration= fAnalyzer.getEnclosingBodyDeclaration();
  		fRewriter= ASTRewrite.create(declaration.getAST());
  		final Map arguments= new HashMap();
  		String project= null;
  		IJavaScriptProject javaProject= fCUnit.getJavaScriptProject();
  		if (javaProject != null)
  			project= javaProject.getElementName();
  		ITypeBinding type= null;
  		if (fDestination instanceof AbstractTypeDeclaration) {
  			final AbstractTypeDeclaration decl= (AbstractTypeDeclaration) fDestination;
  			type= decl.resolveBinding();
  		} else if (fDestination instanceof AnonymousClassDeclaration) {
  			final AnonymousClassDeclaration decl= (AnonymousClassDeclaration) fDestination;
  			type= decl.resolveBinding();
  		}
  		IFunctionBinding method= null;
  		final BodyDeclaration enclosing= fAnalyzer.getEnclosingBodyDeclaration();
  		if (enclosing instanceof FunctionDeclaration) {
  			final FunctionDeclaration node= (FunctionDeclaration) enclosing;
  			method= node.resolveBinding();
  		}
  		final int flags= RefactoringDescriptor.STRUCTURAL_CHANGE | JavaScriptRefactoringDescriptor.JAR_REFACTORING | JavaScriptRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
  		final String description= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_descriptor_description_short, fMethodName);
  		final String label= method != null ? BindingLabelProvider.getBindingLabel(method, JavaScriptElementLabels.ALL_FULLY_QUALIFIED) : '{' + JavaScriptElementLabels.ELLIPSIS_STRING + '}';
  		final String header= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_descriptor_description, new String[] { getSignature(), label, BindingLabelProvider.getBindingLabel(type, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)});
  		final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
  		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_name_pattern, fMethodName));
  		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_destination_pattern, BindingLabelProvider.getBindingLabel(type, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)));
  //		String visibility= JdtFlags.getVisibilityString(fVisibility);
  //		if ("".equals(visibility)) //$NON-NLS-1$
  //			visibility= RefactoringCoreMessages.ExtractMethodRefactoring_default_visibility;
  //		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_visibility_pattern, visibility));
  //		if (fThrowRuntimeExceptions)
  //			comment.addSetting(RefactoringCoreMessages.ExtractMethodRefactoring_declare_thrown_exceptions);
  		if (fReplaceDuplicates)
  			comment.addSetting(RefactoringCoreMessages.ExtractMethodRefactoring_replace_occurrences);
  		if (fGenerateJavadoc)
  			comment.addSetting(RefactoringCoreMessages.ExtractMethodRefactoring_generate_comment);
  		final JDTRefactoringDescriptor descriptor= new JDTRefactoringDescriptor(IJavaScriptRefactorings.EXTRACT_METHOD, project, description, comment.asString(), arguments, flags);
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fCUnit));
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_NAME, fMethodName);
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_SELECTION, Integer.valueOf(fSelectionStart).toString() + " " + Integer.valueOf(fSelectionLength).toString()); //$NON-NLS-1$
  //		arguments.put(ATTRIBUTE_VISIBILITY, Integer.valueOf(fVisibility).toString());
  		arguments.put(ATTRIBUTE_DESTINATION, Integer.valueOf(fDestinationIndex).toString());
  //		arguments.put(ATTRIBUTE_EXCEPTIONS, Boolean.valueOf(fThrowRuntimeExceptions).toString());
  		arguments.put(ATTRIBUTE_COMMENTS, Boolean.valueOf(fGenerateJavadoc).toString());
  		arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(fReplaceDuplicates).toString());
  		final CompilationUnitChange result= new CompilationUnitChange(RefactoringCoreMessages.ExtractMethodRefactoring_change_name, fCUnit);
  		result.setSaveMode(TextFileChange.KEEP_SAVE_STATE);
  		result.setDescriptor(new RefactoringChangeDescriptor(descriptor));
  
  		MultiTextEdit root= new MultiTextEdit();
  		result.setEdit(root);
  		// This is cheap since the compilation unit is already open in a editor.
  		IPath path= ((IFile)fCUnit.getPrimary().getResource()).getFullPath();
  		ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager();
  		try {
  			bufferManager.connect(path, LocationKind.IFILE, new SubProgressMonitor(pm, 1));
  			fDocument= bufferManager.getTextFileBuffer(path, LocationKind.IFILE).getDocument();
  			
  			ASTNode[] selectedNodes= fAnalyzer.getSelectedNodes();
  			fRewriter.setTargetSourceRangeComputer(new SelectionAwareSourceRangeComputer(selectedNodes,
  				fDocument, fSelectionStart, fSelectionLength));
  			
  			TextEditGroup substituteDesc= new TextEditGroup(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_substitute_with_call, fMethodName)); 
  			result.addTextEditGroup(substituteDesc);
  			
  			FunctionDeclaration mm= createNewMethod(fMethodName, true, selectedNodes, fDocument.getLineDelimiter(0), substituteDesc);
  
  			TextEditGroup insertDesc= new TextEditGroup(Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_add_method, fMethodName)); 
  			result.addTextEditGroup(insertDesc);
  			
  			ASTNode afterDecl = declaration.getBodyChild();
  			if (fDestination == fDestinations[0] || afterDecl==null) {
  				ChildListPropertyDescriptor desc= (ChildListPropertyDescriptor)afterDecl.getLocationInParent();
  				ListRewrite container= fRewriter.getListRewrite(afterDecl.getParent(), desc);
  				container.insertAfter(mm, afterDecl, insertDesc);
  			} else {
  				BodyDeclarationRewrite container= BodyDeclarationRewrite.create(fRewriter, fDestination);
  				container.insert(mm, insertDesc);
  			}
  			
  			replaceDuplicates(result);
  		
  			if (fImportRewriter.hasRecordedChanges()) {
  				TextEdit edit= fImportRewriter.rewriteImports(null);
  				root.addChild(edit);
  				result.addTextEditGroup(new TextEditGroup(
  					RefactoringCoreMessages.ExtractMethodRefactoring_organize_imports, 
  					new TextEdit[] {edit}
  				));
  			}
  			root.addChild(fRewriter.rewriteAST(fDocument, fCUnit.getJavaScriptProject().getOptions(true)));
  		} catch (BadLocationException e) {
  			throw new CoreException(new Status(IStatus.ERROR, JavaScriptPlugin.getPluginId(), IStatus.ERROR,
  				e.getMessage(), e));
  		} finally {
  			bufferManager.disconnect(path, LocationKind.IFILE, new SubProgressMonitor(pm, 1));
  			pm.done();
  		}
  		return result;
  	}
  	
  	/**
  	 * Returns the signature of the new method.
  	 * 
  	 * @return the signature of the extracted method
  	 */
  	public String getSignature() {
  		return getSignature(fMethodName);
  	}
  	
  	/**
  	 * Returns the signature of the new method.
  	 * 
  	 * @param methodName the method name used for the new method
  	 * @return the signature of the extracted method
  	 */
  	public String getSignature(String methodName) {
  		FunctionDeclaration method= null;
  		try {
  			method= createNewMethod(methodName, false, null, StubUtility.getLineDelimiterUsed(fCUnit), null);
  		} catch (CoreException cannotHappen) {
  			// we don't generate a code block and java comments.
  			Assert.isTrue(false);
  		} catch (BadLocationException e) {
  			// we don't generate a code block and java comments.
  			Assert.isTrue(false);
  		}
  		method.setBody(fAST.newBlock());
  		ASTFlattener flattener= new ASTFlattener() {
  			public boolean visit(Block node) {
  				return false;
  			}
  		};
  		method.accept(flattener);
  		return flattener.getResult();		
  	}
  	
  	/**
  	 * Returns the number of duplicate code snippets found.
  	 * 
  	 * @return the number of duplicate code fragments
  	 */
  	public int getNumberOfDuplicates() {
  		if (fDuplicates == null)
  			return 0;
  		int result=0;
  		for (int i= 0; i < fDuplicates.length; i++) {
  			if (!fDuplicates[i].isMethodBody())
  				result++;
  		}
  		return result;
  	}
  	
  	public boolean getReplaceDuplicates() {
  		return fReplaceDuplicates;
  	}
  
  	public void setReplaceDuplicates(boolean replace) {
  		fReplaceDuplicates= replace;
  	}
  	
  	public void setGenerateJavadoc(boolean generate) {
  		fGenerateJavadoc= generate;
  	}
  	
  	public boolean getGenerateJavadoc() {
  		return fGenerateJavadoc;
  	}
  	
  	//---- Helper methods ------------------------------------------------------------------------
  	
  	private void initializeParameterInfos() {
  		IVariableBinding[] arguments= fAnalyzer.getArguments();
  		fParameterInfos= new ArrayList(arguments.length);
  		ASTNode root= fAnalyzer.getEnclosingBodyDeclaration();
  		ParameterInfo vararg= null;
  		for (int i= 0; i < arguments.length; i++) {
  			IVariableBinding argument= arguments[i];
  			if (argument == null)
  				continue;
  			VariableDeclaration declaration= ASTNodes.findVariableDeclaration(argument, root);
  			boolean isVarargs= declaration instanceof SingleVariableDeclaration 
  				? ((SingleVariableDeclaration)declaration).isVarargs()
  				: false;
  			ParameterInfo info= new ParameterInfo(argument, getType(declaration, isVarargs), argument.getName(), i);
  			if (isVarargs) {
  				vararg= info;
  			} else {
  				fParameterInfos.add(info);
  			}
  		}
  		if (vararg != null) {
  			fParameterInfos.add(vararg);
  		}
  	}
  	
  	private void initializeUsedNames() {
  		fUsedNames= UsedNamesCollector.perform(fAnalyzer.getSelectedNodes());
  		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
  			ParameterInfo parameter= (ParameterInfo)iter.next();
  			fUsedNames.remove(parameter.getOldName());
  		}
  	}
  	
  	private void initializeDuplicates() {
  		ASTNode start= fAnalyzer.getEnclosingBodyDeclaration();
  		while(!(start instanceof JavaScriptUnit) &&
  				!(start instanceof AbstractTypeDeclaration) &&
  				!(start instanceof AnonymousClassDeclaration)) {
  			start= start.getParent();
  		}
  		
  		fDuplicates= SnippetFinder.perform(start, fAnalyzer.getSelectedNodes());
  		fReplaceDuplicates= fDuplicates.length > 0 && ! fAnalyzer.isLiteralNodeSelected();
  	}
  	
  	private void initializeDestinations() {
  		List result= new ArrayList();
  		BodyDeclaration decl= fAnalyzer.getEnclosingBodyDeclaration();
  		ASTNode current= getNextParent(decl);
  		result.add(current);
  		if (decl instanceof FunctionDeclaration) {
  			ITypeBinding binding= ASTNodes.getEnclosingType(current);
  			ASTNode next= getNextParent(current);
  			while (next != null && binding != null && binding.isNested() && !Modifier.isStatic(binding.getDeclaredModifiers())) {
  				result.add(next);
  				current= next;
  				binding= ASTNodes.getEnclosingType(current);
  				next= getNextParent(next);
  			}
  		}
  		fDestinations= (ASTNode[])result.toArray(new ASTNode[result.size()]);
  		fDestination= fDestinations[fDestinationIndex];
  	}
  	
  	private ASTNode getNextParent(ASTNode node) {
  		do {
  			node= node.getParent();
  		} while (node != null && !(
  				(node instanceof JavaScriptUnit) ||(node instanceof AbstractTypeDeclaration) || (node instanceof AnonymousClassDeclaration)));
  		return node;
  	}
  		
  	private RefactoringStatus mergeTextSelectionStatus(RefactoringStatus status) {
  		status.addFatalError(RefactoringCoreMessages.ExtractMethodRefactoring_no_set_of_statements); 
  		return status;	
  	}
  	
  	private String getType(VariableDeclaration declaration, boolean isVarargs) {
  		String type= ASTNodes.asString(ASTNodeFactory.newType(declaration.getAST(), declaration));
  		if (isVarargs)
  			return type + ParameterInfo.ELLIPSIS;
  		else
  			return type;
  	}
  	
  	//---- Code generation -----------------------------------------------------------------------
  	
  	private ASTNode[] createCallNodes(SnippetFinder.Match duplicate) {
  		List result= new ArrayList(2);
  		
  		IVariableBinding[] locals= fAnalyzer.getCallerLocals();
  		for (int i= 0; i < locals.length; i++) {
  			result.add(createDeclaration(locals[i], null));
  		}
  		
  		FunctionInvocation invocation= fAST.newFunctionInvocation();
  		invocation.setName(fAST.newSimpleName(fMethodName));
  		List arguments= invocation.arguments();
  		for (int i= 0; i < fParameterInfos.size(); i++) {
  			ParameterInfo parameter= ((ParameterInfo)fParameterInfos.get(i));
  			arguments.add(ASTNodeFactory.newName(fAST, getMappedName(duplicate, parameter)));
  		}		
  		
  		ASTNode call;		
  		int returnKind= fAnalyzer.getReturnKind();
  		switch (returnKind) {
  			case ExtractMethodAnalyzer.ACCESS_TO_LOCAL:
  				IVariableBinding binding= fAnalyzer.getReturnLocal();
  				if (binding != null) {
  					VariableDeclarationStatement decl= createDeclaration(getMappedBinding(duplicate, binding), invocation);
  					call= decl;
  				} else {
  					Assignment assignment= fAST.newAssignment();
  					assignment.setLeftHandSide(ASTNodeFactory.newName(fAST, 
  							getMappedBinding(duplicate, fAnalyzer.getReturnValue()).getName()));
  					assignment.setRightHandSide(invocation);
  					call= assignment;
  				}
  				break;
  			case ExtractMethodAnalyzer.RETURN_STATEMENT_VALUE:
  				ReturnStatement rs= fAST.newReturnStatement();
  				rs.setExpression(invocation);
  				call= rs;
  				break;
  			default:
  				call= invocation;
  		}
  		
  		if (call instanceof Expression && !fAnalyzer.isExpressionSelected()) {
  			call= fAST.newExpressionStatement((Expression)call);
  		}
  		result.add(call);
  		
  		// We have a void return statement. The code looks like
  		// extracted();
  		// return;	
  		if (returnKind == ExtractMethodAnalyzer.RETURN_STATEMENT_VOID && !fAnalyzer.isLastStatementSelected()) {
  			result.add(fAST.newReturnStatement());
  		}
  		return (ASTNode[])result.toArray(new ASTNode[result.size()]);		
  	}
  	
  	private IVariableBinding getMappedBinding(SnippetFinder.Match duplicate, IVariableBinding org) {
  		if (duplicate == null)
  			return org;
  		return duplicate.getMappedBinding(org);
  	}
  	
  	private String getMappedName(SnippetFinder.Match duplicate, ParameterInfo paramter) {
  		if (duplicate == null)
  			return paramter.getOldName();
  		return duplicate.getMappedName(paramter.getOldBinding()).getIdentifier();
  	}
  	
  	private void replaceDuplicates(CompilationUnitChange result) {
  		int numberOf= getNumberOfDuplicates();
  		if (numberOf == 0 || !fReplaceDuplicates)
  			return;
  		String label= null;
  		if (numberOf == 1)
  			label= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_duplicates_single, fMethodName); 
  		else
  			label= Messages.format(RefactoringCoreMessages.ExtractMethodRefactoring_duplicates_multi, fMethodName); 
  		
  		TextEditGroup description= new TextEditGroup(label);
  		result.addTextEditGroup(description);
  		
  		for (int d= 0; d < fDuplicates.length; d++) {
  			SnippetFinder.Match duplicate= fDuplicates[d];
  			if (!duplicate.isMethodBody()) {
  				ASTNode[] callNodes= createCallNodes(duplicate);
  				new StatementRewrite(fRewriter, duplicate.getNodes()).replace(callNodes, description);
  			}
  		}		
  	}
  	
  	private FunctionDeclaration createNewMethod(String name, boolean code, ASTNode[] selectedNodes, String lineDelimiter, TextEditGroup substitute) throws CoreException, BadLocationException {
  		FunctionDeclaration result= fAST.newFunctionDeclaration();
  		int modifiers= fVisibility;
  		if (Modifier.isStatic(fAnalyzer.getEnclosingBodyDeclaration().getModifiers()) || fAnalyzer.getForceStatic()) {
  			modifiers|= Modifier.STATIC;
  		}
  		
  		result.modifiers().addAll(ASTNodeFactory.newModifiers(fAST, modifiers));
  		result.setReturnType2((Type)ASTNode.copySubtree(fAST, fAnalyzer.getReturnType()));
  		result.setName(fAST.newSimpleName(name));
  		
  		List parameters= result.parameters();
  		for (int i= 0; i < fParameterInfos.size(); i++) {
  			ParameterInfo info= (ParameterInfo)fParameterInfos.get(i);
  			VariableDeclaration infoDecl= getVariableDeclaration(info);
  			SingleVariableDeclaration parameter= fAST.newSingleVariableDeclaration();
  			parameter.modifiers().addAll(ASTNodeFactory.newModifiers(fAST, ASTNodes.getModifiers(infoDecl)));
  			parameter.setType(ASTNodeFactory.newType(fAST, infoDecl));
  			parameter.setName(fAST.newSimpleName(info.getNewName()));
  			parameter.setVarargs(info.isNewVarargs());
  			parameters.add(parameter);
  		}
  		
  		List exceptions= result.thrownExceptions();
  		ITypeBinding[] exceptionTypes= fAnalyzer.getExceptions(fThrowRuntimeExceptions, fAST);
  		for (int i= 0; i < exceptionTypes.length; i++) {
  			ITypeBinding exceptionType= exceptionTypes[i];
  			exceptions.add(ASTNodeFactory.newName(fAST, fImportRewriter.addImport(exceptionType)));
  		}
  		if (code) {
  			result.setBody(createMethodBody(result, selectedNodes, substitute));
  			if (fGenerateJavadoc) {
  				AbstractTypeDeclaration enclosingType= 
  					(AbstractTypeDeclaration)ASTNodes.getParent(fAnalyzer.getEnclosingBodyDeclaration(), AbstractTypeDeclaration.class);
  
  				String typeName = (enclosingType!=null)?enclosingType.getName().getIdentifier():null;
  				String string= CodeGeneration.getMethodComment(fCUnit, typeName, result, null, lineDelimiter);
  				if (string != null) {
  					JSdoc javadoc= (JSdoc)fRewriter.createStringPlaceholder(string, ASTNode.JSDOC);
  					result.setJavadoc(javadoc);
  				}
  			}
  		}
  		
  		return result;
  	}
  	
  	private Block createMethodBody(FunctionDeclaration method, ASTNode[] selectedNodes, TextEditGroup substitute) throws BadLocationException, CoreException {
  		Block result= fAST.newBlock();
  		ListRewrite statements= fRewriter.getListRewrite(result, Block.STATEMENTS_PROPERTY);
  		
  		// Locals that are not passed as an arguments since the extracted method only
  		// writes to them
  		IVariableBinding[] methodLocals= fAnalyzer.getMethodLocals();
  		for (int i= 0; i < methodLocals.length; i++) {
  			if (methodLocals[i] != null) {
  				result.statements().add(createDeclaration(methodLocals[i], null));
  			}
  		}
  
  		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
  			ParameterInfo parameter= (ParameterInfo)iter.next();
  			if (parameter.isRenamed()) {
  				for (int n= 0; n < selectedNodes.length; n++) {
  					SimpleName[] oldNames= LinkedNodeFinder.findByBinding(selectedNodes[n], parameter.getOldBinding());
  					for (int i= 0; i < oldNames.length; i++) {
  						fRewriter.replace(oldNames[i], fAST.newSimpleName(parameter.getNewName()), null);
  					}
  				}
  			}
  		}
  		
  		boolean extractsExpression= fAnalyzer.isExpressionSelected();
  		ASTNode[] callNodes= createCallNodes(null);
  		ASTNode replacementNode;
  		if (callNodes.length == 1) {
  			replacementNode= callNodes[0];
  		} else {
  			replacementNode= fRewriter.createGroupNode(callNodes);
  		}
  		if (extractsExpression) {
  			// if we have an expression then only one node is selected.
  			ITypeBinding binding= fAnalyzer.getExpressionBinding();
  			if (binding != null && (!binding.isPrimitive() || !"void".equals(binding.getName()))) { //$NON-NLS-1$
  				ReturnStatement rs= fAST.newReturnStatement();
  				rs.setExpression((Expression)fRewriter.createMoveTarget(selectedNodes[0]));
  				statements.insertLast(rs, null);
  			} else {
  				ExpressionStatement st= fAST.newExpressionStatement((Expression)fRewriter.createMoveTarget(selectedNodes[0]));
  				statements.insertLast(st, null);
  			}
  			fRewriter.replace(selectedNodes[0], replacementNode, substitute);
  		} else {
  			if (selectedNodes.length == 1) {
  				statements.insertLast(fRewriter.createMoveTarget(selectedNodes[0]), substitute);
  				fRewriter.replace(selectedNodes[0], replacementNode, substitute);
  			} else {
  				ListRewrite source= fRewriter.getListRewrite(
  					selectedNodes[0].getParent(), 
  					(ChildListPropertyDescriptor)selectedNodes[0].getLocationInParent());
  				ASTNode toMove= source.createMoveTarget(
  					selectedNodes[0], selectedNodes[selectedNodes.length - 1],
  					replacementNode, substitute);
  				statements.insertLast(toMove, substitute);
  			}
  			IVariableBinding returnValue= fAnalyzer.getReturnValue();
  			if (returnValue != null) {
  				ReturnStatement rs= fAST.newReturnStatement();
  				rs.setExpression(fAST.newSimpleName(getName(returnValue)));
  				statements.insertLast(rs, null);				
  			}
  		}
  		return result;
  	}
  	
  	private String getName(IVariableBinding binding) {
  		for (Iterator iter= fParameterInfos.iterator(); iter.hasNext();) {
  			ParameterInfo info= (ParameterInfo)iter.next();
  			if (Bindings.equals(binding, info.getOldBinding())) {
  				return info.getNewName();
  			}
  		}
  		return binding.getName();
  	}
  	
  	private VariableDeclaration getVariableDeclaration(ParameterInfo parameter) {
  		return ASTNodes.findVariableDeclaration(parameter.getOldBinding(), fAnalyzer.getEnclosingBodyDeclaration());
  	}
  	
  	private VariableDeclarationStatement createDeclaration(IVariableBinding binding, Expression intilizer) {
  		VariableDeclaration original= ASTNodes.findVariableDeclaration(binding, fAnalyzer.getEnclosingBodyDeclaration());
  		VariableDeclarationFragment fragment= fAST.newVariableDeclarationFragment();
  		fragment.setName((SimpleName)ASTNode.copySubtree(fAST, original.getName()));
  		fragment.setInitializer(intilizer);	
  		VariableDeclarationStatement result= fAST.newVariableDeclarationStatement(fragment);
  		result.modifiers().addAll(ASTNode.copySubtrees(fAST, ASTNodes.getModifiers(original)));
  		result.setType(ASTNodeFactory.newType(fAST, original));
  		return result;
  	}	
  
  	public IJavaScriptUnit getCompilationUnit() {
  		return fCUnit;
  	}
  
  	public RefactoringStatus initialize(final RefactoringArguments arguments) {
  		if (arguments instanceof JavaRefactoringArguments) {
  			final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
  			final String selection= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_SELECTION);
  			if (selection != null) {
  				int offset= -1;
  				int length= -1;
  				final StringTokenizer tokenizer= new StringTokenizer(selection);
  				if (tokenizer.hasMoreTokens())
  					offset= Integer.valueOf(tokenizer.nextToken()).intValue();
  				if (tokenizer.hasMoreTokens())
  					length= Integer.valueOf(tokenizer.nextToken()).intValue();
  				if (offset >= 0 && length >= 0) {
  					fSelectionStart= offset;
  					fSelectionLength= length;
  				} else
  					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_illegal_argument, new Object[] { selection, JDTRefactoringDescriptor.ATTRIBUTE_SELECTION}));
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_SELECTION));
  			final String handle= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_INPUT);
  			if (handle != null) {
  				final IJavaScriptElement element= JDTRefactoringDescriptor.handleToElement(extended.getProject(), handle, false);
  				if (element == null || !element.exists() || element.getElementType() != IJavaScriptElement.JAVASCRIPT_UNIT)
  					return createInputFatalStatus(element, IJavaScriptRefactorings.EXTRACT_METHOD);
  				else {
  					fCUnit= (IJavaScriptUnit) element;
  		        	try {
  						initialize(fCUnit);
  					} catch (CoreException exception) {
  						JavaScriptPlugin.log(exception);
  					}
  				}
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_INPUT));
  			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;
  			}
  			final String name= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_NAME);
  			if (name != null && !"".equals(name)) //$NON-NLS-1$
  				fMethodName= name;
  			else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_NAME));
  			final String destination= extended.getAttribute(ATTRIBUTE_DESTINATION);
  			if (destination != null && !"".equals(destination)) {//$NON-NLS-1$
  				int index= 0;
  				try {
  					index= Integer.parseInt(destination);
  				} catch (NumberFormatException exception) {
  					return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_DESTINATION));
  				}
  				fDestinationIndex= index;
  			}
  			final String replace= extended.getAttribute(ATTRIBUTE_REPLACE);
  			if (replace != null) {
  				fReplaceDuplicates= Boolean.valueOf(replace).booleanValue();
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE));
  			final String comments= extended.getAttribute(ATTRIBUTE_COMMENTS);
  			if (comments != null)
  				fGenerateJavadoc= Boolean.valueOf(comments).booleanValue();
  			else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_COMMENTS));
  			final String exceptions= extended.getAttribute(ATTRIBUTE_EXCEPTIONS);
  			if (exceptions != null)
  				fThrowRuntimeExceptions= Boolean.valueOf(exceptions).booleanValue();
  			else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_EXCEPTIONS));
  		} else
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
  		return new RefactoringStatus();
  	}	
  }
  