blob: 5fbfff188edc76035dd7217f98114cc6fabdc723 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2006 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.jdt.internal.corext.refactoring.surround;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
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.core.filebuffers.FileBuffers;
import org.eclipse.core.filebuffers.ITextFileBufferManager;
import org.eclipse.core.resources.IFile;
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.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IExtendedModifier;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory;
import org.eclipse.jdt.internal.corext.dom.CodeScopeBuilder;
import org.eclipse.jdt.internal.corext.dom.Selection;
import org.eclipse.jdt.internal.corext.refactoring.Checks;
import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages;
import org.eclipse.jdt.internal.corext.refactoring.changes.CompilationUnitChange;
import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser;
import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil;
import org.eclipse.jdt.internal.corext.refactoring.util.SelectionAwareSourceRangeComputer;
import org.eclipse.jdt.internal.corext.util.Strings;
import org.eclipse.jdt.internal.ui.JavaPlugin;
/**
* 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 ICompilationUnit fCUnit;
private CompilationUnit fRootNode;
private ASTRewrite fRewriter;
private ImportRewrite fImportRewrite;
private CodeScopeBuilder.Scope fScope;
private ASTNode[] fSelectedNodes;
private SurroundWithTryCatchRefactoring(ICompilationUnit cu, Selection selection, ISurroundWithTryCatchQuery query) {
fCUnit= cu;
fSelection= selection;
fQuery= query;
fLeaveDirty= false;
}
public static SurroundWithTryCatchRefactoring create(ICompilationUnit cu, ITextSelection selection, ISurroundWithTryCatchQuery query) {
return new SurroundWithTryCatchRefactoring(cu, Selection.createFromStartLength(selection.getOffset(), selection.getLength()), query);
}
public static SurroundWithTryCatchRefactoring create(ICompilationUnit 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(CompilationUnit rootNode) throws JavaModelException {
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 {
CompilationUnit 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 ICompilationUnit[]{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, new SubProgressMonitor(pm, 1));
IDocument document= bufferManager.getTextFileBuffer(path).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.getJavaProject().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, JavaPlugin.getPluginId(), IStatus.ERROR,
e.getMessage(), e));
} finally {
bufferManager.disconnect(path, 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.getJavaProject());
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()));
}
}
CompilationUnit root= (CompilationUnit)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, lineSeparator);
if (s == null) {
return null;
} else {
return (Statement)fRewriter.createStringPlaceholder(s, ASTNode.RETURN_STATEMENT);
}
}
private IFile getFile() {
return (IFile) fCUnit.getPrimary().getResource();
}
}