1 /*******************************************************************************
2 * Copyright (c) 2000, 2011 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
9 * IBM Corporation - initial API and implementation
10 *******************************************************************************/
11 package org.eclipse.jdt.internal.corext.fix;
13 import java.util.List;
15 import org.eclipse.core.runtime.CoreException;
16 import org.eclipse.core.runtime.IStatus;
17 import org.eclipse.core.runtime.Status;
19 import org.eclipse.text.edits.TextEditGroup;
21 import org.eclipse.jdt.core.IJavaElement;
22 import org.eclipse.jdt.core.IJavaProject;
23 import org.eclipse.jdt.core.dom.AST;
24 import org.eclipse.jdt.core.dom.ASTNode;
25 import org.eclipse.jdt.core.dom.ArrayAccess;
26 import org.eclipse.jdt.core.dom.Assignment;
27 import org.eclipse.jdt.core.dom.CompilationUnit;
28 import org.eclipse.jdt.core.dom.EnhancedForStatement;
29 import org.eclipse.jdt.core.dom.Expression;
30 import org.eclipse.jdt.core.dom.FieldAccess;
31 import org.eclipse.jdt.core.dom.ForStatement;
32 import org.eclipse.jdt.core.dom.IBinding;
33 import org.eclipse.jdt.core.dom.ITypeBinding;
34 import org.eclipse.jdt.core.dom.IVariableBinding;
35 import org.eclipse.jdt.core.dom.InfixExpression;
36 import org.eclipse.jdt.core.dom.InfixExpression.Operator;
37 import org.eclipse.jdt.core.dom.Modifier;
38 import org.eclipse.jdt.core.dom.Name;
39 import org.eclipse.jdt.core.dom.NumberLiteral;
40 import org.eclipse.jdt.core.dom.PostfixExpression;
41 import org.eclipse.jdt.core.dom.PrefixExpression;
42 import org.eclipse.jdt.core.dom.PrimitiveType;
43 import org.eclipse.jdt.core.dom.QualifiedName;
44 import org.eclipse.jdt.core.dom.SimpleName;
45 import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
46 import org.eclipse.jdt.core.dom.Statement;
47 import org.eclipse.jdt.core.dom.ThisExpression;
48 import org.eclipse.jdt.core.dom.Type;
49 import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
50 import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
51 import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
52 import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
53 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
54 import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
56 import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility;
57 import org.eclipse.jdt.internal.corext.dom.ASTNodes;
58 import org.eclipse.jdt.internal.corext.dom.GenericVisitor;
59 import org.eclipse.jdt.internal.corext.dom.JdtASTMatcher;
60 import org.eclipse.jdt.internal.corext.dom.ModifierRewrite;
61 import org.eclipse.jdt.internal.corext.refactoring.structure.CompilationUnitRewrite;
62 import org.eclipse.jdt.internal.corext.refactoring.util.TightSourceRangeComputer;
63 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
66 public class ConvertForLoopOperation extends ConvertLoopOperation {
68 private static final String LENGTH_QUERY= "length"; //$NON-NLS-1$
69 private static final String LITERAL_0= "0"; //$NON-NLS-1$
70 private static final String LITERAL_1= "1"; //$NON-NLS-1$
71 private static final class InvalidBodyError extends Error {
72 private static final long serialVersionUID= 1L;
75 IVariableBinding fIndexBinding;
76 private IVariableBinding fLengthBinding;
77 IBinding fArrayBinding;
78 Expression fArrayAccess;
79 VariableDeclarationFragment fElementDeclaration;
82 public ConvertForLoopOperation(ForStatement forStatement) {
83 this(forStatement, new String[0], false);
86 public ConvertForLoopOperation(ForStatement forStatement, String[] usedNames, boolean makeFinal) {
87 super(forStatement, usedNames);
88 fMakeFinal= makeFinal;
92 public IStatus satisfiesPreconditions() {
93 ForStatement statement= getForStatement();
94 CompilationUnit ast= (CompilationUnit)statement.getRoot();
96 IJavaElement javaElement= ast.getJavaElement();
97 if (javaElement == null)
100 if (!JavaModelUtil.is50OrHigher(javaElement.getJavaProject()))
103 if (!validateInitializers(statement))
106 if (!validateExpression(statement))
109 if (!validateUpdaters(statement))
112 if (!validateBody(statement))
115 return Status.OK_STATUS;
122 * <li>int [result]= 0;</li>
123 * <li>int [result]= 0, [lengthBinding]= [arrayBinding].length;</li>
124 * <li>int , [result]= 0;</li>
127 private boolean validateInitializers(ForStatement statement) {
128 List<Expression> initializers= statement.initializers();
129 if (initializers.size() != 1)
132 Expression expression= initializers.get(0);
133 if (!(expression instanceof VariableDeclarationExpression))
136 VariableDeclarationExpression declaration= (VariableDeclarationExpression)expression;
137 ITypeBinding declarationBinding= declaration.resolveTypeBinding();
138 if (declarationBinding == null)
141 if (!declarationBinding.isPrimitive())
144 if (!PrimitiveType.INT.toString().equals(declarationBinding.getQualifiedName()))
147 List<VariableDeclarationFragment> fragments= declaration.fragments();
148 if (fragments.size() == 1) {
149 IVariableBinding indexBinding= getIndexBindingFromFragment(fragments.get(0));
150 if (indexBinding == null)
153 fIndexBinding= indexBinding;
155 } else if (fragments.size() == 2) {
156 IVariableBinding indexBinding= getIndexBindingFromFragment(fragments.get(0));
157 if (indexBinding == null) {
158 indexBinding= getIndexBindingFromFragment(fragments.get(1));
159 if (indexBinding == null)
162 if (!validateLengthFragment(fragments.get(0)))
165 if (!validateLengthFragment(fragments.get(1)))
169 fIndexBinding= indexBinding;
177 * [lengthBinding]= [arrayBinding].length
179 private boolean validateLengthFragment(VariableDeclarationFragment fragment) {
180 Expression initializer= fragment.getInitializer();
181 if (initializer == null)
184 if (!validateLengthQuery(initializer))
187 IVariableBinding lengthBinding= (IVariableBinding)fragment.getName().resolveBinding();
188 if (lengthBinding == null)
190 fLengthBinding= lengthBinding;
199 * <li>[result]= 0</li>
202 private IVariableBinding getIndexBindingFromFragment(VariableDeclarationFragment fragment) {
203 Expression initializer= fragment.getInitializer();
204 if (!(initializer instanceof NumberLiteral))
207 NumberLiteral number= (NumberLiteral)initializer;
208 if (!LITERAL_0.equals(number.getToken()))
211 return (IVariableBinding)fragment.getName().resolveBinding();
218 * <li>[indexBinding] < [result].length;</li>
219 * <li>[result].length > [indexBinding];</li>
220 * <li>[indexBinding] < [lengthBinding];</li>
221 * <li>[lengthBinding] > [indexBinding];</li>
224 private boolean validateExpression(ForStatement statement) {
225 Expression expression= statement.getExpression();
226 if (!(expression instanceof InfixExpression))
229 InfixExpression infix= (InfixExpression)expression;
231 Expression left= infix.getLeftOperand();
232 Expression right= infix.getRightOperand();
233 if (left instanceof SimpleName && right instanceof SimpleName) {
234 IVariableBinding lengthBinding= fLengthBinding;
235 if (lengthBinding == null)
238 IBinding leftBinding= ((SimpleName)left).resolveBinding();
239 IBinding righBinding= ((SimpleName)right).resolveBinding();
241 if (fIndexBinding.equals(leftBinding)) {
242 return lengthBinding.equals(righBinding);
243 } else if (fIndexBinding.equals(righBinding)) {
244 return lengthBinding.equals(leftBinding);
248 } else if (left instanceof SimpleName) {
249 if (!fIndexBinding.equals(((SimpleName)left).resolveBinding()))
252 if (!Operator.LESS.equals(infix.getOperator()))
255 return validateLengthQuery(right);
256 } else if (right instanceof SimpleName) {
257 if (!fIndexBinding.equals(((SimpleName)right).resolveBinding()))
260 if (!Operator.GREATER.equals(infix.getOperator()))
263 return validateLengthQuery(left);
273 * <li>[result].length</li>
276 private boolean validateLengthQuery(Expression lengthQuery) {
277 if (lengthQuery instanceof QualifiedName) {
278 QualifiedName qualifiedName= (QualifiedName)lengthQuery;
279 SimpleName name= qualifiedName.getName();
280 if (!LENGTH_QUERY.equals(name.getIdentifier()))
283 Name arrayAccess= qualifiedName.getQualifier();
284 ITypeBinding accessType= arrayAccess.resolveTypeBinding();
285 if (accessType == null)
288 if (!accessType.isArray())
291 IBinding arrayBinding= arrayAccess.resolveBinding();
292 if (arrayBinding == null)
295 fArrayBinding= arrayBinding;
296 fArrayAccess= arrayAccess;
298 } else if (lengthQuery instanceof FieldAccess) {
299 FieldAccess fieldAccess= (FieldAccess)lengthQuery;
300 SimpleName name= fieldAccess.getName();
301 if (!LENGTH_QUERY.equals(name.getIdentifier()))
304 Expression arrayAccess= fieldAccess.getExpression();
305 ITypeBinding accessType= arrayAccess.resolveTypeBinding();
306 if (accessType == null)
309 if (!accessType.isArray())
312 IBinding arrayBinding= getBinding(arrayAccess);
313 if (arrayBinding == null)
316 fArrayBinding= arrayBinding;
317 fArrayAccess= arrayAccess;
328 * <li>[indexBinding]++</li>
329 * <li>++[indexBinding]</li>
330 * <li>[indexBinding]+= 1</li>
331 * <li>[indexBinding]= [indexBinding] + 1</li>
332 * <li>[indexBinding]= 1 + [indexBinding]</li>
335 private boolean validateUpdaters(ForStatement statement) {
336 List<Expression> updaters= statement.updaters();
337 if (updaters.size() != 1)
340 Expression updater= updaters.get(0);
341 if (updater instanceof PostfixExpression) {
342 PostfixExpression postfix= (PostfixExpression)updater;
344 if (!PostfixExpression.Operator.INCREMENT.equals(postfix.getOperator()))
347 IBinding binding= getBinding(postfix.getOperand());
348 if (!fIndexBinding.equals(binding))
352 } else if (updater instanceof PrefixExpression) {
353 PrefixExpression prefix= (PrefixExpression) updater;
355 if (!PrefixExpression.Operator.INCREMENT.equals(prefix.getOperator()))
358 IBinding binding= getBinding(prefix.getOperand());
359 if (!fIndexBinding.equals(binding))
363 } else if (updater instanceof Assignment) {
364 Assignment assignment= (Assignment)updater;
365 Expression left= assignment.getLeftHandSide();
366 IBinding binding= getBinding(left);
367 if (!fIndexBinding.equals(binding))
370 if (Assignment.Operator.PLUS_ASSIGN.equals(assignment.getOperator())) {
371 return isOneLiteral(assignment.getRightHandSide());
372 } else if (Assignment.Operator.ASSIGN.equals(assignment.getOperator())) {
373 Expression right= assignment.getRightHandSide();
374 if (!(right instanceof InfixExpression))
377 InfixExpression infixExpression= (InfixExpression)right;
378 Expression leftOperand= infixExpression.getLeftOperand();
379 IBinding leftBinding= getBinding(leftOperand);
380 Expression rightOperand= infixExpression.getRightOperand();
381 IBinding rightBinding= getBinding(rightOperand);
383 if (fIndexBinding.equals(leftBinding)) {
384 return isOneLiteral(rightOperand);
385 } else if (fIndexBinding.equals(rightBinding)) {
386 return isOneLiteral(leftOperand);
393 private boolean isOneLiteral(Expression expression) {
394 if (!(expression instanceof NumberLiteral))
397 NumberLiteral literal= (NumberLiteral)expression;
398 return LITERAL_1.equals(literal.getToken());
405 * <li><code>indexBinding</code> is used for anything else then accessing
406 * an element of <code>arrayBinding</code></li>
407 * <li><code>arrayBinding</code> is assigned</li>
408 * <li>an element of <code>arrayBinding</code> is assigned</li>
409 * <li><code>lengthBinding</code> is referenced</li>
411 * within <code>body</code>
413 private boolean validateBody(ForStatement statement) {
414 Statement body= statement.getBody();
416 body.accept(new GenericVisitor() {
421 protected boolean visitNode(ASTNode node) {
422 if (node instanceof Name) {
423 Name name= (Name)node;
424 IBinding nameBinding= name.resolveBinding();
425 if (nameBinding == null)
426 throw new InvalidBodyError();
428 if (nameBinding.equals(fIndexBinding)) {
429 if (node.getLocationInParent() != ArrayAccess.INDEX_PROPERTY)
430 throw new InvalidBodyError();
432 ArrayAccess arrayAccess= (ArrayAccess)node.getParent();
433 Expression array= arrayAccess.getArray();
434 if (array instanceof QualifiedName) {
435 if (!(fArrayAccess instanceof QualifiedName))
436 throw new InvalidBodyError();
438 IBinding varBinding1= ((QualifiedName) array).getQualifier().resolveBinding();
439 if (varBinding1 == null)
440 throw new InvalidBodyError();
442 IBinding varBinding2= ((QualifiedName) fArrayAccess).getQualifier().resolveBinding();
443 if (!varBinding1.equals(varBinding2))
444 throw new InvalidBodyError();
445 } else if (array instanceof FieldAccess) {
446 Expression arrayExpression= ((FieldAccess) array).getExpression();
447 if (arrayExpression instanceof ThisExpression) {
448 if (fArrayAccess instanceof FieldAccess) {
449 Expression arrayAccessExpression= ((FieldAccess) fArrayAccess).getExpression();
450 if (!(arrayAccessExpression instanceof ThisExpression))
451 throw new InvalidBodyError();
452 } else if (fArrayAccess instanceof QualifiedName) {
453 throw new InvalidBodyError();
456 if (!(fArrayAccess instanceof FieldAccess))
457 throw new InvalidBodyError();
459 Expression arrayAccessExpression= ((FieldAccess) fArrayAccess).getExpression();
460 if (!arrayExpression.subtreeMatch(new JdtASTMatcher(), arrayAccessExpression)) {
461 throw new InvalidBodyError();
465 if (fArrayAccess instanceof QualifiedName) {
466 throw new InvalidBodyError();
468 if (fArrayAccess instanceof FieldAccess) {
469 Expression arrayAccessExpression= ((FieldAccess) fArrayAccess).getExpression();
470 if (!(arrayAccessExpression instanceof ThisExpression))
471 throw new InvalidBodyError();
475 IBinding binding= getBinding(array);
477 throw new InvalidBodyError();
479 if (!fArrayBinding.equals(binding))
480 throw new InvalidBodyError();
482 } else if (nameBinding.equals(fArrayBinding)) {
483 if (isAssigned(node))
484 throw new InvalidBodyError();
485 } else if (nameBinding.equals(fLengthBinding)) {
486 throw new InvalidBodyError();
487 } else if (fElementDeclaration != null && nameBinding.equals(fElementDeclaration.getName().resolveBinding())) {
488 if (isAssigned(node))
489 fElementDeclaration= null;
496 private boolean isAssigned(ASTNode current) {
497 while (current != null && !(current instanceof Statement)) {
498 if (current.getLocationInParent() == Assignment.LEFT_HAND_SIDE_PROPERTY)
501 if (current instanceof PrefixExpression)
504 if (current instanceof PostfixExpression)
507 current= current.getParent();
514 public boolean visit(ArrayAccess node) {
515 if (fElementDeclaration != null)
516 return super.visit(node);
518 IBinding binding= getBinding(node.getArray());
519 if (fArrayBinding.equals(binding)) {
520 IBinding index= getBinding(node.getIndex());
521 if (fIndexBinding.equals(index)) {
522 if (node.getLocationInParent() == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
523 fElementDeclaration= (VariableDeclarationFragment)node.getParent();
527 return super.visit(node);
531 } catch (InvalidBodyError e) {
538 private static IBinding getBinding(Expression expression) {
539 if (expression instanceof FieldAccess) {
540 return ((FieldAccess)expression).resolveFieldBinding();
541 } else if (expression instanceof Name) {
542 return ((Name)expression).resolveBinding();
549 public String getIntroducedVariableName() {
550 if (fElementDeclaration != null) {
551 return fElementDeclaration.getName().getIdentifier();
553 ForStatement forStatement= getForStatement();
554 IJavaProject javaProject= ((CompilationUnit)forStatement.getRoot()).getJavaElement().getJavaProject();
555 String[] proposals= getVariableNameProposals(fArrayAccess.resolveTypeBinding(), javaProject);
564 public void rewriteAST(CompilationUnitRewrite cuRewrite, LinkedProposalModel positionGroups) throws CoreException {
565 TextEditGroup group= createTextEditGroup(FixMessages.Java50Fix_ConvertToEnhancedForLoop_description, cuRewrite);
566 ASTRewrite rewrite= cuRewrite.getASTRewrite();
568 TightSourceRangeComputer rangeComputer;
569 if (rewrite.getExtendedSourceRangeComputer() instanceof TightSourceRangeComputer) {
570 rangeComputer= (TightSourceRangeComputer)rewrite.getExtendedSourceRangeComputer();
572 rangeComputer= new TightSourceRangeComputer();
574 rangeComputer.addTightSourceNode(getForStatement());
575 rewrite.setTargetSourceRangeComputer(rangeComputer);
577 Statement statement= convert(cuRewrite, group, positionGroups);
578 rewrite.replace(getForStatement(), statement, group);
582 protected Statement convert(CompilationUnitRewrite cuRewrite, TextEditGroup group, LinkedProposalModel positionGroups) throws CoreException {
583 ASTRewrite rewrite= cuRewrite.getASTRewrite();
584 ImportRewrite importRewrite= cuRewrite.getImportRewrite();
586 ForStatement forStatement= getForStatement();
588 IJavaProject javaProject= ((CompilationUnit)forStatement.getRoot()).getJavaElement().getJavaProject();
589 String[] proposals= getVariableNameProposals(fArrayAccess.resolveTypeBinding(), javaProject);
591 String parameterName;
592 if (fElementDeclaration != null) {
593 parameterName= fElementDeclaration.getName().getIdentifier();
595 parameterName= proposals[0];
598 LinkedProposalPositionGroup pg= positionGroups.getPositionGroup(parameterName, true);
599 EnhancedForStatement result= pg.generated_1448160073152004877(group, rewrite, importRewrite, forStatement, proposals, parameterName, this);
600 result.setBody(getBody(cuRewrite, group, positionGroups));
602 positionGroups.setEndPosition(rewrite.track(result));
607 void convertBody(Statement body, final IBinding indexBinding, final IBinding arrayBinding, final String parameterName, final ASTRewrite rewrite, final TextEditGroup editGroup, final LinkedProposalPositionGroup pg) {
608 final AST ast= body.getAST();
610 body.accept(new GenericVisitor() {
612 public boolean visit(ArrayAccess node) {
613 IBinding binding= getBinding(node.getArray());
614 if (arrayBinding.equals(binding)) {
615 IBinding index= getBinding(node.getIndex());
616 if (indexBinding.equals(index)) {
621 return super.visit(node);
624 private void replaceAccess(ASTNode node) {
625 if (fElementDeclaration != null && node.getLocationInParent() == VariableDeclarationFragment.INITIALIZER_PROPERTY) {
626 VariableDeclarationFragment fragment= (VariableDeclarationFragment)node.getParent();
627 IBinding targetBinding= fragment.getName().resolveBinding();
628 if (targetBinding != null) {
629 VariableDeclarationStatement statement= (VariableDeclarationStatement)fragment.getParent();
631 if (statement.fragments().size() == 1) {
632 rewrite.remove(statement, editGroup);
634 ListRewrite listRewrite= rewrite.getListRewrite(statement, VariableDeclarationStatement.FRAGMENTS_PROPERTY);
635 listRewrite.remove(fragment, editGroup);
639 SimpleName name= ast.newSimpleName(parameterName);
640 rewrite.replace(node, name, editGroup);
641 pg.addPosition(rewrite.track(name), true);
644 SimpleName name= ast.newSimpleName(parameterName);
645 rewrite.replace(node, name, editGroup);
646 pg.addPosition(rewrite.track(name), true);
652 SingleVariableDeclaration createParameterDeclaration(String parameterName, VariableDeclarationFragment fragement, Expression arrayAccess, ForStatement statement, ImportRewrite importRewrite, ASTRewrite rewrite, TextEditGroup group, LinkedProposalPositionGroup pg, boolean makeFinal) {
653 CompilationUnit compilationUnit= (CompilationUnit)arrayAccess.getRoot();
654 AST ast= compilationUnit.getAST();
656 SingleVariableDeclaration result= ast.newSingleVariableDeclaration();
658 SimpleName name= ast.newSimpleName(parameterName);
659 pg.addPosition(rewrite.track(name), true);
660 result.setName(name);
662 ITypeBinding arrayTypeBinding= arrayAccess.resolveTypeBinding();
663 Type type= importType(arrayTypeBinding.getElementType(), statement, importRewrite, compilationUnit);
664 if (arrayTypeBinding.getDimensions() != 1) {
665 type= ast.newArrayType(type, arrayTypeBinding.getDimensions() - 1);
667 result.setType(type);
669 if (fragement != null) {
670 VariableDeclarationStatement declaration= (VariableDeclarationStatement)fragement.getParent();
671 ModifierRewrite.create(rewrite, result).copyAllModifiers(declaration, group);
673 if (makeFinal && (fragement == null || ASTNodes.findModifierNode(Modifier.FINAL, ASTNodes.getModifiers(fragement)) == null)) {
674 ModifierRewrite.create(rewrite, result).setModifiers(Modifier.FINAL, 0, group);
680 private String[] getVariableNameProposals(ITypeBinding arrayTypeBinding, IJavaProject project) {
681 String[] variableNames= getUsedVariableNames();
682 String baseName= FOR_LOOP_ELEMENT_IDENTIFIER;
683 String name= fArrayBinding.getName();
684 if (name.length() > 2 && name.charAt(name.length() - 1) == 's') {
685 baseName= name.substring(0, name.length() - 1);
687 String[] elementSuggestions= StubUtility.getLocalNameSuggestions(project, baseName, 0, variableNames);
689 String type= arrayTypeBinding.getElementType().getName();
690 String[] typeSuggestions= StubUtility.getLocalNameSuggestions(project, type, arrayTypeBinding.getDimensions() - 1, variableNames);
692 String[] result= new String[elementSuggestions.length + typeSuggestions.length];
693 System.arraycopy(elementSuggestions, 0, result, 0, elementSuggestions.length);
694 System.arraycopy(typeSuggestions, 0, result, elementSuggestions.length, typeSuggestions.length);