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.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.StringTokenizer;
  
  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.SubProgressMonitor;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
  import org.eclipse.text.edits.TextEditGroup;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.compiler.IProblem;
  import org.eclipse.wst.jsdt.core.dom.AST;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.AbstractTypeDeclaration;
  import org.eclipse.wst.jsdt.core.dom.BodyDeclaration;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  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.FieldDeclaration;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.Initializer;
  import org.eclipse.wst.jsdt.core.dom.JSdoc;
  import org.eclipse.wst.jsdt.core.dom.FunctionDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Modifier;
  import org.eclipse.wst.jsdt.core.dom.Name;
  import org.eclipse.wst.jsdt.core.dom.NullLiteral;
  import org.eclipse.wst.jsdt.core.dom.QualifiedName;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.core.dom.SwitchCase;
  import org.eclipse.wst.jsdt.core.dom.Type;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationFragment;
  import org.eclipse.wst.jsdt.core.dom.rewrite.ASTRewrite;
  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.Corext;
  import org.eclipse.wst.jsdt.internal.corext.SourceRange;
  import org.eclipse.wst.jsdt.internal.corext.codemanipulation.StubUtility;
  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.ScopeAnalyzer;
  import org.eclipse.wst.jsdt.internal.corext.dom.fragments.ASTFragmentFactory;
  import org.eclipse.wst.jsdt.internal.corext.dom.fragments.IASTFragment;
  import org.eclipse.wst.jsdt.internal.corext.dom.fragments.IExpressionFragment;
  import org.eclipse.wst.jsdt.internal.corext.fix.LinkedProposalModel;
  import org.eclipse.wst.jsdt.internal.corext.fix.LinkedProposalPositionGroup;
  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.RefactoringCoreMessages;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.base.JavaStringStatusContext;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.base.RefactoringStatusCodes;
  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.rename.RefactoringAnalyzeUtil;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser;
  import org.eclipse.wst.jsdt.internal.corext.util.JdtFlags;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.preferences.JavaPreferencesSettings;
  import org.eclipse.wst.jsdt.internal.ui.text.correction.ASTResolving;
  import org.eclipse.wst.jsdt.internal.ui.text.correction.ModifierCorrectionSubProcessor;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.BindingLabelProvider;
  import org.eclipse.wst.jsdt.ui.CodeGeneration;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  public class ExtractConstantRefactoring extends ScriptableRefactoring {
  
  	private static final String ATTRIBUTE_REPLACE= "replace"; //$NON-NLS-1$
  	private static final String ATTRIBUTE_QUALIFY= "qualify"; //$NON-NLS-1$
  	private static final String ATTRIBUTE_VISIBILITY= "visibility"; //$NON-NLS-1$
  
  	private static final String MODIFIER= "static final"; //$NON-NLS-1$
  	
  	private static final String KEY_NAME= "name"; //$NON-NLS-1$
  	private static final String KEY_TYPE= "type"; //$NON-NLS-1$
  	
  	private CompilationUnitRewrite fCuRewrite;
  	private int fSelectionStart;
  	private int fSelectionLength;
  	private IJavaScriptUnit fCu;
  
  	private IExpressionFragment fSelectedExpression;
  	private Type fConstantTypeCache;
  	private boolean fReplaceAllOccurrences= true; //default value
  	private boolean fQualifyReferencesWithDeclaringClassName= false;	//default value
  
  	private String fVisibility= JdtFlags.VISIBILITY_STRING_PRIVATE; //default value
  	private boolean fTargetIsInterface= false;
  	private String fConstantName;
  	private String[] fExcludedVariableNames;
  
  	private boolean fSelectionAllStaticFinal;
  	private boolean fAllStaticFinalCheckPerformed= false;
  	
  	private List fBodyDeclarations;
  	
  	//Constant Declaration Location
  	private BodyDeclaration fToInsertAfter;
  	private boolean fInsertFirst;
  	
  	private CompilationUnitChange fChange;
  	private String[] fGuessedConstNames;
  	
  	private LinkedProposalModel fLinkedProposalModel;
  
  	/**
  	 * Creates a new extract constant refactoring
  	 * @param unit the compilation unit, or <code>null</code> if invoked by scripting
  	 * @param selectionStart
  	 * @param selectionLength
  	 */
  	public ExtractConstantRefactoring(IJavaScriptUnit unit, int selectionStart, int selectionLength) {
  		Assert.isTrue(selectionStart >= 0);
  		Assert.isTrue(selectionLength >= 0);
  		fSelectionStart= selectionStart;
  		fSelectionLength= selectionLength;
  		fCu= unit;
  		fCuRewrite= null;
  		fLinkedProposalModel= null;
  		fConstantName= ""; //$NON-NLS-1$
  	}
  	
  	public ExtractConstantRefactoring(JavaScriptUnit astRoot, int selectionStart, int selectionLength) {
  		Assert.isTrue(selectionStart >= 0);
  		Assert.isTrue(selectionLength >= 0);
  		Assert.isTrue(astRoot.getTypeRoot() instanceof IJavaScriptUnit);
  		
  		fSelectionStart= selectionStart;
  		fSelectionLength= selectionLength;
  		fCu= (IJavaScriptUnit) astRoot.getTypeRoot();
  		fCuRewrite= new CompilationUnitRewrite(fCu, astRoot);
  		fLinkedProposalModel= null;
  		fConstantName= ""; //$NON-NLS-1$
  	}
  		
  	public void setLinkedProposalModel(LinkedProposalModel linkedProposalModel) {
  		fLinkedProposalModel= linkedProposalModel;
  	}
  	
  	public String getName() {
  		return RefactoringCoreMessages.ExtractConstantRefactoring_name; 
  	}
  
  	public boolean replaceAllOccurrences() {
  		return fReplaceAllOccurrences;
  	}
  
  	public void setReplaceAllOccurrences(boolean replaceAllOccurrences) {
  		fReplaceAllOccurrences= replaceAllOccurrences;
  	}
  	
  	public void setVisibility(String am) {
  		Assert.isTrue(
  			am == JdtFlags.VISIBILITY_STRING_PRIVATE || am == JdtFlags.VISIBILITY_STRING_PROTECTED || am == JdtFlags.VISIBILITY_STRING_PACKAGE || am == JdtFlags.VISIBILITY_STRING_PUBLIC
  		);
  		fVisibility= am;
  	}
  	
  	public String getVisibility() {
  		return fVisibility;	
  	}
  	
  	public boolean getTargetIsInterface() {
  		return fTargetIsInterface;
  	}
  
  	public boolean qualifyReferencesWithDeclaringClassName() {
  		return fQualifyReferencesWithDeclaringClassName;
  	}
  	
  	public void setQualifyReferencesWithDeclaringClassName(boolean qualify) {
  		fQualifyReferencesWithDeclaringClassName= qualify;
  	}
  	
  	public String guessConstantName() throws JavaScriptModelException {
  		String[] proposals= guessConstantNames();
  		if (proposals.length > 0)
  			return proposals[0];
  		else
  			return fConstantName;
  	}
  	
  	/**
  	 * @return proposed variable names (may be empty, but not null).
  	 * The first proposal should be used as "best guess" (if it exists).
  	 */
  	public String[] guessConstantNames() {
  		if (fGuessedConstNames == null) {
  			try {
  				Expression expression= getSelectedExpression().getAssociatedExpression();
  				if (expression != null) {
  					ITypeBinding binding= expression.resolveTypeBinding();
  					fGuessedConstNames= StubUtility.getVariableNameSuggestions(StubUtility.CONSTANT_FIELD, fCu.getJavaScriptProject(), binding, expression, Arrays.asList(getExcludedVariableNames()));
  				} 
  			} catch (JavaScriptModelException e) {
  			}
  			if (fGuessedConstNames == null)
  				fGuessedConstNames= new String[0];
  		}
  		return fGuessedConstNames;
  	}
  	
  	
  	private String[] getExcludedVariableNames() {
  		if (fExcludedVariableNames == null) {
  			try {
  				IExpressionFragment expr= getSelectedExpression();
  				Collection takenNames= new ScopeAnalyzer(fCuRewrite.getRoot()).getUsedVariableNames(expr.getStartPosition(), expr.getLength());
  				fExcludedVariableNames= (String[]) takenNames.toArray(new String[takenNames.size()]);
  			} catch (JavaScriptModelException e) {
  				fExcludedVariableNames= new String[0];
  			}
  		}
  		return fExcludedVariableNames;
  	}
  		
  	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
  		try {
  			pm.beginTask("", 7); //$NON-NLS-1$
  	
  			RefactoringStatus result= Checks.validateEdit(fCu, getValidationContext());
  			if (result.hasFatalError())
  				return result;
  			pm.worked(1);
  			
  			if (fCuRewrite == null) {
  				JavaScriptUnit cuNode= RefactoringASTParser.parseWithASTProvider(fCu, true, new SubProgressMonitor(pm, 3));
  				fCuRewrite= new CompilationUnitRewrite(fCu, cuNode);
  			} else {
  				pm.worked(3);
  			}
  			result.merge(checkSelection(new SubProgressMonitor(pm, 3)));
  	
  			if (result.hasFatalError())
  				return result;
  			
  			if (isLiteralNodeSelected())
  				fReplaceAllOccurrences= false;
  			
  			return result;
  		} finally {
  			pm.done();
  		}
  	}
  	
  	public boolean selectionAllStaticFinal() {
  		Assert.isTrue(fAllStaticFinalCheckPerformed);
  		return fSelectionAllStaticFinal;
  	}
  
  	private void checkAllStaticFinal() throws JavaScriptModelException {
  		fSelectionAllStaticFinal= ConstantChecks.isStaticFinalConstant(getSelectedExpression());
  		fAllStaticFinalCheckPerformed= true;
  	}
  
  	private RefactoringStatus checkSelection(IProgressMonitor pm) throws JavaScriptModelException {
  		try {
  			pm.beginTask("", 2); //$NON-NLS-1$
  			
  			IExpressionFragment selectedExpression= getSelectedExpression();
  			
  			if (selectedExpression == null) {
  				String message= RefactoringCoreMessages.ExtractConstantRefactoring_select_expression; 
  				return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, fCuRewrite.getRoot(), message);
  			}
  			pm.worked(1);
  			
  			RefactoringStatus result= new RefactoringStatus();
  			result.merge(checkExpression());
  			if (result.hasFatalError())
  				return result;
  			pm.worked(1);
  			
  			return result;
  		} finally {
  			pm.done();
  		}
  	}
  
  	private RefactoringStatus checkExpressionBinding() throws JavaScriptModelException {
  		return checkExpressionFragmentIsRValue();
  	}
  	
  	private RefactoringStatus checkExpressionFragmentIsRValue() throws JavaScriptModelException {
  		/* Moved this functionality to Checks, to allow sharing with
  		   ExtractTempRefactoring, others */
  		switch(Checks.checkExpressionIsRValue(getSelectedExpression().getAssociatedExpression())) {
  			case Checks.NOT_RVALUE_MISC:
  				return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_select_expression, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE, null); 
  			case Checks.NOT_RVALUE_VOID:
  				return RefactoringStatus.createStatus(RefactoringStatus.FATAL, RefactoringCoreMessages.ExtractConstantRefactoring_no_void, null, Corext.getPluginId(), RefactoringStatusCodes.EXPRESSION_NOT_RVALUE_VOID, null); 
  			case Checks.IS_RVALUE:
  				return new RefactoringStatus();
  			default:
  				Assert.isTrue(false); return null;
  		}		
  	}
  
  	//	 !!! -- same as in ExtractTempRefactoring
  	private boolean isLiteralNodeSelected() throws JavaScriptModelException {
  		IExpressionFragment fragment= getSelectedExpression();
  		if (fragment == null)
  			return false;
  		Expression expression= fragment.getAssociatedExpression();
  		if (expression == null)
  			return false;
  		switch (expression.getNodeType()) {
  			case ASTNode.BOOLEAN_LITERAL :
  			case ASTNode.CHARACTER_LITERAL :
  			case ASTNode.NULL_LITERAL :
  			case ASTNode.NUMBER_LITERAL :
  			case ASTNode.UNDEFINED_LITERAL :
  			case ASTNode.REGULAR_EXPRESSION_LITERAL :
  				return true;
  			
  			default :
  				return false;
  		}
  	}
  
  	private RefactoringStatus checkExpression() throws JavaScriptModelException {
  		RefactoringStatus result= new RefactoringStatus();
  		result.merge(checkExpressionBinding());
  		if(result.hasFatalError())
  			return result;
  		checkAllStaticFinal();
  
  		IExpressionFragment selectedExpression= getSelectedExpression();
  		Expression associatedExpression= selectedExpression.getAssociatedExpression();
  		if (associatedExpression instanceof NullLiteral)
  			result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_null_literals)); 
  		else if (!ConstantChecks.isLoadTimeConstant(selectedExpression))
  			result.merge(RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_not_load_time_constant)); 
  		else if (associatedExpression instanceof SimpleName) {
  			if (associatedExpression.getParent() instanceof QualifiedName && associatedExpression.getLocationInParent() == QualifiedName.NAME_PROPERTY
  					|| associatedExpression.getParent() instanceof FieldAccess && associatedExpression.getLocationInParent() == FieldAccess.NAME_PROPERTY)
  				return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.ExtractConstantRefactoring_select_expression);
  		}
  		
  		return result;
  	}
  
  	public void setConstantName(String newName) {
  		Assert.isNotNull(newName);
  		fConstantName= newName;
  	}
  
  	public String getConstantName() {
  		return fConstantName;
  	}
  
  	/**
  	 * This method performs checks on the constant name which are
  	 * quick enough to be performed every time the ui input component
  	 * contents are changed.
  	 * 
  	 * @return return the resulting status
  	 * @throws JavaScriptModelException thrown when the operation could not be executed
  	 */
  	public RefactoringStatus checkConstantNameOnChange() throws JavaScriptModelException {
  		if (Arrays.asList(getExcludedVariableNames()).contains(fConstantName))
  			return RefactoringStatus.createErrorStatus(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_another_variable, getConstantName())); 
  		return Checks.checkConstantName(getConstantName());
  	}
  	
  	// !! similar to ExtractTempRefactoring equivalent
  	public String getConstantSignaturePreview() throws JavaScriptModelException {
  		String space= " "; //$NON-NLS-1$
  		return getVisibility() + space + MODIFIER + space + getConstantTypeName() + space + fConstantName;
  	}
  	
  	public CompilationUnitChange createTextChange(IProgressMonitor pm) throws CoreException {
  		createConstantDeclaration();
  		replaceExpressionsWithConstant();
  		return fCuRewrite.createChange(RefactoringCoreMessages.ExtractConstantRefactoring_change_name, true, pm);
  	}
  	
  
  	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
  		pm.beginTask(RefactoringCoreMessages.ExtractConstantRefactoring_checking_preconditions, 4); 
  		
  		/* Note: some checks are performed on change of input widget
  		 * values. (e.g. see ExtractConstantRefactoring.checkConstantNameOnChange())
  		 */ 
  		
  		//TODO: possibly add more checking for name conflicts that might
  		//      lead to a change in behaviour
  		
  		try {
  			RefactoringStatus result= new RefactoringStatus();
  			fChange= createTextChange(new SubProgressMonitor(pm, 2));
  			
  			String newCuSource= fChange.getPreviewContent(new NullProgressMonitor());
  			JavaScriptUnit newCUNode= new RefactoringASTParser(AST.JLS3).parse(newCuSource, fCu, true, true, null);
  			
  			IProblem[] newProblems= RefactoringAnalyzeUtil.getIntroducedCompileProblems(newCUNode, fCuRewrite.getRoot());
  			for (int i= 0; i < newProblems.length; i++) {
  				IProblem problem= newProblems[i];
  				if (problem.isError())
  					result.addEntry(new RefactoringStatusEntry((problem.isError() ? RefactoringStatus.ERROR : RefactoringStatus.WARNING), problem.getMessage(), new JavaStringStatusContext(newCuSource, new SourceRange(problem))));
  			}
  			
  			fConstantTypeCache= null;
  			fCuRewrite.clearASTAndImportRewrites();
  
  			return result;
  		} finally {
  			pm.done();
  		}
  	}
  
  	private void createConstantDeclaration() throws CoreException {
  		Type type= getConstantType();
  		
  		IExpressionFragment fragment= getSelectedExpression();
  		String initializerSource= fCu.getBuffer().getText(fragment.getStartPosition(), fragment.getLength());
  		
  		AST ast= fCuRewrite.getAST();
  		VariableDeclarationFragment variableDeclarationFragment= ast.newVariableDeclarationFragment();
  		variableDeclarationFragment.setName(ast.newSimpleName(fConstantName));
  		variableDeclarationFragment.setInitializer((Expression) fCuRewrite.getASTRewrite().createStringPlaceholder(initializerSource, ASTNode.SIMPLE_NAME));
  		
  		FieldDeclaration fieldDeclaration= ast.newFieldDeclaration(variableDeclarationFragment);
  		fieldDeclaration.setType(type);
  		Modifier.ModifierKeyword accessModifier= Modifier.ModifierKeyword.toKeyword(fVisibility);
  		if (accessModifier != null)
  			fieldDeclaration.modifiers().add(ast.newModifier(accessModifier));
  		fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.STATIC_KEYWORD));
  		fieldDeclaration.modifiers().add(ast.newModifier(Modifier.ModifierKeyword.FINAL_KEYWORD));
  		
  		boolean createComments= JavaPreferencesSettings.getCodeGenerationSettings(fCu.getJavaScriptProject()).createComments;
  		if (createComments) {
  			String comment= CodeGeneration.getFieldComment(fCu, getConstantTypeName(), fConstantName, StubUtility.getLineDelimiterUsed(fCu));
  			if (comment != null && comment.length() > 0) {
  				JSdoc doc= (JSdoc) fCuRewrite.getASTRewrite().createStringPlaceholder(comment, ASTNode.JSDOC);
  				fieldDeclaration.setJavadoc(doc);
  			}
  		}
  		
  		AbstractTypeDeclaration parent= getContainingTypeDeclarationNode();
  		ListRewrite listRewrite= fCuRewrite.getASTRewrite().getListRewrite(parent, parent.getBodyDeclarationsProperty());
  		TextEditGroup msg= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_declare_constant); 
  		if (insertFirst()) {
  			listRewrite.insertFirst(fieldDeclaration, msg);
  		} else {
  			listRewrite.insertAfter(fieldDeclaration, getNodeToInsertConstantDeclarationAfter(), msg);
  		}
  		
  		if (fLinkedProposalModel != null) {
  			ASTRewrite rewrite= fCuRewrite.getASTRewrite();
  			LinkedProposalPositionGroup nameGroup= fLinkedProposalModel.getPositionGroup(KEY_NAME, true);
  			nameGroup.addPosition(rewrite.track(variableDeclarationFragment.getName()), true);
  			
  			String[] nameSuggestions= guessConstantNames();
  			if (nameSuggestions.length > 0 && !nameSuggestions[0].equals(fConstantName)) {
  				nameGroup.addProposal(fConstantName, null, nameSuggestions.length + 1);
  			}
  			for (int i= 0; i < nameSuggestions.length; i++) {
  				nameGroup.addProposal(nameSuggestions[i], null, nameSuggestions.length - i);
  			}
  			
  			LinkedProposalPositionGroup typeGroup= fLinkedProposalModel.getPositionGroup(KEY_TYPE, true);
  			typeGroup.addPosition(rewrite.track(type), true);
  			
  			ITypeBinding typeBinding= fragment.getAssociatedExpression().resolveTypeBinding();
  			if (typeBinding != null) {
  				ITypeBinding[] relaxingTypes= ASTResolving.getNarrowingTypes(ast, typeBinding);
  				for (int i= 0; i < relaxingTypes.length; i++) {
  					typeGroup.addProposal(relaxingTypes[i], fCuRewrite.getCu(), relaxingTypes.length - i);
  				}
  			}
  			ModifierCorrectionSubProcessor.installLinkedVisibilityProposals(fLinkedProposalModel, rewrite, fieldDeclaration.modifiers(), false);
  		}
  	}
  
  	private Type getConstantType() throws JavaScriptModelException {
  		if (fConstantTypeCache == null) {
  			IExpressionFragment fragment= getSelectedExpression();
  			ITypeBinding typeBinding= fragment.getAssociatedExpression().resolveTypeBinding();
  			AST ast= fCuRewrite.getAST();
  			typeBinding= Bindings.normalizeForDeclarationUse(typeBinding, ast);
  			fConstantTypeCache= fCuRewrite.getImportRewrite().addImport(typeBinding, ast);
  		}
  		return fConstantTypeCache;
  	}
  
  	public Change createChange(IProgressMonitor monitor) throws CoreException {
  		final Map arguments= new HashMap();
  		String project= null;
  		IJavaScriptProject javaProject= fCu.getJavaScriptProject();
  		if (javaProject != null)
  			project= javaProject.getElementName();
  		int flags= JavaScriptRefactoringDescriptor.JAR_REFACTORING | JavaScriptRefactoringDescriptor.JAR_SOURCE_ATTACHMENT;
  		if (JdtFlags.getVisibilityCode(fVisibility) != Modifier.PRIVATE)
  			flags|= RefactoringDescriptor.STRUCTURAL_CHANGE;
  		String pattern= ""; //$NON-NLS-1$
  		try {
  			pattern= BindingLabelProvider.getBindingLabel(getContainingTypeBinding(), JavaScriptElementLabels.ALL_FULLY_QUALIFIED) + "."; //$NON-NLS-1$
  		} catch (JavaScriptModelException exception) {
  			JavaScriptPlugin.log(exception);
  		}
  		final String expression= ASTNodes.asString(fSelectedExpression.getAssociatedExpression());
  		final String description= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description_short, fConstantName);
  		final String header= Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_descriptor_description, new String[] { pattern + fConstantName, expression});
  		final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
  		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_name_pattern, fConstantName));
  		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_constant_expression_pattern, expression));
  		String visibility= fVisibility;
  		if ("".equals(visibility)) //$NON-NLS-1$
  			visibility= RefactoringCoreMessages.ExtractConstantRefactoring_default_visibility;
  		comment.addSetting(Messages.format(RefactoringCoreMessages.ExtractConstantRefactoring_visibility_pattern, visibility));
  		if (fReplaceAllOccurrences)
  			comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_replace_occurrences);
  		if (fQualifyReferencesWithDeclaringClassName)
  			comment.addSetting(RefactoringCoreMessages.ExtractConstantRefactoring_qualify_references);
  		final JDTRefactoringDescriptor descriptor= new JDTRefactoringDescriptor(IJavaScriptRefactorings.EXTRACT_CONSTANT, project, description, comment.asString(), arguments, flags);
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fCu));
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_NAME, fConstantName);
  		arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_SELECTION, Integer.valueOf(fSelectionStart).toString() + " " + Integer.valueOf(fSelectionLength).toString()); //$NON-NLS-1$
  		arguments.put(ATTRIBUTE_REPLACE, Boolean.valueOf(fReplaceAllOccurrences).toString());
  		arguments.put(ATTRIBUTE_QUALIFY, Boolean.valueOf(fQualifyReferencesWithDeclaringClassName).toString());
  		arguments.put(ATTRIBUTE_VISIBILITY, Integer.valueOf(JdtFlags.getVisibilityCode(fVisibility)).toString());
  		return new RefactoringDescriptorChange(descriptor, RefactoringCoreMessages.ExtractConstantRefactoring_name, new Change[] { fChange});
  	}
  
  	private void replaceExpressionsWithConstant() throws JavaScriptModelException {
  		ASTRewrite astRewrite= fCuRewrite.getASTRewrite();
  		AST ast= astRewrite.getAST();
  		
  		IASTFragment[] fragmentsToReplace= getFragmentsToReplace();
  		for (int i= 0; i < fragmentsToReplace.length; i++) {
  			IASTFragment fragment= fragmentsToReplace[i];
  			
  			SimpleName ref= ast.newSimpleName(fConstantName);
  			Name replacement= ref;
  			if (qualifyReferencesWithDeclaringClassName()) {
  				replacement= ast.newQualifiedName(ast.newSimpleName(getContainingTypeBinding().getName()), ref);
  			}
  			TextEditGroup description= fCuRewrite.createGroupDescription(RefactoringCoreMessages.ExtractConstantRefactoring_replace);
  			
  			fragment.replace(astRewrite, replacement, description);
  			if (fLinkedProposalModel != null)
  				fLinkedProposalModel.getPositionGroup(KEY_NAME, true).addPosition(astRewrite.track(ref), false);
  		}
  	}
  	
  	private void computeConstantDeclarationLocation() throws JavaScriptModelException {
  		if (isDeclarationLocationComputed())
  			return;
  
  		BodyDeclaration lastStaticDependency= null;
  		Iterator decls= getBodyDeclarations();
  		
  		Assert.isTrue(decls.hasNext()); /* Admissible selected expressions must occur
  		                                   within a body declaration.  Thus, the 
  		                                   class/interface in which such an expression occurs
  		                                   must have at least one body declaration */
  		
  		while (decls.hasNext()) {
  			BodyDeclaration decl= (BodyDeclaration) decls.next();
  			
  			int modifiers;
  			if (decl instanceof FieldDeclaration)
  				modifiers= ((FieldDeclaration) decl).getModifiers();
  			else if (decl instanceof Initializer)
  				modifiers= ((Initializer) decl).getModifiers();
  			else {
  				continue; /* this declaration is not a field declaration
  				              or initializer, so the placement of the constant
  				              declaration relative to it does not matter */
  			}
  			
  			if (Modifier.isStatic(modifiers) && depends(getSelectedExpression(), decl))
  				lastStaticDependency= decl;
  		}
  		
  		if(lastStaticDependency == null)
  			fInsertFirst= true;
  		else
  			fToInsertAfter= lastStaticDependency;
  	}
  	
  	/* bd is a static field declaration or static initializer */
  	private static boolean depends(IExpressionFragment selected, BodyDeclaration bd) {
  		/* We currently consider selected to depend on bd only if db includes a declaration
  		 * of a static field on which selected depends.
  		 * 
  		 * A more accurate strategy might be to also check if bd contains (or is) a
  		 * static initializer containing code which changes the value of a static field on 
  		 * which selected depends.  However, if a static is written to multiple times within
  		 * during class initialization, it is difficult to predict which value should be used.
  		 * This would depend on which value is used by expressions instances for which the new 
  		 * constant will be substituted, and there may be many of these; in each, the
  		 * static field in question may have taken on a different value (if some of these uses
  		 * occur within static initializers).
  		 */
  		
  		if(bd instanceof FieldDeclaration) {
  			FieldDeclaration fieldDecl = (FieldDeclaration) bd;
  			for(Iterator fragments = fieldDecl.fragments().iterator(); fragments.hasNext();) {
  				VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragments.next();
  				SimpleName staticFieldName = fragment.getName();
  				if(selected.getSubFragmentsMatching(ASTFragmentFactory.createFragmentForFullSubtree(staticFieldName)).length != 0)
  					return true;
  			}
  		}
  		return false;
  	}
  
  	private boolean isDeclarationLocationComputed() {
  		return fInsertFirst == true || fToInsertAfter != null;	
  	}
  	
  	private boolean insertFirst() throws JavaScriptModelException {
  		if(!isDeclarationLocationComputed())
  			computeConstantDeclarationLocation();
  		return fInsertFirst;
  	}
  	
  	private BodyDeclaration getNodeToInsertConstantDeclarationAfter() throws JavaScriptModelException {
  		if(!isDeclarationLocationComputed())
  			computeConstantDeclarationLocation();
  		return fToInsertAfter;
  	}
  	
  	private Iterator getBodyDeclarations() throws JavaScriptModelException {
  		if(fBodyDeclarations == null)
  			fBodyDeclarations= getContainingTypeDeclarationNode().bodyDeclarations();
  		return fBodyDeclarations.iterator();
  	}
  
  	private String getConstantTypeName() throws JavaScriptModelException {
  		return ASTNodes.asString(getConstantType());
  	}
  
  	private static boolean isStaticFieldOrStaticInitializer(BodyDeclaration node) {
  		if(node instanceof FunctionDeclaration || node instanceof AbstractTypeDeclaration)
  			return false;
  		
  		int modifiers;
  		if(node instanceof FieldDeclaration) {
  			modifiers = ((FieldDeclaration) node).getModifiers();
  		} else if(node instanceof Initializer) {
  			modifiers = ((Initializer) node).getModifiers();
  		} else {
  			Assert.isTrue(false);
  			return false;
  		}
  		
  		if(!Modifier.isStatic(modifiers))
  			return false;
  		
  		return true;
  	}
  	
  	/*
  	 * Elements returned by next() are BodyDeclaration
  	 * instances.
  	 */
  	private Iterator getReplacementScope() throws JavaScriptModelException {
  		boolean declPredecessorReached= false;
  		
  		Collection scope= new ArrayList();
  		for(Iterator bodyDeclarations = getBodyDeclarations(); bodyDeclarations.hasNext();) {
  		    BodyDeclaration bodyDeclaration= (BodyDeclaration) bodyDeclarations.next();
  		    
  		    if(bodyDeclaration == getNodeToInsertConstantDeclarationAfter())
  		    	declPredecessorReached= true;
  		    
  		    if(insertFirst() || declPredecessorReached || !isStaticFieldOrStaticInitializer(bodyDeclaration))
  		    	scope.add(bodyDeclaration);
  		}
  		return scope.iterator();
  	}
  
  	private IASTFragment[] getFragmentsToReplace() throws JavaScriptModelException {
  		List toReplace = new ArrayList();
  		if (fReplaceAllOccurrences) {
  			Iterator replacementScope = getReplacementScope();
  			while(replacementScope.hasNext()) {
  				BodyDeclaration bodyDecl = (BodyDeclaration) replacementScope.next();
  				IASTFragment[] allMatches= ASTFragmentFactory.createFragmentForFullSubtree(bodyDecl).getSubFragmentsMatching(getSelectedExpression());
  				IASTFragment[] replaceableMatches = retainOnlyReplacableMatches(allMatches);
  				for(int i = 0; i < replaceableMatches.length; i++)
  					toReplace.add(replaceableMatches[i]);
  			}
  		} else if (canReplace(getSelectedExpression()))
  			toReplace.add(getSelectedExpression());
  		return (IASTFragment[]) toReplace.toArray(new IASTFragment[toReplace.size()]);
  	}
  
  	// !! - like one in ExtractTempRefactoring
  	private static IASTFragment[] retainOnlyReplacableMatches(IASTFragment[] allMatches) {
  		List result= new ArrayList(allMatches.length);
  		for (int i= 0; i < allMatches.length; i++) {
  			if (canReplace(allMatches[i]))
  				result.add(allMatches[i]);
  		}
  		return (IASTFragment[]) result.toArray(new IASTFragment[result.size()]);
  	}
  
  	// !! - like one in ExtractTempRefactoring
  	private static boolean canReplace(IASTFragment fragment) {
  		ASTNode node= fragment.getAssociatedNode();
  		ASTNode parent= node.getParent();
  		if (parent instanceof VariableDeclarationFragment) {
  			VariableDeclarationFragment vdf= (VariableDeclarationFragment) parent;
  			if (node.equals(vdf.getName()))
  				return false;
  		}
  		if (parent instanceof ExpressionStatement)
  			return false;
  		if (parent instanceof SwitchCase)
  			return false;
  		return true;
  	}
  
  	private IExpressionFragment getSelectedExpression() throws JavaScriptModelException {
  		if(fSelectedExpression != null)
  			return fSelectedExpression;
  		
  		IASTFragment selectedFragment= ASTFragmentFactory.createFragmentForSourceRange(new SourceRange(fSelectionStart, fSelectionLength), fCuRewrite.getRoot(), fCu);
  		
  		if (selectedFragment instanceof IExpressionFragment
  				&& ! Checks.isInsideJavadoc(selectedFragment.getAssociatedNode())) {
  			fSelectedExpression= (IExpressionFragment) selectedFragment;
  		}
  		
  		return fSelectedExpression;
  	}
  
  	private AbstractTypeDeclaration getContainingTypeDeclarationNode() throws JavaScriptModelException {
  		AbstractTypeDeclaration result= (AbstractTypeDeclaration) ASTNodes.getParent(getSelectedExpression().getAssociatedNode(), AbstractTypeDeclaration.class);  
  		Assert.isNotNull(result);
  		return result;
  	}
  
  	private ITypeBinding getContainingTypeBinding() throws JavaScriptModelException {
  		ITypeBinding result= getContainingTypeDeclarationNode().resolveBinding();
  		Assert.isNotNull(result);
  		return result;
  	}
  
  	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_CONSTANT);
  				else
  					fCu= (IJavaScriptUnit) element;
  			} 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= JdtFlags.getVisibilityString(flag);
  			}
  			final String name= extended.getAttribute(JDTRefactoringDescriptor.ATTRIBUTE_NAME);
  			if (name != null && !"".equals(name)) //$NON-NLS-1$
  				fConstantName= name;
  			else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_NAME));
  			final String replace= extended.getAttribute(ATTRIBUTE_REPLACE);
  			if (replace != null) {
  				fReplaceAllOccurrences= Boolean.valueOf(replace).booleanValue();
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_REPLACE));
  			final String declareFinal= extended.getAttribute(ATTRIBUTE_QUALIFY);
  			if (declareFinal != null) {
  				fQualifyReferencesWithDeclaringClassName= Boolean.valueOf(declareFinal).booleanValue();
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, ATTRIBUTE_QUALIFY));
  		} else
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
  		return new RefactoringStatus();
  	}
  }
  