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.HashMap;
  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.SubProgressMonitor;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.Document;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.IRegion;
  import org.eclipse.jface.text.TextUtilities;
  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.RefactoringStatusContext;
  import org.eclipse.ltk.core.refactoring.participants.RefactoringArguments;
  import org.eclipse.text.edits.MalformedTreeException;
  import org.eclipse.text.edits.RangeMarker;
  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.ISourceRange;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.dom.ASTNode;
  import org.eclipse.wst.jsdt.core.dom.ArrayCreation;
  import org.eclipse.wst.jsdt.core.dom.ArrayInitializer;
  import org.eclipse.wst.jsdt.core.dom.ArrayType;
  import org.eclipse.wst.jsdt.core.dom.Assignment;
  import org.eclipse.wst.jsdt.core.dom.CatchClause;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.FieldDeclaration;
  import org.eclipse.wst.jsdt.core.dom.ForStatement;
  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.IVariableBinding;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.ParenthesizedExpression;
  import org.eclipse.wst.jsdt.core.dom.SimpleName;
  import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.SuperMethodInvocation;
  import org.eclipse.wst.jsdt.core.dom.Type;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression;
  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.ListRewrite;
  import org.eclipse.wst.jsdt.core.refactoring.IJavaScriptRefactorings;
  import org.eclipse.wst.jsdt.internal.corext.SourceRange;
  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.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.JavaStatusContext;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.CompilationUnitChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.rename.TempDeclarationFinder;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.rename.TempOccurrenceAnalyzer;
  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.refactoring.util.ResourceUtil;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.corext.util.Strings;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.viewsupport.BindingLabelProvider;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabels;
  
  public class InlineTempRefactoring extends ScriptableRefactoring {
  
  	private int fSelectionStart;
  	private int fSelectionLength;
  	private IJavaScriptUnit fCu;
  	
  	//the following fields are set after the construction
  	private VariableDeclaration fVariableDeclaration;
  	private SimpleName[] fReferences;
  	private JavaScriptUnit fASTRoot;
  
  	/**
  	 * Creates a new inline constant refactoring.
  	 * @param unit the compilation unit, or <code>null</code> if invoked by scripting
  	 * @param node compilation unit node, or <code>null</code>
  	 * @param selectionStart
  	 * @param selectionLength
  	 */
  	public InlineTempRefactoring(IJavaScriptUnit unit, JavaScriptUnit node, int selectionStart, int selectionLength) {
  		Assert.isTrue(selectionStart >= 0);
  		Assert.isTrue(selectionLength >= 0);
  		fSelectionStart= selectionStart;
  		fSelectionLength= selectionLength;
  		fCu= unit;
  		
  		fASTRoot= node;
  		fVariableDeclaration= null;
  	}
  	
  	/**
  	 * Creates a new inline constant refactoring.
  	 * @param unit the compilation unit, or <code>null</code> if invoked by scripting
  	 * @param selectionStart
  	 * @param selectionLength
  	 */
  	public InlineTempRefactoring(IJavaScriptUnit unit, int selectionStart, int selectionLength) {
  		this(unit, null, selectionStart, selectionLength);
  	}
  	
  	public InlineTempRefactoring(VariableDeclaration decl) {
  		fVariableDeclaration= decl;
  		ASTNode astRoot= decl.getRoot();
  		Assert.isTrue(astRoot instanceof JavaScriptUnit);
  		fASTRoot= (JavaScriptUnit) astRoot;
  		Assert.isTrue(fASTRoot.getJavaElement() instanceof IJavaScriptUnit);
  		
  		fSelectionStart= decl.getStartPosition();
  		fSelectionLength= decl.getLength();
  		fCu= (IJavaScriptUnit) fASTRoot.getJavaElement();
  	}
  	
  	public RefactoringStatus checkIfTempSelected() {
  		VariableDeclaration decl= getVariableDeclaration();
  		if (decl == null) {
  			return CodeRefactoringUtil.checkMethodSyntaxErrors(fSelectionStart, fSelectionLength, getASTRoot(), RefactoringCoreMessages.InlineTempRefactoring_select_temp);
  		}
  		if (decl.getParent() instanceof FieldDeclaration) {
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InlineTemRefactoring_error_message_fieldsCannotBeInlined); 
  		}
  		return new RefactoringStatus();
  	}	
  	
  	private JavaScriptUnit getASTRoot() {
  		if (fASTRoot == null) {
  			fASTRoot= RefactoringASTParser.parseWithASTProvider(fCu, true, null);
  		}
  		return fASTRoot;
  	}
  	
  	public VariableDeclaration getVariableDeclaration() {
  		if (fVariableDeclaration == null) {
  			fVariableDeclaration= TempDeclarationFinder.findTempDeclaration(getASTRoot(), fSelectionStart, fSelectionLength);
  		}
  		return fVariableDeclaration;
  	}
  	
  	/*
  	 * @see IRefactoring#getName()
  	 */
  	public String getName() {
  		return RefactoringCoreMessages.InlineTempRefactoring_name; 
  	}
  	
  	/*
  	 * @see Refactoring#checkActivation(IProgressMonitor)
  	 */
  	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
  		try {
  			pm.beginTask("", 1); //$NON-NLS-1$
  			
  			RefactoringStatus result= Checks.validateModifiesFiles(ResourceUtil.getFiles(new IJavaScriptUnit[]{fCu}), getValidationContext());
  			if (result.hasFatalError())
  				return result;
  					
  			VariableDeclaration declaration= getVariableDeclaration();
  			
  			result.merge(checkSelection(declaration));
  			if (result.hasFatalError())
  				return result;
  			
  			result.merge(checkInitializer(declaration));	
  			return result;
  		} finally {
  			pm.done();
  		}	
  	}
  
      private RefactoringStatus checkInitializer(VariableDeclaration decl) {
  		if (decl.getInitializer().getNodeType() == ASTNode.NULL_LITERAL)
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InlineTemRefactoring_error_message_nulLiteralsCannotBeInlined);
  		return null;
  	}
  
  	private RefactoringStatus checkSelection(VariableDeclaration decl) {
  		ASTNode parent= decl.getParent();
  		if (parent instanceof FunctionDeclaration) {
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InlineTempRefactoring_method_parameter); 
  		}
  		
  		if (parent instanceof CatchClause) {
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InlineTempRefactoring_exceptions_declared); 
  		}
  		
  		if (parent instanceof VariableDeclarationExpression && parent.getLocationInParent() == ForStatement.INITIALIZERS_PROPERTY) {
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InlineTempRefactoring_for_initializers); 
  		}
  		
  		if (decl.getInitializer() == null) {
  			String message= Messages.format(RefactoringCoreMessages.InlineTempRefactoring_not_initialized, decl.getName().getIdentifier());
  			return RefactoringStatus.createFatalErrorStatus(message);
  		}	
  				
  		return checkAssignments(decl);
  	}
  	
  	private RefactoringStatus checkAssignments(VariableDeclaration decl) {
  		TempAssignmentFinder assignmentFinder= new TempAssignmentFinder(decl);
  		getASTRoot().accept(assignmentFinder);
  		if (!assignmentFinder.hasAssignments())
  			return new RefactoringStatus();
  		ASTNode firstAssignment= assignmentFinder.getFirstAssignment();
  		int start= firstAssignment.getStartPosition();
  		int length= firstAssignment.getLength();
  		ISourceRange range= new SourceRange(start, length);
  		RefactoringStatusContext context= JavaStatusContext.create(fCu, range);	
  		String message= Messages.format(RefactoringCoreMessages.InlineTempRefactoring_assigned_more_once, decl.getName().getIdentifier());
  		return RefactoringStatus.createFatalErrorStatus(message, context);
  	}
  	
  	/*
  	 * @see Refactoring#checkInput(IProgressMonitor)
  	 */
  	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
  		try {
  			pm.beginTask("", 1); //$NON-NLS-1$
  			return new RefactoringStatus();
  		} finally {
  			pm.done();
  		}	
  	}
  	
  	//----- changes
  
  	public Change createChange(IProgressMonitor pm) throws CoreException {
  		try {
  			pm.beginTask(RefactoringCoreMessages.InlineTempRefactoring_preview, 2);
  			final Map arguments= new HashMap();
  			String project= null;
  			IJavaScriptProject javaProject= fCu.getJavaScriptProject();
  			if (javaProject != null)
  				project= javaProject.getElementName();
  			
  			final IVariableBinding binding= getVariableDeclaration().resolveBinding();
  			String text= null;
  			final IFunctionBinding method= binding.getDeclaringMethod();
  			if (method != null)
  				text= BindingLabelProvider.getBindingLabel(method, JavaScriptElementLabels.ALL_FULLY_QUALIFIED);
  			else
  				text= '{' + JavaScriptElementLabels.ELLIPSIS_STRING + '}';
  			final String description= Messages.format(RefactoringCoreMessages.InlineTempRefactoring_descriptor_description_short, binding.getName());
  			final String header= Messages.format(RefactoringCoreMessages.InlineTempRefactoring_descriptor_description, new String[] { BindingLabelProvider.getBindingLabel(binding, JavaScriptElementLabels.ALL_FULLY_QUALIFIED), text});
  			final JDTRefactoringDescriptorComment comment= new JDTRefactoringDescriptorComment(project, this, header);
  			comment.addSetting(Messages.format(RefactoringCoreMessages.InlineTempRefactoring_original_pattern, BindingLabelProvider.getBindingLabel(binding, JavaScriptElementLabels.ALL_FULLY_QUALIFIED)));
  			final JDTRefactoringDescriptor descriptor= new JDTRefactoringDescriptor(IJavaScriptRefactorings.INLINE_LOCAL_VARIABLE, project, description, comment.asString(), arguments, RefactoringDescriptor.NONE);
  			arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_INPUT, descriptor.elementToHandle(fCu));
  			arguments.put(JDTRefactoringDescriptor.ATTRIBUTE_SELECTION, String.valueOf(fSelectionStart) + ' ' + String.valueOf(fSelectionLength));
  			
  			CompilationUnitRewrite cuRewrite= new CompilationUnitRewrite(fCu, fASTRoot);
  			
  			inlineTemp(cuRewrite);
  			removeTemp(cuRewrite);
  			
  			final CompilationUnitChange result= cuRewrite.createChange(RefactoringCoreMessages.InlineTempRefactoring_inline, false, new SubProgressMonitor(pm, 1));
  			result.setDescriptor(new RefactoringChangeDescriptor(descriptor));
  			return result;
  		} finally {
  			pm.done();
  		}
  	}
  
  	private void inlineTemp(CompilationUnitRewrite cuRewrite) throws JavaScriptModelException {
  		SimpleName[] references= getReferences();
  
  		TextEditGroup groupDesc= cuRewrite.createGroupDescription(RefactoringCoreMessages.InlineTempRefactoring_inline_edit_name);
  		ASTRewrite rewrite= cuRewrite.getASTRewrite();
  
  		for (int i= 0; i < references.length; i++){
  			SimpleName curr= references[i];
  			ASTNode initializerCopy= getInitializerSource(cuRewrite, curr);
  			rewrite.replace(curr, initializerCopy, groupDesc);
  		}
  	}
  	
      private boolean needsBrackets(SimpleName name, VariableDeclaration variableDeclaration) {
  		Expression initializer= variableDeclaration.getInitializer();
  		if (initializer instanceof Assignment) //for esthetic reasons
  			return true;
      		
      	return ASTNodes.substituteMustBeParenthesized(initializer, name);
      }
      
  
  	private void removeTemp(CompilationUnitRewrite cuRewrite) throws JavaScriptModelException {
  		VariableDeclaration variableDeclaration= getVariableDeclaration();
  		TextEditGroup groupDesc= cuRewrite.createGroupDescription(RefactoringCoreMessages.InlineTempRefactoring_remove_edit_name);
  		ASTNode parent= variableDeclaration.getParent();
  		ASTRewrite rewrite= cuRewrite.getASTRewrite();
  		if (parent instanceof VariableDeclarationStatement && ((VariableDeclarationStatement) parent).fragments().size() == 1) {
  			rewrite.remove(parent, groupDesc);
  		} else {
  			rewrite.remove(variableDeclaration, groupDesc);
  		}
  	}
  	
  	private Expression getInitializerSource(CompilationUnitRewrite rewrite, SimpleName reference) throws JavaScriptModelException {
  		Expression copy= getModifiedInitializerSource(rewrite, reference);
  		boolean brackets= needsBrackets(reference, getVariableDeclaration());
  		if (brackets) {
  			ParenthesizedExpression parentExpr= rewrite.getAST().newParenthesizedExpression();
  			parentExpr.setExpression(copy);
  			return parentExpr;
  		}
  		return copy;
  	}
  	
  	private Expression getModifiedInitializerSource(CompilationUnitRewrite rewrite, SimpleName reference) throws JavaScriptModelException {
  		VariableDeclaration varDecl= getVariableDeclaration();
  		Expression initializer= varDecl.getInitializer();
  		
  		ASTNode referenceContext= reference.getParent();
  		if (isInvocation(initializer)) {
  			if (Invocations.isResolvedTypeInferredFromExpectedType(initializer)) {
  				if (! (referenceContext instanceof VariableDeclarationFragment
  						|| referenceContext instanceof SingleVariableDeclaration
  						|| referenceContext instanceof Assignment)) {
  					Invocations.resolveBinding(initializer);
  					String newSource= createParameterizedInvocation(initializer, new Type[0]);
  					return (Expression) rewrite.getASTRewrite().createStringPlaceholder(newSource, initializer.getNodeType());
  				}
  			}
  		}
  		
  		Expression copy= (Expression) rewrite.getASTRewrite().createCopyTarget(initializer);
  		if (initializer instanceof ArrayInitializer && ASTNodes.getDimensions(varDecl) > 0) {
  			ArrayType newType= (ArrayType) ASTNodeFactory.newType(rewrite.getAST(), varDecl);
  			
  			ArrayCreation newArrayCreation= rewrite.getAST().newArrayCreation();
  			newArrayCreation.setType(newType);
  			newArrayCreation.setInitializer((ArrayInitializer) copy);
  			return newArrayCreation;
  		}
  		return copy;
  	}
  
  	private String createParameterizedInvocation(Expression invocation, Type[] typeArgumentNodes) throws JavaScriptModelException {
  		ASTRewrite rewrite= ASTRewrite.create(invocation.getAST());
  		ListRewrite typeArgsRewrite= rewrite.getListRewrite(invocation, Invocations.getTypeArgumentsProperty(invocation));
  		for (int i= 0; i < typeArgumentNodes.length; i++) {
  			typeArgsRewrite.insertLast(typeArgumentNodes[i], null);
  		}
  		
  		IDocument document= new Document(fCu.getBuffer().getContents());
  		final RangeMarker marker= new RangeMarker(invocation.getStartPosition(), invocation.getLength());
  		IJavaScriptProject project= fCu.getJavaScriptProject();
  		TextEdit[] rewriteEdits= rewrite.rewriteAST(document, project.getOptions(true)).removeChildren();
  		marker.addChildren(rewriteEdits);
  		try {
  			marker.apply(document, TextEdit.UPDATE_REGIONS);
  			String rewrittenInitializer= document.get(marker.getOffset(), marker.getLength());
  			IRegion region= document.getLineInformation(document.getLineOfOffset(marker.getOffset()));
  			int oldIndent= Strings.computeIndentUnits(document.get(region.getOffset(), region.getLength()), project);
  			return Strings.changeIndent(rewrittenInitializer, oldIndent, project, "", TextUtilities.getDefaultLineDelimiter(document)); //$NON-NLS-1$
  		} catch (MalformedTreeException e) {
  			JavaScriptPlugin.log(e);
  		} catch (BadLocationException e) {
  			JavaScriptPlugin.log(e);
  		}
  		//fallback:
  		return fCu.getBuffer().getText(invocation.getStartPosition(), invocation.getLength());
  	}
  	
  	private static boolean isInvocation(Expression node) {
  		return node instanceof FunctionInvocation || node instanceof SuperMethodInvocation;
  	}
  
  	public SimpleName[] getReferences() {
  		if (fReferences != null)
  			return fReferences;
  		TempOccurrenceAnalyzer analyzer= new TempOccurrenceAnalyzer(getVariableDeclaration(), false);
  		analyzer.perform();
  		fReferences= analyzer.getReferenceNodes();
  		return fReferences;
  	}
  
  	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.INLINE_LOCAL_VARIABLE);
  				else {
  					fCu= (IJavaScriptUnit) element;
  		        	if (checkIfTempSelected().hasFatalError())
  						return createInputFatalStatus(element, IJavaScriptRefactorings.INLINE_LOCAL_VARIABLE);
  				}
  			} else
  				return RefactoringStatus.createFatalErrorStatus(Messages.format(RefactoringCoreMessages.InitializableRefactoring_argument_not_exist, JDTRefactoringDescriptor.ATTRIBUTE_INPUT));
  		} else
  			return RefactoringStatus.createFatalErrorStatus(RefactoringCoreMessages.InitializableRefactoring_inacceptable_arguments);
  		return new RefactoringStatus();
  	}
  }
  