/*******************************************************************************
* Copyright (c) 2005, 2011 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.che.ide.ext.java.jdt.internal.corext.fix;
import org.eclipse.che.ide.ext.java.jdt.core.dom.AST;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTNode;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ASTVisitor;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Assignment;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Block;
import org.eclipse.che.ide.ext.java.jdt.core.dom.CompilationUnit;
import org.eclipse.che.ide.ext.java.jdt.core.dom.EnhancedForStatement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Expression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.FieldAccess;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ForStatement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.IBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.IMethodBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ITypeBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.IVariableBinding;
import org.eclipse.che.ide.ext.java.jdt.core.dom.MethodInvocation;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Modifier;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Name;
import org.eclipse.che.ide.ext.java.jdt.core.dom.NullLiteral;
import org.eclipse.che.ide.ext.java.jdt.core.dom.SimpleName;
import org.eclipse.che.ide.ext.java.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.che.ide.ext.java.jdt.core.dom.Statement;
import org.eclipse.che.ide.ext.java.jdt.core.dom.ThisExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.che.ide.ext.java.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.che.ide.ext.java.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.ASTResolving;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.codemanipulation.StubUtility;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.dom.ModifierRewrite;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.refactoring.structure.ImportRemover;
import org.eclipse.che.ide.ext.java.jdt.internal.corext.refactoring.util.TightSourceRangeComputer;
import org.eclipse.che.ide.ext.java.jdt.text.edits.TextEditGroup;
import org.eclipse.che.ide.runtime.CoreException;
import org.eclipse.che.ide.runtime.IStatus;
import org.eclipse.che.ide.runtime.Status;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* Operation to convert for loops over iterables to enhanced for loops.
*
* @since 3.1
*/
public final class ConvertIterableLoopOperation extends ConvertLoopOperation {
private static final Status SEMANTIC_CHANGE_WARNING_STATUS = new Status(IStatus.WARNING, "",
FixMessages.INSTANCE
.ConvertIterableLoopOperation_semanticChangeWarning());
/**
* 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;
}
final ITypeBinding[] types = binding.getInterfaces();
for (int index = 0; index < types.length; index++) {
final ITypeBinding result = getSuperType(types[index], 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<Expression> fOccurrences = new ArrayList<Expression>(2);
private EnhancedForStatement fEnhancedForLoop;
private 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;
}
@Override
public String getIntroducedVariableName() {
if (fElement != null) {
return fElement.getName();
} else {
return getVariableNameProposals()[0];
}
}
private String[] getVariableNameProposals() {
String[] variableNames = getUsedVariableNames();
String[] elementSuggestions = StubUtility.getLocalNameSuggestions(FOR_LOOP_ELEMENT_IDENTIFIER, 0, variableNames);
final ITypeBinding binding = fIterator.getType();
if (binding != null && binding.isParameterizedType()) {
String type = binding.getTypeArguments()[0].getName();
String[] typeSuggestions = StubUtility.getLocalNameSuggestions(type, 0, variableNames);
String[] result = new String[elementSuggestions.length + typeSuggestions.length];
System.arraycopy(typeSuggestions, 0, result, 0, typeSuggestions.length);
System.arraycopy(elementSuggestions, 0, result, typeSuggestions.length, elementSuggestions.length);
return result;
} else {
return elementSuggestions;
}
}
// private IJavaProject getJavaProject() {
// return getRoot().getJavaElement().getJavaProject();
// }
private CompilationUnit getRoot() {
return (CompilationUnit)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 MethodInvocation)
return (MethodInvocation)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) {
if (iterator != null) {
final ITypeBinding[] bindings = iterator.getTypeArguments();
if (bindings.length > 0) {
ITypeBinding arg = bindings[0];
if (arg.isWildcardType()) {
arg = ASTResolving.normalizeWildcardType(arg, true, getRoot().getAST());
}
return arg;
}
}
return getRoot().getAST().resolveWellKnownType("java.lang.Object"); //$NON-NLS-1$
}
/** {@inheritDoc} */
@Override
public void rewriteAST(CompilationUnitRewrite cuRewrite) throws CoreException {
final TextEditGroup group =
createTextEditGroup(FixMessages.INSTANCE.Java50Fix_ConvertToEnhancedForLoop_description(), cuRewrite);
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);
astRewrite.replace(getForStatement(), statement, group);
}
@Override
protected Statement convert(CompilationUnitRewrite cuRewrite, final TextEditGroup group) 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<Expression> iterator = fOccurrences.iterator(); iterator.hasNext(); ) {
final Statement parent = ASTResolving
.findParentStatement(iterator.next()); //TODO//(Statement)ASTNodes.getParent(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;
}
@Override
public final boolean visit(final MethodInvocation node) {
final IMethodBinding 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);
}
@Override
public final boolean visit(final SimpleName node) {
if (fElement != null) {
final IBinding binding = node.resolveBinding();
if (binding != null && binding.equals(fElement)) {
final Statement parent =
ASTResolving.findParentStatement(node); //TODO// (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));
}
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));
for (Iterator<Expression> iterator = getForStatement().initializers().iterator(); iterator.hasNext(); ) {
ASTNode node = iterator.next();
if (node instanceof VariableDeclarationExpression) {
VariableDeclarationExpression variableDeclarationExpression = (VariableDeclarationExpression)node;
remover.registerRemovedNode(variableDeclarationExpression.getType());
} else {
remover.registerRemovedNode(node);
}
}
for (Iterator<Expression> iterator = getForStatement().updaters().iterator(); iterator.hasNext(); ) {
ASTNode node = iterator.next();
remover.registerRemovedNode(node);
}
return fEnhancedForLoop;
}
/**
* Is this proposal applicable?
*
* @return A status with severity <code>IStatus.Error</code> if not
* applicable
*/
@Override
public final IStatus satisfiesPreconditions() {
IStatus resultStatus = Status.OK_STATUS;
if (true /*JavaModelUtil.is50OrHigher()*/) {
resultStatus = checkExpressionCondition();
if (resultStatus.getSeverity() == IStatus.ERROR)
return resultStatus;
List<Expression> updateExpressions =
(List<Expression>)getForStatement().getStructuralProperty(ForStatement.UPDATERS_PROPERTY);
if (updateExpressions.size() == 1) {
resultStatus =
new Status(IStatus.WARNING, "",
FixMessages.INSTANCE.ConvertIterableLoopOperation_RemoveUpdateExpression_Warning(updateExpressions
.get(0)
.toString()));
} else if (updateExpressions.size() > 1) {
resultStatus =
new Status(IStatus.WARNING, "",
FixMessages.INSTANCE.ConvertIterableLoopOperation_RemoveUpdateExpressions_Warning());
}
for (final Iterator<Expression> outer = getForStatement().initializers().iterator(); outer.hasNext(); ) {
final Expression initializer = outer.next();
if (initializer instanceof VariableDeclarationExpression) {
final VariableDeclarationExpression declaration = (VariableDeclarationExpression)initializer;
List<VariableDeclarationFragment> fragments = declaration.fragments();
if (fragments.size() != 1) {
return new Status(IStatus.ERROR, "", ""); //$NON-NLS-1$
} else {
final VariableDeclarationFragment fragment = fragments.get(0);
fragment.accept(new ASTVisitor() {
@Override
public final boolean visit(final MethodInvocation node) {
final IMethodBinding 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 MethodInvocation) {
final MethodInvocation invocation = (MethodInvocation)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;
}
@Override
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() {
@Override
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 (fElement != null && left instanceof SimpleName) {
IBinding binding = ((SimpleName)left).resolveBinding();
if (fElement.equals(binding))
fMakeFinal = false;
}
if (right instanceof MethodInvocation) {
final MethodInvocation invocation = (MethodInvocation)right;
final IMethodBinding 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);
}
}
return true;
}
/**
* {@inheritDoc}
*/
@Override
public boolean visit(MethodInvocation invocation) {
final IMethodBinding 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 true;
}
@Override
public final boolean visit(final VariableDeclarationFragment node) {
return visit(node.getName(), node.getInitializer());
}
});
if (otherInvocationThenNext[0])
return ERROR_STATUS;
if (nextInvocationCount[0] > 1)
return ERROR_STATUS;
if (fElement != null) {
statement.accept(new ASTVisitor() {
@Override
public final boolean visit(final VariableDeclarationFragment node) {
if (node.getInitializer() instanceof NullLiteral) {
SimpleName name = node.getName();
if (iterable.equals(name.resolveTypeBinding()) && fElement.equals(name.resolveBinding())) {
fOccurrences.add(name);
}
}
return true;
}
});
}
}
final ASTNode root = getForStatement().getRoot();
if (root != null) {
root.accept(new ASTVisitor() {
@Override
public final boolean visit(final ForStatement node) {
return false;
}
@Override
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 ERROR_STATUS;
}
}
private IStatus checkExpressionCondition() {
Expression expression = getForStatement().getExpression();
if (!(expression instanceof MethodInvocation))
return SEMANTIC_CHANGE_WARNING_STATUS;
MethodInvocation invoc = (MethodInvocation)expression;
IMethodBinding methodBinding = invoc.resolveMethodBinding();
if (methodBinding == null)
return ERROR_STATUS;
ITypeBinding declaringClass = methodBinding.getDeclaringClass();
if (declaringClass == null)
return ERROR_STATUS;
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 SEMANTIC_CHANGE_WARNING_STATUS;
} else if (qualifiedName.startsWith("java.util.Iterator")) { //$NON-NLS-1$
if (!methodName.equals("hasNext")) //$NON-NLS-1$
return SEMANTIC_CHANGE_WARNING_STATUS;
return checkIteratorCondition();
} else {
return SEMANTIC_CHANGE_WARNING_STATUS;
}
return Status.OK_STATUS;
}
private IStatus checkIteratorCondition() {
List<Expression> initializers = getForStatement().initializers();
if (initializers.size() != 1)
return SEMANTIC_CHANGE_WARNING_STATUS;
Expression expression = initializers.get(0);
if (!(expression instanceof VariableDeclarationExpression))
return SEMANTIC_CHANGE_WARNING_STATUS;
VariableDeclarationExpression declaration = (VariableDeclarationExpression)expression;
List<VariableDeclarationFragment> variableDeclarationFragments = declaration.fragments();
if (variableDeclarationFragments.size() != 1)
return SEMANTIC_CHANGE_WARNING_STATUS;
VariableDeclarationFragment declarationFragment = variableDeclarationFragments.get(0);
Expression initializer = declarationFragment.getInitializer();
if (!(initializer instanceof MethodInvocation))
return SEMANTIC_CHANGE_WARNING_STATUS;
MethodInvocation methodInvocation = (MethodInvocation)initializer;
String methodName = methodInvocation.getName().getIdentifier();
if (!"iterator".equals(methodName)) //$NON-NLS-1$
return SEMANTIC_CHANGE_WARNING_STATUS;
return Status.OK_STATUS;
}
}