/******************************************************************************* * Copyright (c) 2005, 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.fix; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.text.edits.TextEditGroup; import org.eclipse.wst.jsdt.core.IJavaScriptProject; import org.eclipse.wst.jsdt.core.dom.AST; import org.eclipse.wst.jsdt.core.dom.ASTNode; import org.eclipse.wst.jsdt.core.dom.ASTVisitor; import org.eclipse.wst.jsdt.core.dom.Assignment; import org.eclipse.wst.jsdt.core.dom.Block; import org.eclipse.wst.jsdt.core.dom.EnhancedForStatement; import org.eclipse.wst.jsdt.core.dom.Expression; import org.eclipse.wst.jsdt.core.dom.FieldAccess; import org.eclipse.wst.jsdt.core.dom.ForStatement; import org.eclipse.wst.jsdt.core.dom.FunctionInvocation; import org.eclipse.wst.jsdt.core.dom.IBinding; import org.eclipse.wst.jsdt.core.dom.IFunctionBinding; import org.eclipse.wst.jsdt.core.dom.ITypeBinding; import org.eclipse.wst.jsdt.core.dom.IVariableBinding; import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit; 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.SimpleName; import org.eclipse.wst.jsdt.core.dom.SingleVariableDeclaration; import org.eclipse.wst.jsdt.core.dom.Statement; import org.eclipse.wst.jsdt.core.dom.ThisExpression; import org.eclipse.wst.jsdt.core.dom.UndefinedLiteral; import org.eclipse.wst.jsdt.core.dom.VariableDeclarationExpression; 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.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.ASTNodes; import org.eclipse.wst.jsdt.internal.corext.dom.ModifierRewrite; import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite; import org.eclipse.wst.jsdt.internal.corext.refactoring.structure.ImportRemover; import org.eclipse.wst.jsdt.internal.corext.refactoring.util.TightSourceRangeComputer; import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil; import org.eclipse.wst.jsdt.internal.corext.util.Messages; import org.eclipse.wst.jsdt.internal.ui.dialogs.StatusInfo; /** * Operation to convert for loops over iterables to enhanced for loops. * * */ public final class ConvertIterableLoopOperation extends ConvertLoopOperation { /** * Returns the supertype of the given type with the qualified name. * * @param binding * the binding of the type * @param name * the qualified name of the supertype * @return the supertype, or <code>null</code> */ private static ITypeBinding getSuperType(final ITypeBinding binding, final String name) { if (binding.isArray() || binding.isPrimitive()) return null; if (binding.getQualifiedName().startsWith(name)) return binding; final ITypeBinding type= binding.getSuperclass(); if (type != null) { final ITypeBinding result= getSuperType(type, name); if (result != null) return result; } return null; } /** Has the element variable been assigned outside the for statement? */ private boolean fAssigned= false; /** The binding of the element variable */ private IBinding fElement= null; /** The node of the iterable object used in the expression */ private Expression fExpression= null; /** The binding of the iterable object */ private IBinding fIterable= null; /** Is the iterator method invoked on <code>this</code>? */ private boolean fThis= false; /** The binding of the iterator variable */ private IVariableBinding fIterator= null; /** The nodes of the element variable occurrences */ private final List fOccurrences= new ArrayList(2); private EnhancedForStatement fEnhancedForLoop; private final boolean fMakeFinal; public ConvertIterableLoopOperation(ForStatement statement) { this(statement, new String[0], false); } public ConvertIterableLoopOperation(ForStatement statement, String[] usedNames, boolean makeFinal) { super(statement, usedNames); fMakeFinal= makeFinal; } public String getIntroducedVariableName() { if (fElement != null) { return fElement.getName(); } else { return getVariableNameProposals()[0]; } } private String[] getVariableNameProposals() { String[] variableNames= getUsedVariableNames(); String[] elementSuggestions= StubUtility.getLocalNameSuggestions(getJavaProject(), FOR_LOOP_ELEMENT_IDENTIFIER, 0, variableNames); return elementSuggestions; } private IJavaScriptProject getJavaProject() { return getRoot().getJavaElement().getJavaScriptProject(); } private JavaScriptUnit getRoot() { return (JavaScriptUnit)getForStatement().getRoot(); } /** * Returns the expression for the enhanced for statement. * * @param rewrite * the AST rewrite to use * @return the expression node, or <code>null</code> */ private Expression getExpression(final ASTRewrite rewrite) { if (fThis) return rewrite.getAST().newThisExpression(); if (fExpression instanceof FunctionInvocation) return (FunctionInvocation)rewrite.createMoveTarget(fExpression); return (Expression)ASTNode.copySubtree(rewrite.getAST(), fExpression); } /** * Returns the iterable type from the iterator type binding. * * @param iterator * the iterator type binding, or <code>null</code> * @return the iterable type */ private ITypeBinding getIterableType(final ITypeBinding iterator) { return getRoot().getAST().resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$ } /* (non-Javadoc) * @see org.eclipse.wst.jsdt.internal.corext.fix.LinkedFix.ILinkedFixRewriteOperation#rewriteAST(org.eclipse.wst.jsdt.internal.corext.refactoring.structure.CompilationUnitRewrite, java.util.List, java.util.List) */ public void rewriteAST(CompilationUnitRewrite cuRewrite, List textEditGroups, final LinkedProposalModel positionGroups) throws CoreException { final TextEditGroup group= createTextEditGroup(FixMessages.Java50Fix_ConvertToEnhancedForLoop_description); textEditGroups.add(group); final ASTRewrite astRewrite= cuRewrite.getASTRewrite(); TightSourceRangeComputer rangeComputer; if (astRewrite.getExtendedSourceRangeComputer() instanceof TightSourceRangeComputer) { rangeComputer= (TightSourceRangeComputer)astRewrite.getExtendedSourceRangeComputer(); } else { rangeComputer= new TightSourceRangeComputer(); } rangeComputer.addTightSourceNode(getForStatement()); astRewrite.setTargetSourceRangeComputer(rangeComputer); Statement statement= convert(cuRewrite, group, positionGroups); astRewrite.replace(getForStatement(), statement, group); } protected Statement convert(CompilationUnitRewrite cuRewrite, final TextEditGroup group, final LinkedProposalModel positionGroups) throws CoreException { final AST ast= cuRewrite.getAST(); final ASTRewrite astRewrite= cuRewrite.getASTRewrite(); final ImportRewrite importRewrite= cuRewrite.getImportRewrite(); final ImportRemover remover= cuRewrite.getImportRemover(); fEnhancedForLoop= ast.newEnhancedForStatement(); String[] names= getVariableNameProposals(); String name; if (fElement != null) { name= fElement.getName(); } else { name= names[0]; } final LinkedProposalPositionGroup pg= positionGroups.getPositionGroup(name, true); if (fElement != null) pg.addProposal(name, null, 10); for (int i= 0; i < names.length; i++) { pg.addProposal(names[i], null, 10); } final Statement body= getForStatement().getBody(); if (body != null) { final ListRewrite list; if (body instanceof Block) { list= astRewrite.getListRewrite(body, Block.STATEMENTS_PROPERTY); for (final Iterator iterator= fOccurrences.iterator(); iterator.hasNext();) { final Statement parent= (Statement)ASTNodes.getParent((ASTNode)iterator.next(), Statement.class); if (parent != null && list.getRewrittenList().contains(parent)) { list.remove(parent, null); remover.registerRemovedNode(parent); } } } else { list= null; } final String text= name; body.accept(new ASTVisitor() { private boolean replace(final Expression expression) { final SimpleName node= ast.newSimpleName(text); astRewrite.replace(expression, node, group); remover.registerRemovedNode(expression); pg.addPosition(astRewrite.track(node), false); return false; } public final boolean visit(final FunctionInvocation node) { final IFunctionBinding binding= node.resolveMethodBinding(); if (binding != null && (binding.getName().equals("next") || binding.getName().equals("nextElement"))) { //$NON-NLS-1$ //$NON-NLS-2$ final Expression expression= node.getExpression(); if (expression instanceof Name) { final IBinding result= ((Name)expression).resolveBinding(); if (result != null && result.equals(fIterator)) return replace(node); } else if (expression instanceof FieldAccess) { final IBinding result= ((FieldAccess)expression).resolveFieldBinding(); if (result != null && result.equals(fIterator)) return replace(node); } } return super.visit(node); } public final boolean visit(final SimpleName node) { if (fElement != null) { final IBinding binding= node.resolveBinding(); if (binding != null && binding.equals(fElement)) { final Statement parent= (Statement)ASTNodes.getParent(node, Statement.class); if (parent != null && (list == null || list.getRewrittenList().contains(parent))) pg.addPosition(astRewrite.track(node), false); } } return false; } }); fEnhancedForLoop.setBody(getBody(cuRewrite, group, positionGroups)); } final SingleVariableDeclaration declaration= ast.newSingleVariableDeclaration(); final SimpleName simple= ast.newSimpleName(name); pg.addPosition(astRewrite.track(simple), true); declaration.setName(simple); final ITypeBinding iterable= getIterableType(fIterator.getType()); declaration.setType(importType(iterable, getForStatement(), importRewrite, getRoot())); if (fMakeFinal) { ModifierRewrite.create(astRewrite, declaration).setModifiers(Modifier.FINAL, 0, group); } remover.registerAddedImport(iterable.getQualifiedName()); fEnhancedForLoop.setParameter(declaration); fEnhancedForLoop.setExpression(getExpression(astRewrite)); remover.registerRemovedNode(getForStatement().getExpression()); for (Iterator iterator= getForStatement().initializers().iterator(); iterator.hasNext();) { ASTNode node= (ASTNode)iterator.next(); remover.registerRemovedNode(node); } for (Iterator iterator= getForStatement().updaters().iterator(); iterator.hasNext();) { ASTNode node= (ASTNode)iterator.next(); remover.registerRemovedNode(node); } return fEnhancedForLoop; } /** * Is this proposal applicable? * * @return A status with severity <code>IStatus.Error</code> if not * applicable */ public final IStatus satisfiesPreconditions() { IStatus resultStatus= StatusInfo.OK_STATUS; if (JavaModelUtil.is50OrHigher(getJavaProject())) { resultStatus= checkExpressionCondition(); if (resultStatus.getSeverity() == IStatus.ERROR) return resultStatus; List updateExpressions= (List)getForStatement().getStructuralProperty(ForStatement.UPDATERS_PROPERTY); if (updateExpressions.size() == 1) { resultStatus= new StatusInfo(IStatus.WARNING, Messages.format(FixMessages.ConvertIterableLoopOperation_RemoveUpdateExpression_Warning, ((Expression)updateExpressions.get(0)).toString())); } else if (updateExpressions.size() > 1) { resultStatus= new StatusInfo(IStatus.WARNING, FixMessages.ConvertIterableLoopOperation_RemoveUpdateExpressions_Warning); } for (final Iterator outer= getForStatement().initializers().iterator(); outer.hasNext();) { final Expression initializer= (Expression)outer.next(); if (initializer instanceof VariableDeclarationExpression) { final VariableDeclarationExpression declaration= (VariableDeclarationExpression)initializer; List fragments= declaration.fragments(); if (fragments.size() != 1) { return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$ } else { final VariableDeclarationFragment fragment= (VariableDeclarationFragment)fragments.get(0); fragment.accept(new ASTVisitor() { public final boolean visit(final FunctionInvocation node) { final IFunctionBinding binding= node.resolveMethodBinding(); if (binding != null) { final ITypeBinding type= binding.getReturnType(); if (type != null) { final String qualified= type.getQualifiedName(); if (qualified.startsWith("java.util.Enumeration<") || qualified.startsWith("java.util.Iterator<")) { //$NON-NLS-1$ //$NON-NLS-2$ final Expression qualifier= node.getExpression(); if (qualifier != null) { final ITypeBinding resolved= qualifier.resolveTypeBinding(); if (resolved != null) { final ITypeBinding iterable= getSuperType(resolved, "java.lang.Iterable"); //$NON-NLS-1$ if (iterable != null) { fExpression= qualifier; if (qualifier instanceof Name) { final Name name= (Name)qualifier; fIterable= name.resolveBinding(); } else if (qualifier instanceof FunctionInvocation) { final FunctionInvocation invocation= (FunctionInvocation)qualifier; fIterable= invocation.resolveMethodBinding(); } else if (qualifier instanceof FieldAccess) { final FieldAccess access= (FieldAccess)qualifier; fIterable= access.resolveFieldBinding(); } else if (qualifier instanceof ThisExpression) fIterable= resolved; } } } else { final ITypeBinding declaring= binding.getDeclaringClass(); if (declaring != null) { final ITypeBinding superBinding= getSuperType(declaring, "java.lang.Iterable"); //$NON-NLS-1$ if (superBinding != null) { fIterable= superBinding; fThis= true; } } } } } } return true; } public final boolean visit(final VariableDeclarationFragment node) { final IVariableBinding binding= node.resolveBinding(); if (binding != null) { final ITypeBinding type= binding.getType(); if (type != null) { ITypeBinding iterator= getSuperType(type, "java.util.Iterator"); //$NON-NLS-1$ if (iterator != null) fIterator= binding; else { iterator= getSuperType(type, "java.util.Enumeration"); //$NON-NLS-1$ if (iterator != null) fIterator= binding; } } } return true; } }); } } } final Statement statement= getForStatement().getBody(); final boolean[] otherInvocationThenNext= new boolean[] {false}; final int[] nextInvocationCount= new int[] {0}; if (statement != null && fIterator != null) { final ITypeBinding iterable= getIterableType(fIterator.getType()); statement.accept(new ASTVisitor() { public final boolean visit(final Assignment node) { return visit(node.getLeftHandSide(), node.getRightHandSide()); } private boolean visit(final Expression node) { if (node != null) { final ITypeBinding binding= node.resolveTypeBinding(); if (binding != null && iterable.equals(binding)) { if (node instanceof Name) { final Name name= (Name)node; final IBinding result= name.resolveBinding(); if (result != null) { fOccurrences.add(node); fElement= result; return false; } } else if (node instanceof FieldAccess) { final FieldAccess access= (FieldAccess)node; final IBinding result= access.resolveFieldBinding(); if (result != null) { fOccurrences.add(node); fElement= result; return false; } } } } return true; } private boolean visit(final Expression left, final Expression right) { if (right instanceof FunctionInvocation) { // final FunctionInvocation invocation= (FunctionInvocation)right; // final IFunctionBinding binding= invocation.resolveMethodBinding(); // if (binding != null && (binding.getName().equals("next") || binding.getName().equals("nextElement"))) { //$NON-NLS-1$ //$NON-NLS-2$ // final Expression expression= invocation.getExpression(); // if (expression instanceof Name) { // final Name qualifier= (Name)expression; // final IBinding result= qualifier.resolveBinding(); // if (result != null && result.equals(fIterator)) { // nextInvocationCount[0]++; // return visit(left); // } // } else if (expression instanceof FieldAccess) { // final FieldAccess qualifier= (FieldAccess)expression; // final IBinding result= qualifier.resolveFieldBinding(); // if (result != null && result.equals(fIterator)) { // nextInvocationCount[0]++; // return visit(left); // } // } // } else { // return visit(invocation); // } } else if (right instanceof NullLiteral || right instanceof UndefinedLiteral) return visit(left); return true; } /** * {@inheritDoc} */ public boolean visit(FunctionInvocation invocation) { final IFunctionBinding binding= invocation.resolveMethodBinding(); if (binding != null) { final Expression expression= invocation.getExpression(); if (expression instanceof Name) { final Name qualifier= (Name)expression; final IBinding result= qualifier.resolveBinding(); if (result != null && result.equals(fIterator)) { // if (!binding.getName().equals("next") && !binding.getName().equals("nextElement")) { //$NON-NLS-1$ //$NON-NLS-2$ // otherInvocationThenNext[0]= true; // } else { // nextInvocationCount[0]++; // } } } else if (expression instanceof FieldAccess) { final FieldAccess qualifier= (FieldAccess)expression; final IBinding result= qualifier.resolveFieldBinding(); if (result != null && result.equals(fIterator)) { // if (!binding.getName().equals("next") && !binding.getName().equals("nextElement")) { //$NON-NLS-1$ //$NON-NLS-2$ // otherInvocationThenNext[0]= true; // } else { // nextInvocationCount[0]++; // } } } } return false; } public final boolean visit(final VariableDeclarationFragment node) { return visit(node.getName(), node.getInitializer()); } }); if (otherInvocationThenNext[0]) return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$ if (nextInvocationCount[0] > 1) return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$ } final ASTNode root= getForStatement().getRoot(); if (root != null) { root.accept(new ASTVisitor() { public final boolean visit(final ForStatement node) { return false; } public final boolean visit(final SimpleName node) { final IBinding binding= node.resolveBinding(); if (binding != null && binding.equals(fElement)) fAssigned= true; return false; } }); } } if ((fExpression != null || fThis) && fIterable != null && fIterator != null && !fAssigned) { return resultStatus; } else { return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$ } } private IStatus checkExpressionCondition() { String warningLable= FixMessages.ConvertIterableLoopOperation_semanticChangeWarning; Expression expression= getForStatement().getExpression(); if (!(expression instanceof FunctionInvocation)) return new StatusInfo(IStatus.WARNING, warningLable); FunctionInvocation invoc= (FunctionInvocation)expression; IFunctionBinding methodBinding= invoc.resolveMethodBinding(); if (methodBinding == null) return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$ ITypeBinding declaringClass= methodBinding.getDeclaringClass(); if (declaringClass == null) return new StatusInfo(IStatus.ERROR, ""); //$NON-NLS-1$ // String qualifiedName= declaringClass.getQualifiedName(); // String methodName= invoc.getName().getIdentifier(); // if (qualifiedName.startsWith("java.util.Enumeration")) { //$NON-NLS-1$ // if (!methodName.equals("hasMoreElements")) //$NON-NLS-1$ // return new StatusInfo(IStatus.WARNING, warningLable); // } else if (qualifiedName.startsWith("java.util.Iterator")) { //$NON-NLS-1$ // if (!methodName.equals("hasNext")) //$NON-NLS-1$ // return new StatusInfo(IStatus.WARNING, warningLable); // } else { // return new StatusInfo(IStatus.WARNING, warningLable); // } return StatusInfo.OK_STATUS; } }