content: /*******************************************************************************
   * Copyright (c) 2000, 2010 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.rename;
  
  import java.util.StringTokenizer;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.GroupCategorySet;
  import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.TextChange;
  import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
  import org.eclipse.ltk.core.refactoring.participants.RenameArguments;
  import org.eclipse.text.edits.MultiTextEdit;
  import org.eclipse.text.edits.ReplaceEdit;
  import org.eclipse.text.edits.TextEdit;
  import org.eclipse.text.edits.TextEditGroup;
  import org.eclipse.wst.jsdt.core.IFunction;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.ILocalVariable;
  import org.eclipse.wst.jsdt.core.ISourceRange;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclaration;
  import org.eclipse.wst.jsdt.core.refactoring.IJavaScriptRefactorings;
  import org.eclipse.wst.jsdt.core.refactoring.descriptors.RenameLocalVariableDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.dom.NodeFinder;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptor;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JDTRefactoringDescriptorComment;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.JavaRefactoringArguments;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringAvailabilityTester;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.CompilationUnitChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.RefactoringDescriptorChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.TextChangeCompatibility;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.code.ScriptableRefactoring;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.participants.JavaProcessors;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.rename.RenameAnalyzeUtil.LocalAnalyzePackage;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.IReferenceUpdating;
  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.TextChangeManager;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.refactoring.RefactoringSaveHelper;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  public class RenameLocalVariableProcessor extends JavaRenameProcessor implements IReferenceUpdating {
  
  	private ILocalVariable fLocalVariable;
  	private IJavaScriptUnit fCu;
  	
  	//the following fields are set or modified after the construction
  	private boolean fUpdateReferences;
  	private String fCurrentName;
  	private String fNewName;
  	private JavaScriptUnit fCompilationUnitNode;
  	private VariableDeclaration fTempDeclarationNode;
  	private TextChange fChange;
  	
  	private boolean fIsComposite;
  	private GroupCategorySet fCategorySet;
  	private TextChangeManager fChangeManager;
  	private RenameAnalyzeUtil.LocalAnalyzePackage fLocalAnalyzePackage;
  
  	public static final String IDENTIFIER= "org.eclipse.wst.jsdt.ui.renameLocalVariableProcessor"; //$NON-NLS-1$
  	
  	/**
  	 * Creates a new rename local variable processor.
  	 * @param localVariable the local variable, or <code>null</code> if invoked by scripting
  	 */
  	public RenameLocalVariableProcessor(ILocalVariable localVariable) {
  		fLocalVariable= localVariable;
  		fUpdateReferences= true;
  		if (localVariable != null)
  			fCu= (IJavaScriptUnit) localVariable.getAncestor(IJavaScriptElement.JAVASCRIPT_UNIT);
  		fNewName= ""; //$NON-NLS-1$
  		fIsComposite= false;
  	}
  	
  	/**
  	 * Creates a new rename local variable processor.
  	 * <p>
  	 * This constructor is only used by <code>RenameTypeProcessor</code>.
  	 * </p>
  	 * 
  	 * @param localVariable the local variable
  	 * @param manager the change manager
  	 * @param node the compilation unit node
  	 * @param categorySet the group category set
  	 */
  	RenameLocalVariableProcessor(ILocalVariable localVariable, TextChangeManager manager, JavaScriptUnit node, GroupCategorySet categorySet) {
  		this(localVariable);
  		fChangeManager= manager;
  		fCategorySet= categorySet;
  		fCompilationUnitNode= node;
  		fIsComposite= true;
  	}
  	
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.rename.JavaRenameProcessor#getAffectedProjectNatures()
  	 */
  	protected final String[] getAffectedProjectNatures() throws CoreException {
  		return JavaProcessors.computeAffectedNatures(fLocalVariable);
  	}
  	
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#getElements()
  	 */
  	public Object[] getElements() {
  		return new Object[] { fLocalVariable };
  	}
  	
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#getIdentifier()
  	 */
  	public String getIdentifier() {
  		return IDENTIFIER;
  	}
  	
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#getProcessorName()
  	 */
  	public String getProcessorName() {
  		return RefactoringCoreMessages.RenameTempRefactoring_rename; 
  	}
  	
  	/*
  	 * @see org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#isApplicable()
  	 */
  	public boolean isApplicable() throws CoreException {
  		return RefactoringAvailabilityTester.isRenameAvailable(fLocalVariable);
  	}
  	
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.IReferenceUpdating#canEnableUpdateReferences()
  	 */
  	public boolean canEnableUpdateReferences() {
  		return true;
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.rename.JavaRenameProcessor#getUpdateReferences()
  	 */
  	public boolean getUpdateReferences() {
  		return fUpdateReferences;
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.IReferenceUpdating#setUpdateReferences(boolean)
  	 */
  	public void setUpdateReferences(boolean updateReferences) {
  		fUpdateReferences= updateReferences;
  	}
  	
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.INameUpdating#getCurrentElementName()
  	 */
  	public String getCurrentElementName() {
  		return fCurrentName;
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.INameUpdating#getNewElementName()
  	 */
  	public String getNewElementName() {
  		return fNewName;
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.INameUpdating#setNewElementName(java.lang.String)
  	 */
  	public void setNewElementName(String newName) {
  		Assert.isNotNull(newName);
  		fNewName= newName;
  	}
  
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.INameUpdating#getNewElement()
  	 */
  	public Object getNewElement() {
  		return null; //cannot create an ILocalVariable
  	}
  
  	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
  		initAST();
  		if (fTempDeclarationNode == null || fTempDeclarationNode.resolveBinding() == null)
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.RenameTempRefactoring_must_select_local); 
  		//if (! Checks.isDeclaredIn(fTempDeclarationNode, FunctionDeclaration.class) 
  		 //&& ! Checks.isDeclaredIn(fTempDeclarationNode, Initializer.class))
  			//return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.RenameTempRefactoring_only_in_methods_and_initializers); 
  				
  		initNames();			
  		return new RefactoringStatus();
  	}
  
  	private void initAST() throws JavaScriptModelException {
  		if (!fIsComposite)
  			fCompilationUnitNode= RefactoringASTParser.parseWithASTProvider(fCu, true, null);
  		ISourceRange sourceRange= fLocalVariable.getNameRange();
  		ASTNode name= NodeFinder.perform(fCompilationUnitNode, sourceRange);
  		if (name == null)
  			return;
  		if (name.getParent() instanceof VariableDeclaration)
  			fTempDeclarationNode= (VariableDeclaration) name.getParent();
  	}
  	
  	private void initNames(){
  		fCurrentName= fTempDeclarationNode.getName().getIdentifier();
  	}
  	
  	protected RenameModifications computeRenameModifications() throws CoreException {
  		RenameModifications result= new RenameModifications();
  		result.rename(fLocalVariable, new RenameArguments(getNewElementName(), getUpdateReferences()));
  		return result;
  	}
  	
  	protected IFile[] getChangedFiles() throws CoreException {
  		return new IFile[] {ResourceUtil.getFile(fCu)};
  	}
  	
  	public int getSaveMode() {
  		return RefactoringSaveHelper.SAVE_NOTHING;
  	}
  	
  	protected RefactoringStatus doCheckFinalConditions(IProgressMonitor pm, CheckConditionsContext context)
  			throws CoreException, OperationCanceledException {
  		try {
  			pm.beginTask("", 1);	 //$NON-NLS-1$
  
  			RefactoringStatus result= checkNewElementName(fNewName);
  			if (result.hasFatalError())
  				return result;
  			createEdits();
  			if (!fIsComposite) {
  				LocalAnalyzePackage[] localAnalyzePackages= new RenameAnalyzeUtil.LocalAnalyzePackage[] { fLocalAnalyzePackage };
  				result.merge(RenameAnalyzeUtil.analyzeLocalRenames(localAnalyzePackages, fChange, fCompilationUnitNode, true));
  			}
  			return result;
  		} finally {
  			pm.done();
  			if (fIsComposite) {
  				// end of life cycle for this processor
  				fChange= null;
  				fCompilationUnitNode= null;
  				fTempDeclarationNode= null;
  			}
  		}	
  	}
  		
  	/*
  	 * @see org.eclipse.wst.jsdt.internal.corext.refactoring.tagging.INameUpdating#checkNewElementName(java.lang.String)
  	 */
  	public RefactoringStatus checkNewElementName(String newName) throws JavaScriptModelException {
  		RefactoringStatus result= Checks.checkFieldName(newName);
  		if (! Checks.startsWithLowerCase(newName))
  			if (fIsComposite) {
  				final String nameOfParent= (fLocalVariable.getParent() instanceof IFunction) ? fLocalVariable.getParent().getElementName() : RefactoringCoreMessages.JavaElementUtil_initializer;
  				final String nameOfType= fLocalVariable.getAncestor(IJavaScriptElement.TYPE).getElementName();
  				result.addWarning(Messages.format(RefactoringCoreMessages.RenameTempRefactoring_lowercase2, new String[] { newName, nameOfParent, nameOfType }));
  			} else {
  				result.addWarning(RefactoringCoreMessages.RenameTempRefactoring_lowercase);
  			}
  		return result;		
  	}
  		
  	private void createEdits() {
  		TextEdit declarationEdit= createRenameEdit(fTempDeclarationNode.getName().getStartPosition());
  		TextEdit[] allRenameEdits= getAllRenameEdits(declarationEdit);
  		
  		TextEdit[] allUnparentedRenameEdits= new TextEdit[allRenameEdits.length];
  		TextEdit unparentedDeclarationEdit= null;
  		
  		fChange= new CompilationUnitChange(RefactoringCoreMessages.RenameTempRefactoring_rename, fCu); 
  		MultiTextEdit rootEdit= new MultiTextEdit();
  		fChange.setEdit(rootEdit);
  		fChange.setKeepPreviewEdits(true);
  
  		for (int i= 0; i < allRenameEdits.length; i++) {
  			if (fIsComposite) {
  				// Add a copy of the text edit (text edit may only have one
  				// parent) to keep problem reporting code clean
  				TextChangeCompatibility.addTextEdit(fChangeManager.get(fCu), RefactoringCoreMessages.RenameTempRefactoring_changeName, allRenameEdits[i].copy(), fCategorySet);
  				
  				// Add a separate copy for problem reporting
  				allUnparentedRenameEdits[i]= allRenameEdits[i].copy();
  				if (allRenameEdits[i].equals(declarationEdit))
  					unparentedDeclarationEdit= allUnparentedRenameEdits[i];
  			}
  			rootEdit.addChild(allRenameEdits[i]);
  			fChange.addTextEditGroup(new TextEditGroup(RefactoringCoreMessages.RenameTempRefactoring_changeName, allRenameEdits[i]));
  		}
  
  		// store information for analysis
  		if (fIsComposite) {
  			fLocalAnalyzePackage= new RenameAnalyzeUtil.LocalAnalyzePackage(unparentedDeclarationEdit, allUnparentedRenameEdits);
  		} else 
  			fLocalAnalyzePackage= new RenameAnalyzeUtil.LocalAnalyzePackage(declarationEdit, allRenameEdits);
  	}
  	
  	private TextEdit[] getAllRenameEdits(TextEdit declarationEdit) {
  		if (! fUpdateReferences)
  			return new TextEdit[] { declarationEdit };
  		
  		TempOccurrenceAnalyzer fTempAnalyzer= new TempOccurrenceAnalyzer(fTempDeclarationNode, true);
  		fTempAnalyzer.perform();
  		int[] referenceOffsets= fTempAnalyzer.getReferenceAndJavadocOffsets();
  
  		TextEdit[] allRenameEdits= new TextEdit[referenceOffsets.length + 1];
  		for (int i= 0; i < referenceOffsets.length; i++)
  			allRenameEdits[i]= createRenameEdit(referenceOffsets[i]);
  		allRenameEdits[referenceOffsets.length]= declarationEdit;
  		return allRenameEdits;
  	}
  
  	private TextEdit createRenameEdit(int offset) {
  		return new ReplaceEdit(offset, fCurrentName.length(), fNewName);
  	}
  
  	public Change createChange(IProgressMonitor monitor) throws CoreException {
  		try {
  			monitor.beginTask(RefactoringCoreMessages.RenameTypeProcessor_creating_changes, 1);
  			Change change= fChange;
  			if (change != null) {
  				final ISourceRange range= fLocalVariable.getNameRange();
  				String project= null;
  				IJavaScriptProject javaProject= fCu.getJavaScriptProject();
  				if (javaProject != null)
  					project= javaProject.getElementName();
  				final String header= Messages.format(RefactoringCoreMessages.RenameLocalVariableProcessor_descriptor_description, new String[] { fCurrentName, JavaScriptElementLabels.getElementLabel(fLocalVariable.getParent(), JavaScriptElementLabels.ALL_FULLY_QUALIFIED), fNewName});
  				final String description= Messages.format(RefactoringCoreMessages.RenameLocalVariableProcessor_descriptor_description_short, fCurrentName);
  				final String comment= new JDTRefactoringDescriptorComment(project, this, header).asString();
  				final RenameLocalVariableDescriptor descriptor= new RenameLocalVariableDescriptor();
  				descriptor.setProject(project);
  				descriptor.setDescription(description);
  				descriptor.setComment(comment);
  				descriptor.setFlags(RefactoringDescriptor.NONE);
  				descriptor.setCompilationUnit(fCu);
  				descriptor.setNewName(getNewElementName());
  				descriptor.setSelection(range);
  				descriptor.setUpdateReferences(fUpdateReferences);
  				final RefactoringDescriptorChange result= new RefactoringDescriptorChange(descriptor, RefactoringCoreMessages.RenameTempRefactoring_rename, new Change[] { change});
  				result.markAsSynthetic();
  				change= result;
  			}
  			return change;
  		} finally {
  			monitor.done();
  		}
  	}
  
  	public RefactoringStatus initialize(RefactoringArguments arguments) {
  		if (arguments instanceof JavaRefactoringArguments) {
  			final JavaRefactoringArguments extended= (JavaRefactoringArguments) arguments;
  			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()) {
  					if (element.getElementType() == IJavaScriptElement.JAVASCRIPT_UNIT) {
  						fCu= (IJavaScriptUnit) element;
  					} else if (element.getElementType() == IJavaScriptElement.LOCAL_VARIABLE) {
  						fLocalVariable= (ILocalVariable) element;
  						fCu= (IJavaScriptUnit) fLocalVariable.getAncestor(IJavaScriptElement.JAVASCRIPT_UNIT);
  						if (fCu == null)
  							return ScriptableRefactoring.createInputFatalStatus(element, getRefactoring().getName(), IJavaScriptRefactorings.RENAME_LOCAL_VARIABLE);
  					} else
  						return ScriptableRefactoring.createInputFatalStatus(element, getRefactoring().getName(), IJavaScriptRefactorings.RENAME_LOCAL_VARIABLE);
  				} else
  					return ScriptableRefactoring.createInputFatalStatus(element, getRefactoring().getName(), IJavaScriptRefactorings.RENAME_LOCAL_VARIABLE);
  			} 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 && !"".equals(name)) //$NON-NLS-1$
  				setNewElementName(name);
  			else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_NAME));
  			if (fCu != null && fLocalVariable == null) {
  				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) {
  						try {
  							final IJavaScriptElement[] elements= fCu.codeSelect(offset, length);
  							if (elements != null) {
  								for (int index= 0; index < elements.length; index++) {
  									final IJavaScriptElement element= elements[index];
  									if (element instanceof ILocalVariable)
  										fLocalVariable= (ILocalVariable) element;
  								}
  							}
  							if (fLocalVariable == null)
  								return ScriptableRefactoring.createInputFatalStatus(null, getRefactoring().getName(), IJavaScriptRefactorings.RENAME_LOCAL_VARIABLE);
  						} catch (JavaScriptModelException exception) {
  							JavaScriptPlugin.log(exception);
  						}
  					} 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 references= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_REFERENCES);
  			if (references != null) {
  				fUpdateReferences= Boolean.valueOf(references).booleanValue();
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_REFERENCES));
  		} else
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
  		return new RefactoringStatus();
  	}
  
  	public RenameAnalyzeUtil.LocalAnalyzePackage getLocalAnalyzePackage() {
  		return fLocalAnalyzePackage;
  	}
  }
  