| /******************************************************************************* |
| * Copyright (c) 2013 Yatta Solutions GmbH 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: |
| * Lukas Hanke <hanke@yatta.de> - Bug 241696 [quick fix] quickfix to iterate over a collection - https://bugs.eclipse.org/bugs/show_bug.cgi?id=241696 |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.ui.text.correction.proposals; |
| |
| import java.util.Collection; |
| import java.util.Iterator; |
| |
| import org.eclipse.core.runtime.CoreException; |
| |
| import org.eclipse.jface.text.link.LinkedPosition; |
| |
| import org.eclipse.jdt.core.ICompilationUnit; |
| 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.CompilationUnit; |
| import org.eclipse.jdt.core.dom.EnhancedForStatement; |
| import org.eclipse.jdt.core.dom.Expression; |
| import org.eclipse.jdt.core.dom.FieldAccess; |
| import org.eclipse.jdt.core.dom.ForStatement; |
| import org.eclipse.jdt.core.dom.IMethodBinding; |
| import org.eclipse.jdt.core.dom.ITypeBinding; |
| import org.eclipse.jdt.core.dom.InfixExpression; |
| import org.eclipse.jdt.core.dom.MethodInvocation; |
| import org.eclipse.jdt.core.dom.NumberLiteral; |
| import org.eclipse.jdt.core.dom.PostfixExpression; |
| import org.eclipse.jdt.core.dom.PrimitiveType; |
| 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.VariableDeclarationExpression; |
| import org.eclipse.jdt.core.dom.VariableDeclarationFragment; |
| import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; |
| |
| import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; |
| import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; |
| import org.eclipse.jdt.internal.corext.dom.Bindings; |
| import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer; |
| |
| import org.eclipse.jdt.internal.ui.JavaPluginImages; |
| import org.eclipse.jdt.internal.ui.text.correction.CorrectionMessages; |
| import org.eclipse.jdt.internal.ui.text.correction.IProposalRelevance; |
| |
| /** |
| * Generates a proposal for quick assist, to loop over a variable or method result which represents |
| * an {@link Iterable} or an array. |
| */ |
| public class GenerateForLoopAssistProposal extends LinkedCorrectionProposal { |
| |
| public static final int GENERATE_FOREACH= 0; |
| |
| public static final int GENERATE_ITERATOR_FOR= 1; |
| |
| public static final int GENERATE_ITERATE_ARRAY= 2; |
| |
| private ASTNode fCurrentNode; |
| |
| private Expression fCurrentExpression; |
| |
| private Expression fSubExpression; |
| |
| private int fLoopTypeToGenerate= -1; |
| |
| /** |
| * Creates an instance of a {@link GenerateForLoopAssistProposal}. |
| * |
| * @param cu the current {@link ICompilationUnit} |
| * @param currentNode the {@link ASTNode} instance representing the statement on which the |
| * assist was called |
| * @param currentExpression the {@link Expression} contained in the currentNode |
| * @param loopTypeToGenerate the type of the loop to generate, possible values are |
| * {@link GenerateForLoopAssistProposal#GENERATE_FOREACH}, |
| * {@link GenerateForLoopAssistProposal#GENERATE_ITERATOR_FOR} or |
| * {@link GenerateForLoopAssistProposal#GENERATE_ITERATE_ARRAY} |
| */ |
| public GenerateForLoopAssistProposal(ICompilationUnit cu, ASTNode currentNode, Expression currentExpression, int loopTypeToGenerate) { |
| super("", cu, null, IProposalRelevance.GENERATE_FOR_LOOP, JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE)); //$NON-NLS-1$ |
| fCurrentNode= currentNode; |
| fCurrentExpression= currentExpression; |
| fLoopTypeToGenerate= loopTypeToGenerate; |
| |
| switch (loopTypeToGenerate) { |
| case GenerateForLoopAssistProposal.GENERATE_FOREACH: |
| setDisplayName(CorrectionMessages.QuickAssistProcessor_generate_enhanced_for_loop); |
| break; |
| case GenerateForLoopAssistProposal.GENERATE_ITERATOR_FOR: |
| setDisplayName(CorrectionMessages.QuickAssistProcessor_generate_iterator_for_loop); |
| break; |
| case GenerateForLoopAssistProposal.GENERATE_ITERATE_ARRAY: |
| setDisplayName(CorrectionMessages.QuickAssistProcessor_generate_iterate_array_for_loop); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* |
| * (non-Javadoc) |
| * @see org.eclipse.jdt.ui.text.java.correction.ASTRewriteCorrectionProposal#getRewrite() |
| */ |
| @Override |
| protected ASTRewrite getRewrite() throws CoreException { |
| |
| AST ast= fCurrentNode.getAST(); |
| createImportRewrite((CompilationUnit) fCurrentExpression.getRoot()); |
| |
| // generate the subexpression which represents the expression to iterate over |
| if (fCurrentExpression instanceof Assignment) { |
| this.fSubExpression= ((Assignment) fCurrentExpression).getLeftHandSide(); |
| } else { |
| this.fSubExpression= fCurrentExpression; |
| } |
| |
| switch (fLoopTypeToGenerate) { |
| case GenerateForLoopAssistProposal.GENERATE_FOREACH: |
| return generateForEachRewrite(ast); |
| case GenerateForLoopAssistProposal.GENERATE_ITERATOR_FOR: |
| return generateIteratorBasedForRewrite(ast); |
| case GenerateForLoopAssistProposal.GENERATE_ITERATE_ARRAY: |
| return generateIndexBasedForRewrite(ast); |
| default: |
| return null; |
| } |
| } |
| |
| /** |
| * Helper to generate a <code>foreach</code> loop to iterate over an {@link Iterable}. |
| * |
| * @param ast the {@link AST} instance to rewrite the loop to |
| * @return the complete {@link ASTRewrite} object |
| */ |
| private ASTRewrite generateForEachRewrite(AST ast) { |
| |
| EnhancedForStatement loopStatement= ast.newEnhancedForStatement(); |
| |
| ASTRewrite rewrite= ASTRewrite.create(ast); |
| ITypeBinding loopOverType= extractElementType(ast); |
| |
| // generate name proposals and add them to the variable declaration |
| SimpleName forDeclarationName= resolveLinkedVariableNameWithProposals(rewrite, loopOverType.getName(), true); |
| |
| SingleVariableDeclaration forLoopInitializer= ast.newSingleVariableDeclaration(); |
| forLoopInitializer.setType(getImportRewrite().addImport(loopOverType, ast, new ContextSensitiveImportRewriteContext(fCurrentNode, getImportRewrite()))); |
| forLoopInitializer.setName(forDeclarationName); |
| |
| loopStatement.setParameter(forLoopInitializer); |
| loopStatement.setExpression((Expression) rewrite.createCopyTarget(fSubExpression)); |
| |
| Block forLoopBody= ast.newBlock(); |
| forLoopBody.statements().add(createBlankLineStatementWithCursorPosition(rewrite)); |
| |
| loopStatement.setBody(forLoopBody); |
| |
| rewrite.replace(fCurrentNode, loopStatement, null); |
| |
| return rewrite; |
| } |
| |
| /** |
| * Helper to generate an iterator based <code>for</code> loop to iterate over an |
| * {@link Iterable}. |
| * |
| * @param ast the {@link AST} instance to rewrite the loop to |
| * @return the complete {@link ASTRewrite} object |
| */ |
| private ASTRewrite generateIteratorBasedForRewrite(AST ast) { |
| ASTRewrite rewrite= ASTRewrite.create(ast); |
| ForStatement loopStatement= ast.newForStatement(); |
| |
| ITypeBinding loopOverType= extractElementType(ast); |
| |
| SimpleName loopVariableName= resolveLinkedVariableNameWithProposals(rewrite, "iterator", true); //$NON-NLS-1$ |
| loopStatement.initializers().add(getIteratorBasedForInitializer(rewrite, loopVariableName)); |
| |
| MethodInvocation loopExpression= ast.newMethodInvocation(); |
| loopExpression.setName(ast.newSimpleName("hasNext")); //$NON-NLS-1$ |
| SimpleName expressionName= ast.newSimpleName(loopVariableName.getIdentifier()); |
| addLinkedPosition(rewrite.track(expressionName), false, expressionName.getIdentifier()); |
| loopExpression.setExpression(expressionName); |
| |
| loopStatement.setExpression(loopExpression); |
| |
| Block forLoopBody= ast.newBlock(); |
| Assignment assignResolvedVariable= getIteratorBasedForBodyAssignment(rewrite, loopOverType, loopVariableName); |
| forLoopBody.statements().add(ast.newExpressionStatement(assignResolvedVariable)); |
| forLoopBody.statements().add(createBlankLineStatementWithCursorPosition(rewrite)); |
| |
| loopStatement.setBody(forLoopBody); |
| |
| rewrite.replace(fCurrentNode, loopStatement, null); |
| |
| return rewrite; |
| } |
| |
| /** |
| * Generates the initializer for an iterator based <code>for</code> loop, which declares and |
| * initializes the variable to loop over. |
| * |
| * @param rewrite the instance of {@link ASTRewrite} |
| * @param loopVariableName the proposed name of the loop variable |
| * @return a {@link VariableDeclarationExpression} to use as initializer |
| */ |
| private VariableDeclarationExpression getIteratorBasedForInitializer(ASTRewrite rewrite, SimpleName loopVariableName) { |
| AST ast= rewrite.getAST(); |
| IMethodBinding iteratorMethodBinding= Bindings.findMethodInHierarchy(fCurrentExpression.resolveTypeBinding(), "iterator", new ITypeBinding[] {}); //$NON-NLS-1$ |
| // initializing fragment |
| VariableDeclarationFragment varDeclarationFragment= ast.newVariableDeclarationFragment(); |
| varDeclarationFragment.setName(loopVariableName); |
| MethodInvocation iteratorExpression= ast.newMethodInvocation(); |
| iteratorExpression.setName(ast.newSimpleName(iteratorMethodBinding.getName())); |
| iteratorExpression.setExpression((Expression) rewrite.createCopyTarget(fCurrentExpression)); |
| varDeclarationFragment.setInitializer(iteratorExpression); |
| |
| // declaration |
| VariableDeclarationExpression varDeclarationExpression= ast.newVariableDeclarationExpression(varDeclarationFragment); |
| varDeclarationExpression.setType(getImportRewrite().addImport(iteratorMethodBinding.getReturnType(), ast, new ContextSensitiveImportRewriteContext(fCurrentNode, getImportRewrite()))); |
| |
| return varDeclarationExpression; |
| } |
| |
| /** |
| * Generates the Assignment in an iterator based for, used in the first statement of an iterator |
| * based <code>for</code> loop body, to retrieve the next element of the {@link Iterable} |
| * instance. |
| * |
| * @param rewrite the current instance of {@link ASTRewrite} |
| * @param loopOverType the {@link ITypeBinding} of the loop variable |
| * @param loopVariableName the name of the loop variable |
| * @return an {@link Assignment}, which retrieves the next element of the {@link Iterable} using |
| * the active {@link Iterator} |
| */ |
| private Assignment getIteratorBasedForBodyAssignment(ASTRewrite rewrite, ITypeBinding loopOverType, SimpleName loopVariableName) { |
| AST ast= rewrite.getAST(); |
| Assignment assignResolvedVariable= ast.newAssignment(); |
| |
| // left hand side |
| SimpleName resolvedVariableName= resolveLinkedVariableNameWithProposals(rewrite, loopOverType.getName(), false); |
| VariableDeclarationFragment resolvedVariableDeclarationFragment= ast.newVariableDeclarationFragment(); |
| resolvedVariableDeclarationFragment.setName(resolvedVariableName); |
| VariableDeclarationExpression resolvedVariableDeclaration= ast.newVariableDeclarationExpression(resolvedVariableDeclarationFragment); |
| resolvedVariableDeclaration.setType(getImportRewrite().addImport(loopOverType, ast, new ContextSensitiveImportRewriteContext(fCurrentNode, getImportRewrite()))); |
| assignResolvedVariable.setLeftHandSide(resolvedVariableDeclaration); |
| |
| // right hand side |
| MethodInvocation invokeIteratorNextExpression= ast.newMethodInvocation(); |
| invokeIteratorNextExpression.setName(ast.newSimpleName("next")); //$NON-NLS-1$ |
| SimpleName currentElementName= ast.newSimpleName(loopVariableName.getIdentifier()); |
| addLinkedPosition(rewrite.track(currentElementName), false, currentElementName.getIdentifier()); |
| invokeIteratorNextExpression.setExpression(currentElementName); |
| assignResolvedVariable.setRightHandSide(invokeIteratorNextExpression); |
| |
| assignResolvedVariable.setOperator(Assignment.Operator.ASSIGN); |
| |
| return assignResolvedVariable; |
| } |
| |
| /** |
| * Helper to generate an index based <code>for</code> loop to iterate over an array. |
| * |
| * @param ast the current {@link AST} instance to generate the {@link ASTRewrite} for |
| * @return an applicable {@link ASTRewrite} instance |
| */ |
| private ASTRewrite generateIndexBasedForRewrite(AST ast) { |
| ASTRewrite rewrite= ASTRewrite.create(ast); |
| |
| ForStatement loopStatement= ast.newForStatement(); |
| SimpleName loopVariableName= resolveLinkedVariableNameWithProposals(rewrite, "i", true); //$NON-NLS-1$ |
| loopStatement.initializers().add(getIndexBasedForInitializer(ast, loopVariableName)); |
| loopStatement.setExpression(getLinkedInfixExpression(rewrite, loopVariableName.getIdentifier())); |
| loopStatement.updaters().add(getLinkedIncrementExpression(rewrite, loopVariableName.getIdentifier())); |
| |
| Block forLoopBody= ast.newBlock(); |
| forLoopBody.statements().add(createBlankLineStatementWithCursorPosition(rewrite)); |
| loopStatement.setBody(forLoopBody); |
| rewrite.replace(fCurrentNode, loopStatement, null); |
| |
| return rewrite; |
| } |
| |
| /** |
| * Creates an {@link InfixExpression} which is linked to the group of the variableToIncrement. |
| * |
| * @param rewrite the current {@link ASTRewrite} instance |
| * @param variableToIncrement the name of the variable to generate the {@link InfixExpression} |
| * for |
| * @return a filled, new {@link InfixExpression} instance |
| */ |
| private InfixExpression getLinkedInfixExpression(ASTRewrite rewrite, String variableToIncrement) { |
| AST ast= rewrite.getAST(); |
| InfixExpression loopExpression= ast.newInfixExpression(); |
| SimpleName name= ast.newSimpleName(variableToIncrement); |
| addLinkedPosition(rewrite.track(name), false, name.getIdentifier()); |
| loopExpression.setLeftOperand(name); |
| loopExpression.setOperator(InfixExpression.Operator.LESS); |
| |
| FieldAccess getArrayLengthExpression= ast.newFieldAccess(); |
| getArrayLengthExpression.setExpression((Expression) rewrite.createCopyTarget(fSubExpression)); |
| getArrayLengthExpression.setName(ast.newSimpleName("length")); //$NON-NLS-1$ |
| |
| loopExpression.setRightOperand(getArrayLengthExpression); |
| return loopExpression; |
| } |
| |
| /** |
| * Creates a {@link PostfixExpression} used to increment the loop variable of a <code>for</code> |
| * loop to iterate over an array. |
| * |
| * @param rewrite the current {@link ASTRewrite} instance |
| * @param variableToIncrement the name of the variable to increment |
| * @return a filled {@link PostfixExpression} realizing an incrementation of the specified |
| * variable |
| */ |
| private Expression getLinkedIncrementExpression(ASTRewrite rewrite, String variableToIncrement) { |
| AST ast= rewrite.getAST(); |
| PostfixExpression incrementLoopVariable= ast.newPostfixExpression(); |
| SimpleName name= ast.newSimpleName(variableToIncrement); |
| addLinkedPosition(rewrite.track(name), false, name.getIdentifier()); |
| incrementLoopVariable.setOperand(name); |
| incrementLoopVariable.setOperator(PostfixExpression.Operator.INCREMENT); |
| return incrementLoopVariable; |
| } |
| |
| /** |
| * Generates a {@link VariableDeclarationExpression}, which initializes the loop variable to |
| * iterate over an array. |
| * |
| * @param ast the current {@link AST} instance |
| * @param loopVariableName the name of the variable which should be initialized |
| * @return a filled {@link VariableDeclarationExpression}, declaring a int variable, which is |
| * initializes with 0 |
| */ |
| private VariableDeclarationExpression getIndexBasedForInitializer(AST ast, SimpleName loopVariableName) { |
| // initializing fragment |
| VariableDeclarationFragment firstDeclarationFragment= ast.newVariableDeclarationFragment(); |
| firstDeclarationFragment.setName(loopVariableName); |
| NumberLiteral startIndex= ast.newNumberLiteral(); |
| firstDeclarationFragment.setInitializer(startIndex); |
| |
| // declaration |
| VariableDeclarationExpression variableDeclaration= ast.newVariableDeclarationExpression(firstDeclarationFragment); |
| PrimitiveType variableType= ast.newPrimitiveType(PrimitiveType.INT); |
| variableDeclaration.setType(variableType); |
| |
| return variableDeclaration; |
| } |
| |
| /** |
| * Resolves name proposals by the given basename and adds a {@link LinkedPosition} to the |
| * returned {@link SimpleName} expression. |
| * |
| * @param rewrite the current instance of an {@link ASTRewrite} |
| * @param basename the base string to use for proposal calculation |
| * @param firstLinkedProposal true if the generated name is the first {@link LinkedPosition} to |
| * edit in the current {@link CompilationUnit}, false otherwise |
| * @return the linked {@link SimpleName} instance based on the name proposals |
| */ |
| private SimpleName resolveLinkedVariableNameWithProposals(ASTRewrite rewrite, String basename, boolean firstLinkedProposal) { |
| AST ast= rewrite.getAST(); |
| String[] nameProposals= getVariableNameProposals(basename); |
| SimpleName forDeclarationName= (nameProposals.length > 0 ? ast.newSimpleName(nameProposals[0]) : ast.newSimpleName(basename)); |
| for (int i= 0; i < nameProposals.length; i++) { |
| addLinkedPositionProposal(forDeclarationName.getIdentifier(), nameProposals[i], null); |
| } |
| |
| // mark declaration name as editable |
| addLinkedPosition(rewrite.track(forDeclarationName), firstLinkedProposal, forDeclarationName.getIdentifier()); |
| return forDeclarationName; |
| } |
| |
| /** |
| * Generates an empty statement, which is shown as blank line and is set as end position for the |
| * cursor. |
| * |
| * @param rewrite the current {@link ASTRewrite} instance |
| * @return an empty statement, shown as blank line |
| */ |
| private Statement createBlankLineStatementWithCursorPosition(ASTRewrite rewrite) { |
| Statement blankLineStatement= (Statement) rewrite.createStringPlaceholder("", ASTNode.EMPTY_STATEMENT); //$NON-NLS-1$ |
| setEndPosition(rewrite.track(blankLineStatement)); |
| return blankLineStatement; |
| } |
| |
| /** |
| * Retrieves variable name proposals for the loop variable. |
| * |
| * @param basename the basename of the proposals |
| * @return an array of proposal strings |
| */ |
| private String[] getVariableNameProposals(String basename) { |
| ASTNode surroundingBlock= fCurrentNode; |
| while ((surroundingBlock= surroundingBlock.getParent()) != null) { |
| if (surroundingBlock instanceof Block) { |
| break; |
| } |
| } |
| Collection<String> localUsedNames= new ScopeAnalyzer((CompilationUnit) fCurrentExpression.getRoot()).getUsedVariableNames(surroundingBlock.getStartPosition(), surroundingBlock.getLength()); |
| String[] names= StubUtility.getLocalNameSuggestions(getCompilationUnit().getJavaProject(), basename, 0, localUsedNames.toArray(new String[localUsedNames.size()])); |
| return names; |
| } |
| |
| /** |
| * Extracts the type parameter of the variable contained in fSubExpression or the elements type |
| * to iterate over an array using <code>foreach</code>. |
| * |
| * @param ast the current {@link AST} instance |
| * @return the {@link ITypeBinding} of the elements to iterate over |
| */ |
| private ITypeBinding extractElementType(AST ast) { |
| ITypeBinding binding= fSubExpression.resolveTypeBinding(); |
| if (binding.isArray()) { |
| return Bindings.normalizeForDeclarationUse(binding.getElementType(), ast); |
| } |
| |
| // extract elements type directly out of the bindings |
| IMethodBinding iteratorMethodBinding= Bindings.findMethodInHierarchy(fCurrentExpression.resolveTypeBinding(), "iterator", new ITypeBinding[] {}); //$NON-NLS-1$ |
| IMethodBinding iteratorNextMethodBinding= Bindings.findMethodInHierarchy(iteratorMethodBinding.getReturnType(), "next", new ITypeBinding[] {}); //$NON-NLS-1$ |
| |
| return iteratorNextMethodBinding.getReturnType(); |
| } |
| |
| } |