diff --git a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java index 104149c..40a4d85 100644 --- a/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java +++ b/org.eclipse.jdt.ui.tests/ui/org/eclipse/jdt/ui/tests/quickfix/AssistQuickFixTest.java @@ -8,6 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation * Sebastian Davids - testInvertEquals1-23 + * Lukas Hanke - Bug 241696 [quick fix] quickfix to iterate over a collection - https://bugs.eclipse.org/bugs/show_bug.cgi?id=241696 *******************************************************************************/ package org.eclipse.jdt.ui.tests.quickfix; @@ -44,6 +45,7 @@ import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.text.correction.AssistContext; +import org.eclipse.jdt.internal.ui.text.correction.CorrectionMessages; import org.eclipse.jdt.internal.ui.text.correction.QuickAssistProcessor; import org.eclipse.jdt.internal.ui.text.correction.proposals.AssignToVariableAssistProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedNamesAssistProposal; @@ -1268,7 +1270,7 @@ AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(str) + str.length(), 0); List proposals= collectAssists(context, false); - assertNumberOfProposals(proposals, 2); + assertNumberOfProposals(proposals, 4); assertCorrectLabels(proposals); CUCorrectionProposal proposal= (CUCorrectionProposal) proposals.get(0); @@ -8410,4 +8412,394 @@ assertExpectedExistInProposals(proposals, expected); } + + public void testGenerateForSimple() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" collection\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + Map saveOptions= fJProject1.getOptions(false); + Map newOptions= new HashMap(saveOptions); + newOptions.put(DefaultCodeFormatterConstants.FORMATTER_PUT_EMPTY_STATEMENT_ON_NEW_LINE, "true"); + try { + fJProject1.setOptions(newOptions); + String selection= "collection"; + AssistContext context= getCorrectionContext(cu, buf.toString().lastIndexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 2); + assertCorrectLabels(proposals); + + String[] expected= new String[2]; + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (String string : collection) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[0]= buf.toString(); + + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("import java.util.Iterator;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (Iterator iterator = collection.iterator(); iterator\n"); + buf.append(" .hasNext();) {\n"); + buf.append(" String string = iterator.next();\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[1]= buf.toString(); + + assertExpectedExistInProposals(proposals, expected); + } finally { + fJProject1.setOptions(saveOptions); + } + } + + public void testGenerateForWithSemicolon() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" collection;\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + Map saveOptions= fJProject1.getOptions(false); + Map newOptions= new HashMap(saveOptions); + newOptions.put(DefaultCodeFormatterConstants.FORMATTER_PUT_EMPTY_STATEMENT_ON_NEW_LINE, "true"); + try { + fJProject1.setOptions(newOptions); + String selection= "collection;"; + AssistContext context= getCorrectionContext(cu, buf.toString().lastIndexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 4); + assertCorrectLabels(proposals); + + String[] expected= new String[2]; + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (String string : collection) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[0]= buf.toString(); + + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("import java.util.Iterator;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (Iterator iterator = collection.iterator(); iterator\n"); + buf.append(" .hasNext();) {\n"); + buf.append(" String string = iterator.next();\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[1]= buf.toString(); + + assertExpectedExistInProposals(proposals, expected); + } finally { + fJProject1.setOptions(saveOptions); + } + } + + public void testGenerateForMethodInvocation() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Map;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Map map) {\n"); + buf.append(" map.keySet()\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + Map saveOptions= fJProject1.getOptions(false); + Map newOptions= new HashMap(saveOptions); + newOptions.put(DefaultCodeFormatterConstants.FORMATTER_PUT_EMPTY_STATEMENT_ON_NEW_LINE, "true"); + try { + fJProject1.setOptions(newOptions); + String selection= "keySet()"; + AssistContext context= getCorrectionContext(cu, buf.toString().indexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 6); + assertCorrectLabels(proposals); + + String[] expected= new String[2]; + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Map;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Map map) {\n"); + buf.append(" for (String string : map.keySet()) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[0]= buf.toString(); + + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Iterator;\n"); + buf.append("import java.util.Map;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Map map) {\n"); + buf.append(" for (Iterator iterator = map.keySet().iterator(); iterator\n"); + buf.append(" .hasNext();) {\n"); + buf.append(" String string = iterator.next();\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[1]= buf.toString(); + + assertExpectedExistInProposals(proposals, expected); + } finally { + fJProject1.setOptions(saveOptions); + } + } + + public void testGenerateForComplexParametrization() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.LinkedList;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(MySecondOwnIterable collection) {\n"); + buf.append(" collection\n"); + buf.append(" }\n"); + buf.append("private class MyFirstOwnIterable extends LinkedList{}"); + buf.append("private class MySecondOwnIterable extends MyFirstOwnIterable{}"); + buf.append("}\n"); + + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + Map saveOptions= fJProject1.getOptions(false); + Map newOptions= new HashMap(saveOptions); + newOptions.put(DefaultCodeFormatterConstants.FORMATTER_PUT_EMPTY_STATEMENT_ON_NEW_LINE, "true"); + try { + fJProject1.setOptions(newOptions); + String selection= "collection"; + AssistContext context= getCorrectionContext(cu, buf.toString().lastIndexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 2); + assertCorrectLabels(proposals); + + String[] expected= new String[2]; + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.LinkedList;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(MySecondOwnIterable collection) {\n"); + buf.append(" for (String string : collection) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("private class MyFirstOwnIterable extends LinkedList{}"); + buf.append("private class MySecondOwnIterable extends MyFirstOwnIterable{}"); + buf.append("}\n"); + expected[0]= buf.toString(); + + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Iterator;\n"); + buf.append("import java.util.LinkedList;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(MySecondOwnIterable collection) {\n"); + buf.append(" for (Iterator iterator = collection.iterator(); iterator\n"); + buf.append(" .hasNext();) {\n"); + buf.append(" String string = iterator.next();\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("private class MyFirstOwnIterable extends LinkedList{}"); + buf.append("private class MySecondOwnIterable extends MyFirstOwnIterable{}"); + buf.append("}\n"); + expected[1]= buf.toString(); + + assertExpectedExistInProposals(proposals, expected); + } finally { + fJProject1.setOptions(saveOptions); + } + } + + public void testGenerateForMissingParametrization() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" collection\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + Map saveOptions= fJProject1.getOptions(false); + Map newOptions= new HashMap(saveOptions); + newOptions.put(DefaultCodeFormatterConstants.FORMATTER_PUT_EMPTY_STATEMENT_ON_NEW_LINE, "true"); + try { + fJProject1.setOptions(newOptions); + String selection= "collection"; + AssistContext context= getCorrectionContext(cu, buf.toString().lastIndexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 2); + assertCorrectLabels(proposals); + + String[] expected= new String[2]; + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (Object object : collection) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[0]= buf.toString(); + + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("import java.util.Iterator;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (Iterator iterator = collection.iterator(); iterator\n"); + buf.append(" .hasNext();) {\n"); + buf.append(" Object object = iterator.next();\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[1]= buf.toString(); + + assertExpectedExistInProposals(proposals, expected); + } finally { + fJProject1.setOptions(saveOptions); + } + } + + public void testGenerateForLowVersion() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" collection\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + Map saveOptions= fJProject1.getOptions(false); + Map newOptions= new HashMap(); + JavaCore.setComplianceOptions(JavaCore.VERSION_1_4, newOptions); + newOptions.put(DefaultCodeFormatterConstants.FORMATTER_PUT_EMPTY_STATEMENT_ON_NEW_LINE, "true"); + try { + fJProject1.setOptions(newOptions); + + String selection= "collection"; + AssistContext context= getCorrectionContext(cu, buf.toString().lastIndexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 1); + assertProposalDoesNotExist(proposals, CorrectionMessages.QuickAssistProcessor_generate_enhanced_for_loop); + assertCorrectLabels(proposals); + + String[] expected= new String[1]; + + // no generics should be added to iterator since the version is too low + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("import java.util.Collection;\n"); + buf.append("import java.util.Iterator;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(Collection collection) {\n"); + buf.append(" for (Iterator iterator = collection.iterator(); iterator.hasNext();) {\n"); + buf.append(" Object object = iterator.next();\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[0]= buf.toString(); + + assertExpectedExistInProposals(proposals, expected); + } finally { + fJProject1.setOptions(saveOptions); + } + } + + public void testGenerateForArray() throws Exception { + IPackageFragment pack1= fSourceFolder.createPackageFragment("test1", false, null); + StringBuffer buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(String[] array) {\n"); + buf.append(" array\n"); + buf.append(" }\n"); + buf.append("}\n"); + ICompilationUnit cu= pack1.createCompilationUnit("E.java", buf.toString(), false, null); + + String selection= "array"; + AssistContext context= getCorrectionContext(cu, buf.toString().lastIndexOf(selection) + selection.length(), 0); + List proposals= collectAssists(context, false); + + assertNumberOfProposals(proposals, 2); + assertCorrectLabels(proposals); + + String[] expected= new String[1]; + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(String[] array) {\n"); + buf.append(" for (int i = 0; i < array.length; i++) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + expected[0]= buf.toString(); + + buf= new StringBuffer(); + buf.append("package test1;\n"); + buf.append("public class E {\n"); + buf.append(" void foo(String[] array) {\n"); + buf.append(" for (String string : array) {\n"); + buf.append(" \n"); + buf.append(" }\n"); + buf.append(" }\n"); + buf.append("}\n"); + + assertExpectedExistInProposals(proposals, expected); + } } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java index 5df46e8..9086618 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.java @@ -9,6 +9,7 @@ * IBM Corporation - initial API and implementation * Benjamin Muskalla - [quick fix] Quick fix for missing synchronized modifier - https://bugs.eclipse.org/bugs/show_bug.cgi?id=245250 * Billy Huang - [quick assist] concatenate/merge string literals - https://bugs.eclipse.org/77632 + * Lukas Hanke - 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; @@ -44,6 +45,9 @@ public static String QuickAssistProcessor_convert_local_to_field_description; public static String QuickAssistProcessor_convert_to_indexed_for_loop; public static String QuickAssistProcessor_convert_to_iterator_for_loop; + public static String QuickAssistProcessor_generate_enhanced_for_loop; + public static String QuickAssistProcessor_generate_iterator_for_loop; + public static String QuickAssistProcessor_generate_iterate_array_for_loop; public static String QuickAssistProcessor_convert_to_message_format; public static String QuickAssistProcessor_convert_to_multiple_singletype_catch_blocks; public static String QuickAssistProcessor_convert_to_single_multicatch_block; diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties index b7c7ac6..d1fdaaa 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/CorrectionMessages.properties @@ -9,6 +9,7 @@ # IBM Corporation - initial API and implementation # Benjamin Muskalla - [quick fix] Quick fix for missing synchronized modifier - https://bugs.eclipse.org/bugs/show_bug.cgi?id=245250 # Billy Huang - [quick assist] concatenate/merge string literals - https://bugs.eclipse.org/77632 +# Lukas Hanke - Bug 241696 [quick fix] quickfix to iterate over a collection - https://bugs.eclipse.org/bugs/show_bug.cgi?id=241696 ############################################################################### # ------ SerialVersionProposal @@ -344,6 +345,9 @@ QuickAssistProcessor_convert_local_to_field_description=Convert local variable to field QuickAssistProcessor_convert_to_indexed_for_loop=Convert to indexed 'for' loop QuickAssistProcessor_convert_to_iterator_for_loop=Convert to Iterator-based 'for' loop +QuickAssistProcessor_generate_enhanced_for_loop=Iterate over collection using foreach +QuickAssistProcessor_generate_iterator_for_loop=Iterate over collection using iterator +QuickAssistProcessor_generate_iterate_array_for_loop=Iterate over array using for QuickAssistProcessor_convert_to_message_format=Use 'MessageFormat' for string concatenation QuickAssistProcessor_convert_to_multiple_singletype_catch_blocks=Use separate catch blocks QuickAssistProcessor_convert_to_single_multicatch_block=Combine catch blocks diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java index cbc39f7..db7701e 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/IProposalRelevance.java @@ -8,6 +8,7 @@ * Contributors: * IBM Corporation - initial API and implementation * Billy Huang - [quick assist] concatenate/merge string literals - https://bugs.eclipse.org/77632 + * Lukas Hanke - 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; @@ -210,6 +211,7 @@ public static final int JOIN_VARIABLE_DECLARATION= 1; public static final int INVERT_EQUALS= 1; public static final int CONVERT_TO_ITERATOR_FOR_LOOP= 1; + public static final int GENERATE_FOR_LOOP= 1; public static final int ADD_TYPE_TO_ARRAY_INITIALIZER= 1; public static final int REMOVE_EXTRA_PARENTHESES= 1; public static final int CONVERT_ITERABLE_LOOP_TO_ENHANCED= 1; diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java index fa64e79..0175dcb 100644 --- a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/QuickAssistProcessor.java @@ -10,6 +10,7 @@ * Sebastian Davids - Bug 37432 getInvertEqualsProposal * Benjamin Muskalla - Bug 36350 convertToStringBufferPropsal * Chris West (Faux) - [quick assist] "Use 'StringBuilder' for string concatenation" could fix existing misuses - https://bugs.eclipse.org/bugs/show_bug.cgi?id=282755 + * Lukas Hanke - 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; @@ -64,6 +65,7 @@ import org.eclipse.jdt.core.dom.EnhancedForStatement; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.ExpressionStatement; +import org.eclipse.jdt.core.dom.FieldAccess; import org.eclipse.jdt.core.dom.ForStatement; import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMethodBinding; @@ -151,6 +153,7 @@ import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; import org.eclipse.jdt.internal.ui.text.correction.proposals.AssignToVariableAssistProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal; +import org.eclipse.jdt.internal.ui.text.correction.proposals.GenerateForLoopAssistProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedCorrectionProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedNamesAssistProposal; import org.eclipse.jdt.internal.ui.text.correction.proposals.NewDefiningMethodProposal; @@ -206,6 +209,7 @@ || getConvertForLoopProposal(context, coveringNode, null) || getConvertIterableLoopProposal(context, coveringNode, null) || getConvertEnhancedForLoopProposal(context, coveringNode, null) + || getGenerateForLoopProposals(context, coveringNode, null) || getExtractVariableProposal(context, false, null) || getExtractMethodProposal(context, coveringNode, false, null) || getInlineLocalProposal(context, coveringNode, null) @@ -257,6 +261,7 @@ if (!getConvertForLoopProposal(context, coveringNode, resultingCollections)) getConvertIterableLoopProposal(context, coveringNode, resultingCollections); getConvertEnhancedForLoopProposal(context, coveringNode, resultingCollections); + getGenerateForLoopProposals(context, coveringNode, resultingCollections); getRemoveBlockProposals(context, coveringNode, resultingCollections); getMakeVariableDeclarationFinalProposals(context, resultingCollections); getConvertStringConcatenationProposals(context, resultingCollections); @@ -2551,6 +2556,49 @@ return true; } + private boolean getGenerateForLoopProposals(IInvocationContext context, ASTNode coveringNode, Collection resultingCollections) { + Statement statement= ASTResolving.findParentStatement(coveringNode); + if (!(statement instanceof ExpressionStatement)) { + return false; + } + + Expression expression= ((ExpressionStatement) statement).getExpression(); + + ICompilationUnit cu= context.getCompilationUnit(); + ITypeBinding expressionType; + if (expression instanceof MethodInvocation + || expression instanceof SimpleName + || expression instanceof FieldAccess) { + expressionType= expression.resolveTypeBinding(); + } else if (expression instanceof Assignment + && ((Assignment) expression).getRightHandSide().resolveTypeBinding() == null + && ((Assignment) expression).getLeftHandSide().resolveTypeBinding() != null) { + expressionType= ((Assignment) expression).getLeftHandSide().resolveTypeBinding(); + } else { + return false; + } + + if(expressionType != null) { + if (Bindings.findTypeInHierarchy(expressionType, "java.lang.Iterable") != null) { //$NON-NLS-1$ + GenerateForLoopAssistProposal iteratorForProposal= new GenerateForLoopAssistProposal(cu, statement, expression, GenerateForLoopAssistProposal.GENERATE_ITERATOR_FOR); + resultingCollections.add(iteratorForProposal); + } else if (expressionType.isArray()) { + GenerateForLoopAssistProposal iterateArrayProposal= new GenerateForLoopAssistProposal(cu, statement, expression, GenerateForLoopAssistProposal.GENERATE_ITERATE_ARRAY); + resultingCollections.add(iterateArrayProposal); + } else { + return false; + } + } else { + return false; + } + + if (JavaModelUtil.is50OrHigher(cu.getJavaProject())) { + GenerateForLoopAssistProposal foreachProposal= new GenerateForLoopAssistProposal(cu, statement, expression, GenerateForLoopAssistProposal.GENERATE_FOREACH); + resultingCollections.add(foreachProposal); + } + return true; + } + private static ForStatement getEnclosingForStatementHeader(ASTNode node) { return getEnclosingHeader(node, ForStatement.class, ForStatement.INITIALIZERS_PROPERTY, ForStatement.EXPRESSION_PROPERTY, ForStatement.UPDATERS_PROPERTY); } diff --git a/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/GenerateForLoopAssistProposal.java b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/GenerateForLoopAssistProposal.java new file mode 100644 index 0000000..5e4abe7 --- /dev/null +++ b/org.eclipse.jdt.ui/ui/org/eclipse/jdt/internal/ui/text/correction/proposals/GenerateForLoopAssistProposal.java @@ -0,0 +1,455 @@ +/******************************************************************************* + * Copyright (c) 2000, 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 - 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.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.ParameterizedType; +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.SimpleType; +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.core.dom.rewrite.ImportRewrite; + +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.corext.util.JavaModelUtil; + +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(); + + // 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; + } + } + + /** + * Private helper to generate a foreach 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); + String loopOverTypename= extractElementTypeName(ast); + if (loopOverTypename == null) { + loopOverTypename= Object.class.getSimpleName(); + } + // generate name proposals and add them to the variable declaration + SimpleName forDeclarationName= resolveLinkedVariableNameWithProposals(ast, rewrite, loopOverTypename, true); + + SingleVariableDeclaration forLoopInitializer= ast.newSingleVariableDeclaration(); + forLoopInitializer.setType(ast.newSimpleType(ast.newSimpleName(loopOverTypename))); + forLoopInitializer.setName(forDeclarationName); + + loopStatement.setParameter(forLoopInitializer); + loopStatement.setExpression((Expression) ASTNode.copySubtree(ast, fSubExpression)); + + Block forLoopBody= ast.newBlock(); + forLoopBody.statements().add(createBlankLineStatementWithCursorPosition(rewrite)); + + loopStatement.setBody(forLoopBody); + + rewrite.replace(fCurrentNode, loopStatement, null); + + return rewrite; + } + + /** + * Private helper to generate an iterator based for 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(); + + String loopOverTypename= extractElementTypeName(ast); + if (loopOverTypename == null && JavaModelUtil.is50OrHigher(getCompilationUnit().getJavaProject())) { + loopOverTypename= Object.class.getSimpleName(); + } + + SimpleName loopVariableName= resolveLinkedVariableNameWithProposals(ast, rewrite, "iterator", true); //$NON-NLS-1$ + loopStatement.initializers().add(getIteratorBasedForInitializer(ast, rewrite, loopOverTypename, 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(ast, rewrite, loopOverTypename, loopVariableName); + + forLoopBody.statements().add(ast.newExpressionStatement(assignResolvedVariable)); + forLoopBody.statements().add(createBlankLineStatementWithCursorPosition(rewrite)); + + loopStatement.setBody(forLoopBody); + + ImportRewrite fixImports= createImportRewrite((CompilationUnit) fCurrentExpression.getRoot()); + fixImports.addImport("java.util.Iterator"); //$NON-NLS-1$ + + rewrite.replace(fCurrentNode, loopStatement, null); + + return rewrite; + } + + /** + * Generates the initializer for an iterator based for loop, which declares and initializes the + * variable to loop over. + * + * @param ast the current {@link AST} + * @param rewrite the instance of {@link ASTRewrite} + * @param loopOverTypename the type of the loop variable represented as {@link String} + * @param loopVariableName the proposed name of the loop variable + * @return a {@link VariableDeclarationExpression} to use as initializer + */ + private VariableDeclarationExpression getIteratorBasedForInitializer(AST ast, ASTRewrite rewrite, String loopOverTypename, SimpleName loopVariableName) { + // initializing fragment + VariableDeclarationFragment firstDeclarationFragment= ast.newVariableDeclarationFragment(); + firstDeclarationFragment.setName(loopVariableName); + MethodInvocation getIteratorExpression= ast.newMethodInvocation(); + getIteratorExpression.setName(ast.newSimpleName("iterator")); //$NON-NLS-1$ + getIteratorExpression.setExpression((Expression) ASTNode.copySubtree(ast, fSubExpression)); + firstDeclarationFragment.setInitializer(getIteratorExpression); + + // declaration + VariableDeclarationExpression variableDeclaration= ast.newVariableDeclarationExpression(firstDeclarationFragment); + SimpleType simple= ast.newSimpleType(ast.newSimpleName("Iterator")); //$NON-NLS-1$ + if (loopOverTypename != null) { + ParameterizedType variableType= ast.newParameterizedType(simple); + variableType.typeArguments().add(ast.newSimpleType(ast.newSimpleName(loopOverTypename))); + variableDeclaration.setType(variableType); + } else { + variableDeclaration.setType(simple); + } + + return variableDeclaration; + } + + /** + * Generates the Assignment in an iterator based for, used in the first statement of an iterator + * based for loop body, to retrieve the next element of the {@link Iterable} instance. + * + * @param ast the current {@link AST} + * @param rewrite the current instance of {@link ASTRewrite} + * @param loopOverTypename the type of the loop variable in string representation + * @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(AST ast, ASTRewrite rewrite, String loopOverTypename, SimpleName loopVariableName) { + Assignment assignResolvedVariable= ast.newAssignment(); + + // in case no generics were given we get instances of Object.class using iterator.next() + String elementTypename= (loopOverTypename == null ? Object.class.getSimpleName() : loopOverTypename); + // left hand side + SimpleName resolvedVariableName= resolveLinkedVariableNameWithProposals(ast, rewrite, elementTypename, false); + VariableDeclarationFragment resolvedVariableDeclarationFragment= ast.newVariableDeclarationFragment(); + resolvedVariableDeclarationFragment.setName(resolvedVariableName); + VariableDeclarationExpression resolvedVariableDeclaration= ast.newVariableDeclarationExpression(resolvedVariableDeclarationFragment); + resolvedVariableDeclaration.setType(ast.newSimpleType(ast.newSimpleName(elementTypename))); + 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; + } + + /** + * Private helper to generate an index based for 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(ast, rewrite, "i", true); //$NON-NLS-1$ + loopStatement.initializers().add(getIndexBasedForInitializer(ast, loopVariableName)); + + loopStatement.setExpression(getLinkedInfixExpression(ast, rewrite, loopVariableName.getIdentifier())); + + loopStatement.updaters().add(getLinkedIncrementExpression(ast, 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 ast the current {@link AST} instance + * @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(AST ast, ASTRewrite rewrite, String variableToIncrement) { + 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) ASTNode.copySubtree(ast, 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 for loop to + * iterate over an array. + * + * @param ast the current {@link AST} instance + * @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(AST ast, ASTRewrite rewrite, String variableToIncrement) { + 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 an {@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 ast the current {@link AST} + * @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(AST ast, ASTRewrite rewrite, String basename, boolean firstLinkedProposal) { + 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 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 foreach. + * + * @param ast the current {@link AST} instance + * @return the string representation of the type's unqualified name + */ + private String extractElementTypeName(AST ast) { + ITypeBinding binding= fSubExpression.resolveTypeBinding(); + if (binding.isArray()) { + return Bindings.normalizeForDeclarationUse(binding.getElementType(), ast).getName(); + } + ITypeBinding[] typeArguments= Bindings.findTypeInHierarchy(binding, "java.lang.Iterable").getTypeArguments(); //$NON-NLS-1$ + if (typeArguments != null && typeArguments.length > 0) { + return Bindings.normalizeForDeclarationUse(typeArguments[0], ast).getName(); + } + + return null; + } + +}