| /******************************************************************************* |
| * 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.ui.text.correction; |
| |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.CoreException; |
| |
| import org.eclipse.jdt.core.IJavaElement; |
| import org.eclipse.jdt.core.IType; |
| 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.BodyDeclaration; |
| import org.eclipse.jdt.core.dom.CompilationUnit; |
| import org.eclipse.jdt.core.dom.Expression; |
| 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.SimpleName; |
| import org.eclipse.jdt.core.dom.SingleVariableDeclaration; |
| import org.eclipse.jdt.core.dom.Statement; |
| import org.eclipse.jdt.core.dom.SwitchStatement; |
| import org.eclipse.jdt.core.dom.ThisExpression; |
| import org.eclipse.jdt.core.dom.Type; |
| import org.eclipse.jdt.core.dom.VariableDeclaration; |
| import org.eclipse.jdt.core.dom.VariableDeclarationExpression; |
| 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.ASTNodes; |
| import org.eclipse.jdt.internal.corext.dom.GenericVisitor; |
| import org.eclipse.jdt.internal.corext.dom.LocalVariableIndex; |
| import org.eclipse.jdt.internal.corext.dom.ModifierRewrite; |
| import org.eclipse.jdt.internal.corext.dom.Selection; |
| import org.eclipse.jdt.internal.corext.dom.VariableDeclarationRewrite; |
| import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowContext; |
| import org.eclipse.jdt.internal.corext.refactoring.code.flow.FlowInfo; |
| import org.eclipse.jdt.internal.corext.refactoring.code.flow.InOutFlowAnalyzer; |
| import org.eclipse.jdt.internal.corext.refactoring.surround.SurroundWithAnalyzer; |
| |
| import org.eclipse.jdt.ui.text.java.IInvocationContext; |
| |
| public abstract class SurroundWith { |
| |
| private static final class SplitSelectedOperator implements ISplitOperation { |
| |
| private List fAccessedInside; |
| private ListRewrite fStatementRewrite; |
| private List fAccessedAfter; |
| private ASTRewrite fRewrite; |
| private ListRewrite fBlockRewrite; |
| private VariableDeclarationStatement fLastStatement= null; |
| |
| public SplitSelectedOperator(List inside, List after, ListRewrite blockRewrite, ASTRewrite rewrite, ListRewrite statementRewrite) { |
| super(); |
| fAccessedInside= inside; |
| fStatementRewrite= statementRewrite; |
| fAccessedAfter= after; |
| fRewrite= rewrite; |
| fBlockRewrite= blockRewrite; |
| } |
| |
| public boolean needsSplit(VariableDeclarationFragment last, VariableDeclarationFragment current) { |
| return fAccessedInside.contains(last) != fAccessedInside.contains(current) || fAccessedAfter.contains(last) != fAccessedAfter.contains(current); |
| } |
| |
| public void initializeStatement(VariableDeclarationStatement statement, VariableDeclarationFragment current) { |
| if (fAccessedAfter.contains(current)) { |
| if (fAccessedInside.contains(current)) |
| makeFinal(statement, fRewrite); |
| handleInitializer(current); |
| |
| if (fLastStatement != null) { |
| fBlockRewrite.insertAfter(statement, fLastStatement, null); |
| } |
| fLastStatement= statement; |
| } else { |
| if (fLastStatement != null) { |
| handleNewStatement(statement); |
| } else { |
| handleStatement(statement); |
| fLastStatement= statement; |
| } |
| } |
| } |
| |
| protected void handleStatement(Statement statement) { |
| fStatementRewrite.insertLast(fRewrite.createMoveTarget(statement), null); |
| } |
| |
| protected void handleNewStatement(Statement statement) { |
| fStatementRewrite.insertLast(statement, null); |
| } |
| |
| protected void handleInitializer(VariableDeclarationFragment fragment) { |
| splitOffInitializer(fStatementRewrite, fragment, fRewrite); |
| } |
| |
| } |
| |
| private static final class SplitUnselectedOperator implements ISplitOperation { |
| |
| private List fAccessedInside; |
| private ListRewrite fBlockRewrite; |
| private ASTRewrite fRewrite; |
| private VariableDeclarationStatement fLastStatement; |
| |
| private SplitUnselectedOperator(List accessedInside, ListRewrite blockRewrite, ASTRewrite rewrite) { |
| super(); |
| fAccessedInside= accessedInside; |
| fBlockRewrite= blockRewrite; |
| fRewrite= rewrite; |
| fLastStatement= null; |
| } |
| |
| public boolean needsSplit(VariableDeclarationFragment last, VariableDeclarationFragment current) { |
| return fAccessedInside.contains(last) != fAccessedInside.contains(current); |
| } |
| |
| public void initializeStatement(VariableDeclarationStatement statement, VariableDeclarationFragment current) { |
| if (fAccessedInside.contains(current)) |
| makeFinal(statement, fRewrite); |
| |
| if (fLastStatement != null) |
| fBlockRewrite.insertAfter(statement, fLastStatement, null); |
| fLastStatement= statement; |
| } |
| } |
| |
| protected interface ISplitOperation { |
| boolean needsSplit(VariableDeclarationFragment last, VariableDeclarationFragment current); |
| void initializeStatement(VariableDeclarationStatement statement, VariableDeclarationFragment current); |
| } |
| |
| private final CompilationUnit fRootNode; |
| private final Statement[] fSelectedStatements; |
| private boolean fIsNewContext; |
| |
| public SurroundWith(CompilationUnit root, Statement[] selectedStatements) { |
| fRootNode= root; |
| fSelectedStatements= selectedStatements; |
| } |
| |
| /** |
| * Selected nodes in <code>context</code> under <code>selection</code> or null if no valid selection. |
| * @param context The context in which the proposal is applyed. |
| * @return Selected nodes or null if no valid selection. |
| * @throws JavaModelException |
| */ |
| public static Statement[] getSelectedStatements(IInvocationContext context) throws JavaModelException { |
| Selection selection= Selection.createFromStartLength(context.getSelectionOffset(), context.getSelectionLength()); |
| SurroundWithAnalyzer analyzer= new SurroundWithAnalyzer(context.getCompilationUnit(), selection); |
| context.getASTRoot().accept(analyzer); |
| |
| if (!analyzer.getStatus().isOK() || !analyzer.hasSelectedNodes()) { |
| return null; |
| } else { |
| return analyzer.getSelectedStatements(); |
| } |
| } |
| |
| /** |
| * Returns the rewriter to be used. |
| * @return Returns the rewriter to be used. |
| * @throws CoreException A core exception is thrown when the could not be created. |
| */ |
| public ASTRewrite getRewrite() throws CoreException { |
| Statement[] selectedStatements= fSelectedStatements; |
| AST ast= getAst(); |
| |
| ASTRewrite rewrite= ASTRewrite.create(ast); |
| Block newBody= ast.newBlock(); |
| Statement insertedCode= generateCodeSkeleton(newBody, rewrite); |
| |
| BodyDeclaration enclosingBodyDeclaration= (BodyDeclaration)ASTNodes.getParent(selectedStatements[0], BodyDeclaration.class); |
| int maxVariableId= LocalVariableIndex.perform(enclosingBodyDeclaration) + 1; |
| |
| fIsNewContext= isNewContext(); |
| |
| List accessedAfter= getVariableDeclarationsAccessedAfter(selectedStatements[selectedStatements.length - 1], maxVariableId); |
| List readInside; |
| readInside= getVariableDeclarationReadsInside(selectedStatements, maxVariableId); |
| |
| ListRewrite listRewrite= rewrite.getListRewrite(newBody, Block.STATEMENTS_PROPERTY); |
| moveToBlock(selectedStatements, listRewrite, accessedAfter, readInside, rewrite); |
| if (fIsNewContext) { |
| ImportRewrite importRewrite= StubUtility.createImportRewrite((CompilationUnit)selectedStatements[0].getRoot(), false); |
| for (int i= 0; i < selectedStatements.length; i++) { |
| qualifyThisExpressions(selectedStatements[i], rewrite, importRewrite); |
| } |
| } |
| |
| if (selectedStatements.length == 1 && ASTNodes.isControlStatementBody(selectedStatements[0].getLocationInParent())) { |
| Block wrap= ast.newBlock(); |
| rewrite.replace(selectedStatements[0], wrap, null); |
| rewrite.getListRewrite(wrap, Block.STATEMENTS_PROPERTY).insertFirst(insertedCode, null); |
| } else { |
| getListRewrite(selectedStatements[0], rewrite).insertAfter(insertedCode, selectedStatements[selectedStatements.length - 1], null); |
| } |
| return rewrite; |
| } |
| |
| /** |
| * Generate a new code skeleton. |
| * @param newBody The new body which will be filled with code. |
| * @param rewrite The rewrite to use to change the ast. |
| * @return The root of the new code. |
| */ |
| protected abstract Statement generateCodeSkeleton(Block newBody, ASTRewrite rewrite); |
| |
| /** |
| * Will the code be moved to a new context? |
| */ |
| protected abstract boolean isNewContext(); |
| |
| /** |
| * List of VariableDeclaration of variables which are read in <code>selectedNodes</code>. |
| * |
| * @param maxVariableId Maximum number of variable declarations block |
| * @param selectedNodes The selectedNodes |
| * @return List of VariableDeclaration |
| */ |
| protected List getVariableDeclarationReadsInside(Statement[] selectedNodes, int maxVariableId) { |
| ArrayList result= new ArrayList(); |
| if (!fIsNewContext) |
| return result; |
| |
| IVariableBinding[] reads= getReads(selectedNodes, maxVariableId); |
| for (int i= 0; i < reads.length; i++) { |
| IVariableBinding read= reads[i]; |
| if (!read.isField()) { |
| ASTNode readDecl= getRootNode().findDeclaringNode(read); |
| if (readDecl instanceof VariableDeclaration) { |
| result.add(readDecl); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * List of VariableDeclarationFragments which are accessed after <code>startNode</code>. |
| * |
| * @param startNode The node after to inspect |
| * @param maxVariableId The maximum number of variable declarations |
| * @return List of VariableDeclarationFragments which can't be moved to the new block |
| */ |
| protected List getVariableDeclarationsAccessedAfter(ASTNode startNode, int maxVariableId) { |
| |
| List statements; |
| if (startNode.getLocationInParent() == SwitchStatement.STATEMENTS_PROPERTY) { |
| SwitchStatement block= (SwitchStatement)ASTNodes.getParent(startNode, SwitchStatement.class); |
| statements= block.statements(); |
| } else { |
| Block block= (Block)ASTNodes.getParent(startNode, Block.class); |
| statements= block.statements(); |
| } |
| List bodyAfterSelection= statements.subList(statements.indexOf(startNode) + 1, statements.size()); |
| |
| List result= new ArrayList(); |
| if (!bodyAfterSelection.isEmpty()) { |
| |
| IVariableBinding[] accesses= getAccesses((ASTNode[]) bodyAfterSelection.toArray(new ASTNode[bodyAfterSelection.size()]), maxVariableId); |
| |
| for (int i= 0; i < accesses.length; i++) { |
| IVariableBinding curVar= accesses[i]; |
| if (!curVar.isField()) { |
| ASTNode readDecl= ASTNodes.findDeclaration(curVar, getRootNode()); |
| if (readDecl instanceof VariableDeclarationFragment) { |
| result.add(readDecl); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| /** |
| * @param region The region to inspect |
| * @param maxVariableId Max number of variables in region |
| * @return All variables with read access in region |
| */ |
| private IVariableBinding[] getReads(ASTNode[] region, int maxVariableId) { |
| FlowContext flowContext= new FlowContext(0, maxVariableId); |
| flowContext.setConsiderAccessMode(true); |
| flowContext.setComputeMode(FlowContext.ARGUMENTS); |
| FlowInfo argInfo= new InOutFlowAnalyzer(flowContext).perform(region); |
| IVariableBinding[] reads= argInfo.get(flowContext, FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.UNKNOWN); |
| return reads; |
| } |
| |
| /** |
| * @param region The region to inspect |
| * @param maxVariableId Max number of variables in region |
| * @return All variables with read or write access in region |
| */ |
| private IVariableBinding[] getAccesses(ASTNode[] region, int maxVariableId) { |
| FlowContext flowContext= new FlowContext(0, maxVariableId); |
| flowContext.setConsiderAccessMode(true); |
| flowContext.setComputeMode(FlowContext.ARGUMENTS); |
| FlowInfo argInfo= new InOutFlowAnalyzer(flowContext).perform(region); |
| IVariableBinding[] varsAccessedAfter= argInfo.get(flowContext, FlowInfo.READ | FlowInfo.READ_POTENTIAL | FlowInfo.WRITE | FlowInfo.WRITE_POTENTIAL | FlowInfo.UNKNOWN); |
| return varsAccessedAfter; |
| } |
| |
| /** |
| * Moves the nodes in toMove to <code>block</block> except the VariableDeclarationFragments |
| * in <code>accessedAfter</code>. The initializers (if any) of variable declarations |
| * in <code>accessedAfter</code> are moved to the block if the variable declaration is |
| * part of <code>toMove</code>. VariableDeclarations in <code>accessedInside</code> are |
| * made final unless they are moved to <code>block</code>. |
| * |
| * i.e. (if <code>i</code> is element of <code>accessedAfter</code>): |
| * <code>int i= 10;</code> ---> <code>int i;</code> and <code>{i= 10;}</code> |
| * |
| * Declarations with more then one fragments are splited if required. i.e.: |
| * <code>int i,j,k;</code> ---> <code>int i,j; final int k;</code> |
| * |
| * @param toMove Nodes to be moved to block |
| * @param statements ListRewrite to move to. |
| * @param accessedAfter VariableDeclarationFragments which can not be moved to block |
| * @param accessedInside VariableDeclaration which can be made final |
| * @param rewrite The rewrite to use. |
| */ |
| private final void moveToBlock(Statement[] toMove, ListRewrite statements, final List/*<VariableDeclarationFragment>*/ accessedAfter, final List/*<VariableDeclaration>*/ accessedInside, final ASTRewrite rewrite) { |
| |
| for (int i= 0; i < toMove.length; i++) { |
| ASTNode node= toMove[i]; |
| if (node instanceof VariableDeclarationStatement) { |
| VariableDeclarationStatement statement= (VariableDeclarationStatement)node; |
| final ListRewrite blockRewrite= getListRewrite(statement, rewrite); |
| |
| splitVariableDeclarationStatement(statement, createSplitSelectedOperator(accessedAfter, accessedInside, rewrite, statements, blockRewrite), rewrite); |
| |
| for (Iterator iter= statement.fragments().iterator(); iter.hasNext();) { |
| accessedInside.remove(iter.next()); |
| } |
| } else { |
| insertNodeAtEnd(rewrite, statements, node); |
| } |
| } |
| |
| while (!accessedInside.isEmpty()) { |
| VariableDeclaration variableDeclaration= (VariableDeclaration)accessedInside.get(0); |
| if (variableDeclaration instanceof SingleVariableDeclaration) { |
| if (ASTNodes.findModifierNode(Modifier.FINAL, ASTNodes.getModifiers(variableDeclaration)) == null) { |
| ModifierRewrite.create(rewrite, variableDeclaration).setModifiers(Modifier.FINAL, Modifier.NONE, null); |
| } |
| accessedInside.remove(0); |
| } else if (variableDeclaration.getParent() instanceof VariableDeclarationStatement) { |
| VariableDeclarationStatement statement= (VariableDeclarationStatement)variableDeclaration.getParent(); |
| final ListRewrite blockRewrite= getListRewrite(statement, rewrite); |
| |
| splitVariableDeclarationStatement(statement, createSplitUnselectedOperator(accessedInside, rewrite, blockRewrite), rewrite); |
| |
| for (Iterator iter= statement.fragments().iterator(); iter.hasNext();) { |
| VariableDeclarationFragment fragment= (VariableDeclarationFragment)iter.next(); |
| accessedInside.remove(fragment); |
| } |
| } else if (variableDeclaration.getParent() instanceof VariableDeclarationExpression) { |
| VariableDeclarationExpression expression= (VariableDeclarationExpression)variableDeclaration.getParent(); |
| |
| VariableDeclarationRewrite.rewriteModifiers(expression, Modifier.FINAL, 0, rewrite, null); |
| |
| for (Iterator iter= expression.fragments().iterator(); iter.hasNext();) { |
| VariableDeclarationFragment fragment= (VariableDeclarationFragment)iter.next(); |
| accessedInside.remove(fragment); |
| } |
| } |
| } |
| } |
| |
| private void insertNodeAtEnd(final ASTRewrite rewrite, final ListRewrite statements, ASTNode node) { |
| statements.insertLast(rewrite.createMoveTarget(node), null); |
| } |
| |
| protected ISplitOperation createSplitUnselectedOperator(List accessedInside, ASTRewrite rewrite, ListRewrite blockRewrite) { |
| return new SplitUnselectedOperator(accessedInside, blockRewrite, rewrite); |
| } |
| |
| protected ISplitOperation createSplitSelectedOperator(List accessedAfter, List accessedInside, ASTRewrite rewrite, ListRewrite statements, ListRewrite blockRewrite) { |
| return new SplitSelectedOperator(accessedInside, accessedAfter, blockRewrite, rewrite, statements); |
| } |
| |
| /** |
| * Split the fragments in <code>statement</code> to multiple VariableDeclarationStatements whenever |
| * <code>splitOperator.needsSplit</code> returns <code>true</code>. |
| * i.e.: |
| * int i, j; ---> int i; int j; (if splitOperator.needsSplit(i, j) == true) |
| * |
| * @param statement The VariableDeclarationStatement to split |
| * @param splitOperator The operator to use to split |
| * @param rewrite The rewriter to use to generate new VariableDeclarationStatements. |
| */ |
| private void splitVariableDeclarationStatement(VariableDeclarationStatement statement, ISplitOperation splitOperator, ASTRewrite rewrite) { |
| |
| List fragments= statement.fragments(); |
| Iterator iter= fragments.iterator(); |
| VariableDeclarationFragment lastFragment= (VariableDeclarationFragment)iter.next(); |
| VariableDeclarationStatement lastStatement= statement; |
| |
| splitOperator.initializeStatement(lastStatement, lastFragment); |
| |
| ListRewrite fragmentsRewrite= null; |
| while (iter.hasNext()) { |
| VariableDeclarationFragment currentFragment= (VariableDeclarationFragment)iter.next(); |
| |
| if (splitOperator.needsSplit(lastFragment, currentFragment)) { |
| |
| VariableDeclarationStatement newStatement= getAst().newVariableDeclarationStatement((VariableDeclarationFragment)rewrite.createMoveTarget(currentFragment)); |
| |
| ListRewrite modifierRewrite= rewrite.getListRewrite(newStatement, VariableDeclarationStatement.MODIFIERS2_PROPERTY); |
| for (Iterator iterator= statement.modifiers().iterator(); iterator.hasNext();) { |
| modifierRewrite.insertLast(rewrite.createCopyTarget((ASTNode)iterator.next()), null); |
| } |
| |
| newStatement.setType((Type)rewrite.createCopyTarget(statement.getType())); |
| |
| splitOperator.initializeStatement(newStatement, currentFragment); |
| |
| fragmentsRewrite= rewrite.getListRewrite(newStatement, VariableDeclarationStatement.FRAGMENTS_PROPERTY); |
| |
| lastStatement= newStatement; |
| } else if (fragmentsRewrite != null) { |
| ASTNode fragment0= rewrite.createMoveTarget(currentFragment); |
| fragmentsRewrite.insertLast(fragment0, null); |
| } |
| lastFragment= currentFragment; |
| } |
| } |
| |
| /** |
| * Make statement final |
| * @param statement |
| * @param rewrite |
| */ |
| protected static void makeFinal(VariableDeclarationStatement statement, ASTRewrite rewrite) { |
| VariableDeclaration fragment= (VariableDeclaration)statement.fragments().get(0); |
| if (ASTNodes.findModifierNode(Modifier.FINAL, ASTNodes.getModifiers(fragment)) == null) { |
| ModifierRewrite.create(rewrite, statement).setModifiers(Modifier.FINAL, Modifier.NONE, null); |
| } |
| } |
| |
| private void qualifyThisExpressions(ASTNode node, final ASTRewrite rewrite, final ImportRewrite importRewrite) { |
| node.accept(new GenericVisitor() { |
| /** |
| * {@inheritDoc} |
| */ |
| public boolean visit(ThisExpression thisExpr) { |
| if (thisExpr.getQualifier() == null) { |
| ITypeBinding typeBinding= thisExpr.resolveTypeBinding(); |
| if (typeBinding != null) { |
| IJavaElement javaElement= typeBinding.getJavaElement(); |
| if (javaElement instanceof IType) { |
| String typeName= ((IType)javaElement).getElementName(); |
| SimpleName simpleName= thisExpr.getAST().newSimpleName(typeName); |
| rewrite.set(thisExpr, ThisExpression.QUALIFIER_PROPERTY, simpleName, null); |
| } |
| } |
| } |
| return super.visit(thisExpr); |
| } |
| }); |
| } |
| |
| /** |
| * Split off initializer in <code>fragment</code> (if any) and add it as a new expression at the end of <code>statements</code>. |
| * @param statements The home of the new expression. |
| * @param fragment The fragment to split. |
| * @param rewrite The rewrite to use. |
| */ |
| protected static void splitOffInitializer(ListRewrite statements, VariableDeclarationFragment fragment, ASTRewrite rewrite) { |
| Expression initializer= fragment.getInitializer(); |
| if (initializer != null) { |
| AST ast= statements.getASTRewrite().getAST(); |
| Assignment assignment= ast.newAssignment(); |
| assignment.setLeftHandSide((Expression)rewrite.createCopyTarget(fragment.getName())); |
| assignment.setRightHandSide((Expression)rewrite.createMoveTarget(initializer)); |
| statements.insertLast(ast.newExpressionStatement(assignment), null); |
| } |
| } |
| |
| /** |
| * Get a list rewrite for statement sequence node is element |
| * @param node |
| * @param rewrite |
| * @return The list rewrite |
| */ |
| private ListRewrite getListRewrite(ASTNode node, ASTRewrite rewrite) { |
| if (node.getLocationInParent() == SwitchStatement.STATEMENTS_PROPERTY) { |
| ASTNode block= ASTNodes.getParent(node, SwitchStatement.class); |
| return rewrite.getListRewrite(block, SwitchStatement.STATEMENTS_PROPERTY); |
| } else { |
| ASTNode block= ASTNodes.getParent(node, Block.class); |
| return rewrite.getListRewrite(block, Block.STATEMENTS_PROPERTY); |
| } |
| } |
| |
| protected final AST getAst() { |
| return getRootNode().getAST(); |
| } |
| |
| protected final Statement[] getSelectedStatements() { |
| return fSelectedStatements; |
| } |
| |
| private CompilationUnit getRootNode() { |
| if (fSelectedStatements.length > 0) |
| return (CompilationUnit)fSelectedStatements[0].getRoot(); |
| return fRootNode; |
| } |
| |
| } |