content: /*******************************************************************************
   * Copyright (c) 2000, 2008 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.surround;
  
  import java.util.ArrayList;
  import java.util.Iterator;
  import java.util.List;
  
  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.CoreException;
  import org.eclipse.core.runtime.IPath;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.NullProgressMonitor;
  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.jface.text.ITextSelection;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.Refactoring;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.TextFileChange;
  import org.eclipse.text.edits.MultiTextEdit;
  import org.eclipse.text.edits.TextEdit;
  import org.eclipse.text.edits.TextEditGroup;
  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.Assignment;
  import org.eclipse.wst.jsdt.core.dom.Block;
  import org.eclipse.wst.jsdt.core.dom.CatchClause;
  import org.eclipse.wst.jsdt.core.dom.ChildListPropertyDescriptor;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.dom.Expression;
  import org.eclipse.wst.jsdt.core.dom.IExtendedModifier;
  import org.eclipse.wst.jsdt.core.dom.ITypeBinding;
  import org.eclipse.wst.jsdt.core.dom.IVariableBinding;
  import org.eclipse.wst.jsdt.core.dom.Modifier;
  import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration;
  import org.eclipse.wst.jsdt.core.dom.Statement;
  import org.eclipse.wst.jsdt.core.dom.TryStatement;
  import org.eclipse.wst.jsdt.core.dom.Type;
  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.internal.corext.codemanipulation.StubUtility;
  import org.eclipse.wst.jsdt.internal.corext.dom.ASTNodeFactory;
  import org.eclipse.wst.jsdt.internal.corext.dom.CodeScopeBuilder;
  import org.eclipse.wst.jsdt.internal.corext.dom.Selection;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
  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.Strings;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  
  /**
   * Surround a set of statements with a try/catch block.
   * 
   * Special case:
   * 
   * URL url= file.toURL();
   * 
   * In this case the variable declaration statement gets convert into a
   * declaration without initializer. So the body of the try/catch block 
   * only consists of new assignments. In this case we can't move the 
   * selected nodes (e.g. the declaration) into the try block.
   */
  public class SurroundWithTryCatchRefactoring extends Refactoring {
  
  	private Selection fSelection;
  	private ISurroundWithTryCatchQuery fQuery;
  	private SurroundWithTryCatchAnalyzer fAnalyzer;
  	private boolean fLeaveDirty;
  
  	private IJavaScriptUnit fCUnit;
  	private JavaScriptUnit fRootNode;
  	private ASTRewrite fRewriter;
  	private ImportRewrite fImportRewrite;
  	private CodeScopeBuilder.Scope fScope;
  	private ASTNode[] fSelectedNodes;
  
  	private SurroundWithTryCatchRefactoring(IJavaScriptUnit cu, Selection selection, ISurroundWithTryCatchQuery query) {
  		fCUnit= cu;
  		fSelection= selection;
  		fQuery= query;
  		fLeaveDirty= false;
  	}
  
  	public static SurroundWithTryCatchRefactoring create(IJavaScriptUnit cu, ITextSelection selection, ISurroundWithTryCatchQuery query) {
  		return new SurroundWithTryCatchRefactoring(cu, Selection.createFromStartLength(selection.getOffset(), selection.getLength()), query);
  	}
  		
  	public static SurroundWithTryCatchRefactoring create(IJavaScriptUnit cu, int offset, int length, ISurroundWithTryCatchQuery query) {
  		return new SurroundWithTryCatchRefactoring(cu, Selection.createFromStartLength(offset, length), query);
  	}
  
  	public void setLeaveDirty(boolean leaveDirty) {
  		fLeaveDirty= leaveDirty;
  	}
  	
  	public boolean stopExecution() {
  		if (fAnalyzer == null)
  			return true;
  		ITypeBinding[] exceptions= fAnalyzer.getExceptions();
  		return exceptions == null || exceptions.length == 0;
  	}
  	
  	/* non Java-doc
  	 * @see IRefactoring#getName()
  	 */
  	public String getName() {
  		return RefactoringCoreMessages.SurroundWithTryCatchRefactoring_name; 
  	}
  
  	public RefactoringStatus checkActivationBasics(JavaScriptUnit rootNode) throws JavaScriptModelException {
  		RefactoringStatus result= new RefactoringStatus();
  		fRootNode= rootNode;
  			
  		fAnalyzer= new SurroundWithTryCatchAnalyzer(fCUnit, fSelection, fQuery);
  		fRootNode.accept(fAnalyzer);
  		result.merge(fAnalyzer.getStatus());
  		return result;
  	}
  
  
  	/*
  	 * @see Refactoring#checkActivation(IProgressMonitor)
  	 */
  	public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException {
  		JavaScriptUnit rootNode= new RefactoringASTParser(AST.JLS3).parse(fCUnit, true, pm);
  		return checkActivationBasics(rootNode);
  	}
  
  	/*
  	 * @see Refactoring#checkInput(IProgressMonitor)
  	 */
  	public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException {
  		return Checks.validateModifiesFiles(
  			ResourceUtil.getFiles(new IJavaScriptUnit[]{fCUnit}),
  			getValidationContext());
  	}
  
  	/* non Java-doc
  	 * @see IRefactoring#createChange(IProgressMonitor)
  	 */
  	public Change createChange(IProgressMonitor pm) throws CoreException {
  		final String NN= ""; //$NON-NLS-1$
  		if (pm == null) pm= new NullProgressMonitor();
  		pm.beginTask(NN, 2);
  		// This is cheap since the compilation unit is already open in a editor.
  		IPath path= getFile().getFullPath();
  		ITextFileBufferManager bufferManager= FileBuffers.getTextFileBufferManager();
  		try {
  			bufferManager.connect(path, LocationKind.IFILE, new SubProgressMonitor(pm, 1));
  			IDocument document= bufferManager.getTextFileBuffer(path, LocationKind.IFILE).getDocument();
  			final CompilationUnitChange result= new CompilationUnitChange(getName(), fCUnit);
  			if (fLeaveDirty)
  				result.setSaveMode(TextFileChange.LEAVE_DIRTY);
  			MultiTextEdit root= new MultiTextEdit();
  			result.setEdit(root);
  			fRewriter= ASTRewrite.create(fAnalyzer.getEnclosingBodyDeclaration().getAST());
  			fRewriter.setTargetSourceRangeComputer(new SelectionAwareSourceRangeComputer(
  				fAnalyzer.getSelectedNodes(), document, fSelection.getOffset(), fSelection.getLength()));
  			fImportRewrite= StubUtility.createImportRewrite(fRootNode, true);
  			
  			fScope= CodeScopeBuilder.perform(fAnalyzer.getEnclosingBodyDeclaration(), fSelection).
  				findScope(fSelection.getOffset(), fSelection.getLength());
  			fScope.setCursor(fSelection.getOffset());
  			
  			fSelectedNodes= fAnalyzer.getSelectedNodes();
  			
  			createTryCatchStatement(document);
  			
  			if (fImportRewrite.hasRecordedChanges()) {
  				TextEdit edit= fImportRewrite.rewriteImports(null);
  				root.addChild(edit);
  				result.addTextEditGroup(new TextEditGroup(NN, new TextEdit[] {edit} ));
  			}
  			TextEdit change= fRewriter.rewriteAST(document, fCUnit.getJavaScriptProject().getOptions(true));
  			root.addChild(change);
  			result.addTextEditGroup(new TextEditGroup(NN, new TextEdit[] {change} ));
  			return result;
  		} 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();
  		}
  	}
  	
  	private AST getAST() {
  		return fRootNode.getAST();
  	}
  	
  	private void createTryCatchStatement(IDocument document) throws CoreException, BadLocationException {
  		String lineDelimiter= document.getLineDelimiter(0);
  		List result= new ArrayList(1);
  		TryStatement tryStatement= getAST().newTryStatement();
  		ITypeBinding[] exceptions= fAnalyzer.getExceptions();
  		for (int i= 0; i < exceptions.length; i++) {
  			ITypeBinding exception= exceptions[i];
  			String type= fImportRewrite.addImport(exception);
  			CatchClause catchClause= getAST().newCatchClause();
  			tryStatement.catchClauses().add(catchClause);
  			SingleVariableDeclaration decl= getAST().newSingleVariableDeclaration();
  			String varName= StubUtility.getExceptionVariableName(fCUnit.getJavaScriptProject());
  			
  			String name= fScope.createName(varName, false);
  			decl.setName(getAST().newSimpleName(name));
  			decl.setType(ASTNodeFactory.newType(getAST(), type));
  			catchClause.setException(decl);
  			Statement st= getCatchBody(type, name, lineDelimiter);
  			if (st != null) {
  				catchClause.getBody().statements().add(st);
  			}
  		}
  		List variableDeclarations= getSpecialVariableDeclarationStatements();
  		ListRewrite statements= fRewriter.getListRewrite(tryStatement.getBody(), Block.STATEMENTS_PROPERTY);
  		boolean selectedNodeRemoved= false;
  		ASTNode expressionStatement= null;
  		for (int i= 0; i < fSelectedNodes.length; i++) {
  			ASTNode node= fSelectedNodes[i];
  			if (node instanceof VariableDeclarationStatement && variableDeclarations.contains(node)) {
  				AST ast= getAST();
  				VariableDeclarationStatement statement= (VariableDeclarationStatement)node;
  				// Create a copy and remove the initializer
  				VariableDeclarationStatement copy= (VariableDeclarationStatement)ASTNode.copySubtree(ast, statement);
  				List modifiers= copy.modifiers();
  				for (Iterator iter= modifiers.iterator(); iter.hasNext();) {
  					IExtendedModifier modifier= (IExtendedModifier) iter.next();
  					if (modifier.isModifier() && Modifier.isFinal(((Modifier)modifier).getKeyword().toFlagValue())) {
  						iter.remove();
  					}
  				}
  				List fragments= copy.fragments();
  				for (Iterator iter= fragments.iterator(), original= statement.fragments().iterator(); iter.hasNext();) {
  					VariableDeclarationFragment fragment= (VariableDeclarationFragment)iter.next();
  					IVariableBinding binding= ((VariableDeclarationFragment)original.next()).resolveBinding();
  					// If we want to initialize the new local then we should do a flow analysis upfront
  					// to decide if the first access is a read or write.
  					if (true /* binding == null */) {
  						fragment.setInitializer(null);
  					} else {
  						fragment.setInitializer(ASTNodeFactory.newDefaultExpression(ast, binding.getType()));
  					}
  				}
  				JavaScriptUnit root= (JavaScriptUnit)statement.getRoot();
  				int extendedStart= root.getExtendedStartPosition(statement);
  				// we have a leading comment and the comment is covered by the selection
  				if (extendedStart != statement.getStartPosition() && extendedStart >= fSelection.getOffset()) {
  					String commentToken= document.get(extendedStart, statement.getStartPosition() - extendedStart);
  					commentToken= Strings.trimTrailingTabsAndSpaces(commentToken);
  					Type type= statement.getType();
  					String typeName= document.get(type.getStartPosition(), type.getLength());
  					copy.setType((Type)fRewriter.createStringPlaceholder(commentToken + typeName, type.getNodeType()));
  				}
  				result.add(copy);
  				// convert the fragments into expression statements
  				fragments= statement.fragments();
  				if (!fragments.isEmpty()) {
  					List newExpressionStatements= new ArrayList();
  					for (Iterator iter= fragments.iterator(); iter.hasNext();) {
  						VariableDeclarationFragment fragment= (VariableDeclarationFragment)iter.next();
  						Expression initializer= fragment.getInitializer();
  						if (initializer != null) {
  							Assignment assignment= ast.newAssignment();
  							assignment.setLeftHandSide((Expression)fRewriter.createCopyTarget(fragment.getName()));
  							assignment.setRightHandSide((Expression)fRewriter.createCopyTarget(initializer));
  							newExpressionStatements.add(ast.newExpressionStatement(assignment));
  						}
  					}
  					if (!newExpressionStatements.isEmpty()) {
  						if (fSelectedNodes.length == 1) {
  							expressionStatement= fRewriter.createGroupNode((ASTNode[])newExpressionStatements.toArray(new ASTNode[newExpressionStatements.size()]));
  						} else {
  							fRewriter.replace(
  								statement, 
  								fRewriter.createGroupNode((ASTNode[])newExpressionStatements.toArray(new ASTNode[newExpressionStatements.size()])), 
  								null);
  						}
  					} else {
  						fRewriter.remove(statement, null);
  						selectedNodeRemoved= true;
  					}
  				} else {
  					fRewriter.remove(statement, null);
  					selectedNodeRemoved= true;
  				}
  			}
  		}
  		result.add(tryStatement);
  		ASTNode replacementNode;
  		if (result.size() == 1) {
  			replacementNode= (ASTNode)result.get(0);
  		} else {
  			replacementNode= fRewriter.createGroupNode((ASTNode[])result.toArray(new ASTNode[result.size()]));
  		}
  		if (fSelectedNodes.length == 1) {
  			if (expressionStatement != null) {
  				statements.insertLast(expressionStatement, null);
  			} else {
  				if (!selectedNodeRemoved)
  					statements.insertLast(fRewriter.createMoveTarget(fSelectedNodes[0]), null);
  			}
  			fRewriter.replace(fSelectedNodes[0], replacementNode, null);
  		} else {
  			ListRewrite source= fRewriter.getListRewrite(
  				fSelectedNodes[0].getParent(), 
  				(ChildListPropertyDescriptor)fSelectedNodes[0].getLocationInParent());
  			ASTNode toMove= source.createMoveTarget(
  				fSelectedNodes[0], fSelectedNodes[fSelectedNodes.length - 1],
  				replacementNode, null);
  			statements.insertLast(toMove, null);
  		}
  	}
  	
  	private List getSpecialVariableDeclarationStatements() {
  		List result= new ArrayList(3);
  		VariableDeclaration[] locals= fAnalyzer.getAffectedLocals();
  		for (int i= 0; i < locals.length; i++) {
  			ASTNode parent= locals[i].getParent();
  			if (parent instanceof VariableDeclarationStatement && !result.contains(parent))
  				result.add(parent);
  		}
  		return result;
  		
  	}
  	
  	private Statement getCatchBody(String type, String name, String lineSeparator) throws CoreException {
  		String s= StubUtility.getCatchBodyContent(fCUnit, type, name, fSelectedNodes[0], lineSeparator);
  		if (s == null) {
  			return null;
  		} else {
  			return (Statement)fRewriter.createStringPlaceholder(s, ASTNode.RETURN_STATEMENT);
  		}
  	}
  	
  	private IFile getFile() {
  		return (IFile) fCUnit.getPrimary().getResource();
  	}
  }
  