Hint to convert an old-style Iterator to a for-loop.
authorJesse Glick <jglick@netbeans.org>
Mon, 16 Apr 2012 15:13:19 -0400
changeset 178155df2d064fa29
parent 17814 89f1aa22083f
child 17816 1f86cca7a83c
child 17818 361013e4690a
Hint to convert an old-style Iterator to a for-loop.
javahints/nbproject/project.properties
javahints/nbproject/project.xml
javahints/src/org/netbeans/modules/javahints/jdk5/IteratorToFor.java
javahints/test/unit/src/org/netbeans/modules/javahints/jdk5/IteratorToForTest.java
     1.1 --- a/javahints/nbproject/project.properties	Mon Apr 16 13:14:53 2012 -0400
     1.2 +++ b/javahints/nbproject/project.properties	Mon Apr 16 15:13:19 2012 -0400
     1.3 @@ -50,7 +50,7 @@
     1.4  auxiliary.org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width=80
     1.5  javac.compilerargs=-Xlint:unchecked
     1.6  javac.source=1.6
     1.7 -spec.version.base=2.53.0
     1.8 +spec.version.base=2.54.0
     1.9  
    1.10  nbm.needs.restart=true
    1.11  requires.nb.javac=true
     2.1 --- a/javahints/nbproject/project.xml	Mon Apr 16 13:14:53 2012 -0400
     2.2 +++ b/javahints/nbproject/project.xml	Mon Apr 16 15:13:19 2012 -0400
     2.3 @@ -423,6 +423,10 @@
     2.4                          <test/>
     2.5                      </test-dependency>
     2.6                      <test-dependency>
     2.7 +                        <code-name-base>org.netbeans.modules.java.hints.test</code-name-base>
     2.8 +                        <compile-dependency/>
     2.9 +                    </test-dependency>
    2.10 +                    <test-dependency>
    2.11                          <code-name-base>org.netbeans.modules.java.lexer</code-name-base>
    2.12                      </test-dependency>
    2.13                      <test-dependency>
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/javahints/src/org/netbeans/modules/javahints/jdk5/IteratorToFor.java	Mon Apr 16 15:13:19 2012 -0400
     3.3 @@ -0,0 +1,198 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
     3.8 + *
     3.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    3.10 + * Other names may be trademarks of their respective owners.
    3.11 + *
    3.12 + * The contents of this file are subject to the terms of either the GNU
    3.13 + * General Public License Version 2 only ("GPL") or the Common
    3.14 + * Development and Distribution License("CDDL") (collectively, the
    3.15 + * "License"). You may not use this file except in compliance with the
    3.16 + * License. You can obtain a copy of the License at
    3.17 + * http://www.netbeans.org/cddl-gplv2.html
    3.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.19 + * specific language governing permissions and limitations under the
    3.20 + * License.  When distributing the software, include this License Header
    3.21 + * Notice in each file and include the License file at
    3.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    3.23 + * particular file as subject to the "Classpath" exception as provided
    3.24 + * by Oracle in the GPL Version 2 section of the License file that
    3.25 + * accompanied this code. If applicable, add the following below the
    3.26 + * License Header, with the fields enclosed by brackets [] replaced by
    3.27 + * your own identifying information:
    3.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    3.29 + *
    3.30 + * If you wish your version of this file to be governed by only the CDDL
    3.31 + * or only the GPL Version 2, indicate your decision by adding
    3.32 + * "[Contributor] elects to include this software in this distribution
    3.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.34 + * single choice of license, a recipient has the option to distribute
    3.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    3.36 + * to extend the choice of license to its licensees as provided above.
    3.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    3.38 + * Version 2 license, then the option applies only if the new code is
    3.39 + * made subject to such option by the copyright holder.
    3.40 + *
    3.41 + * Contributor(s):
    3.42 + *
    3.43 + * Portions Copyrighted 2012 Sun Microsystems, Inc.
    3.44 + */
    3.45 +
    3.46 +package org.netbeans.modules.javahints.jdk5;
    3.47 +
    3.48 +import com.sun.source.tree.BlockTree;
    3.49 +import com.sun.source.tree.ExpressionTree;
    3.50 +import com.sun.source.tree.StatementTree;
    3.51 +import com.sun.source.tree.Tree;
    3.52 +import com.sun.source.tree.VariableTree;
    3.53 +import com.sun.source.util.TreePath;
    3.54 +import com.sun.source.util.TreePathScanner;
    3.55 +import java.util.ArrayList;
    3.56 +import java.util.Collection;
    3.57 +import java.util.Collections;
    3.58 +import java.util.List;
    3.59 +import java.util.Map;
    3.60 +import javax.lang.model.element.Element;
    3.61 +import javax.lang.model.element.Modifier;
    3.62 +import org.netbeans.api.java.source.TreeMaker;
    3.63 +import org.netbeans.api.java.source.WorkingCopy;
    3.64 +import org.netbeans.spi.editor.hints.ErrorDescription;
    3.65 +import org.netbeans.spi.java.hints.ErrorDescriptionFactory;
    3.66 +import org.netbeans.spi.java.hints.Hint;
    3.67 +import org.netbeans.spi.java.hints.HintContext;
    3.68 +import org.netbeans.spi.java.hints.JavaFix;
    3.69 +import org.netbeans.spi.java.hints.JavaFix.TransformationContext;
    3.70 +import org.netbeans.spi.java.hints.TriggerPattern;
    3.71 +import org.openide.util.NbBundle.Messages;
    3.72 +
    3.73 +@Hint(displayName="#DN_IteratorToFor", description="#DESC_IteratorToFor", category="rules15")
    3.74 +@Messages({
    3.75 +    "DN_IteratorToFor=Use JDK 5 for-loop",
    3.76 +    "DESC_IteratorToFor=Replaces simple uses of Iterator with a corresponding for-loop.",
    3.77 +    "ERR_IteratorToFor=Use of Iterator for simple loop",
    3.78 +    "FIX_IteratorToFor=Convert to for-loop"
    3.79 +})
    3.80 +public class IteratorToFor {
    3.81 +
    3.82 +    @TriggerPattern("java.util.Iterator $it = $coll.iterator(); while ($it.hasNext()) {$type $elem = ($type) $it.next(); $rest$;}")
    3.83 +    public static ErrorDescription whileIdiom(HintContext ctx) {
    3.84 +        if (uses(ctx, ctx.getMultiVariables().get("$rest$"), ctx.getVariables().get("$it"))) {
    3.85 +            return null;
    3.86 +        }
    3.87 +        return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_IteratorToFor(), new WhileFix(ctx).toEditorFix());
    3.88 +    }
    3.89 +
    3.90 +    // like JavaFixUtilities.rewriteFix(..., "for ($type $elem : $coll) {$rest$;}"))
    3.91 +    // but does not mess up interior comments
    3.92 +    private static final class WhileFix extends JavaFix {
    3.93 +
    3.94 +        private final HintContext hctx;
    3.95 +
    3.96 +        WhileFix(HintContext hctx) {
    3.97 +            super(hctx.getInfo(), hctx.getPath());
    3.98 +            // XXX #211273 comment #3: should not be keeping hctx here, ought to rewrite
    3.99 +            this.hctx = hctx;
   3.100 +        }
   3.101 +
   3.102 +        @Override protected String getText() {
   3.103 +            return Bundle.FIX_IteratorToFor();
   3.104 +        }
   3.105 +
   3.106 +        @Override protected void performRewrite(TransformationContext ctx) {
   3.107 +            WorkingCopy wc = ctx.getWorkingCopy();
   3.108 +            TreeMaker tm = wc.getTreeMaker();
   3.109 +            Map<String,TreePath> vars = hctx.getVariables();
   3.110 +            Map<String,Collection<? extends TreePath>> multivars = hctx.getMultiVariables();
   3.111 +            BlockTree block = (BlockTree) vars.get("$_").getLeaf();
   3.112 +            List<StatementTree> stmts = new ArrayList<StatementTree>(block.getStatements());
   3.113 +            boolean deleted = stmts.remove((StatementTree) vars.get("$it").getLeaf());
   3.114 +            assert deleted;
   3.115 +            int idx = stmts.indexOf((StatementTree) vars.get("$elem").getParentPath().getParentPath().getLeaf());
   3.116 +            assert idx != -1;
   3.117 +            VariableTree decl = tm.Variable(tm.Modifiers(Collections.<Modifier>emptySet()), hctx.getVariableNames().get("$elem"), vars.get("$type").getLeaf(), null);
   3.118 +            ExpressionTree expr = (ExpressionTree) vars.get("$coll").getLeaf();
   3.119 +            List<StatementTree> rest = new ArrayList<StatementTree>();
   3.120 +            for (TreePath p : multivars.get("$rest$")) {
   3.121 +                rest.add((StatementTree) p.getLeaf());
   3.122 +            }
   3.123 +            StatementTree body = tm.Block(rest, false);
   3.124 +            stmts.set(idx, tm.EnhancedForLoop(decl, expr, body));
   3.125 +            wc.rewrite(block, tm.Block(stmts, false));
   3.126 +        }
   3.127 +
   3.128 +    }
   3.129 +
   3.130 +    @TriggerPattern("for (java.util.Iterator $it = $coll.iterator(); $it.hasNext(); ) {$type $elem = ($type) $it.next(); $rest$;}")
   3.131 +    public static ErrorDescription forIdiom(HintContext ctx) {
   3.132 +        if (uses(ctx, ctx.getMultiVariables().get("$rest$"), ctx.getVariables().get("$it"))) {
   3.133 +            return null;
   3.134 +        }
   3.135 +        return ErrorDescriptionFactory.forName(ctx, ctx.getPath(), Bundle.ERR_IteratorToFor(), new ForFix(ctx).toEditorFix());
   3.136 +    }
   3.137 +
   3.138 +    private static final class ForFix extends JavaFix {
   3.139 +
   3.140 +        private final HintContext hctx;
   3.141 +
   3.142 +        ForFix(HintContext hctx) {
   3.143 +            super(hctx.getInfo(), hctx.getPath());
   3.144 +            this.hctx = hctx;
   3.145 +        }
   3.146 +
   3.147 +        @Override protected String getText() {
   3.148 +            return Bundle.FIX_IteratorToFor();
   3.149 +        }
   3.150 +
   3.151 +        @Override protected void performRewrite(TransformationContext ctx) {
   3.152 +            WorkingCopy wc = ctx.getWorkingCopy();
   3.153 +            TreeMaker tm = wc.getTreeMaker();
   3.154 +            Map<String,TreePath> vars = hctx.getVariables();
   3.155 +            Map<String,Collection<? extends TreePath>> multivars = hctx.getMultiVariables();
   3.156 +            VariableTree decl = tm.Variable(tm.Modifiers(Collections.<Modifier>emptySet()), hctx.getVariableNames().get("$elem"), vars.get("$type").getLeaf(), null);
   3.157 +            ExpressionTree expr = (ExpressionTree) vars.get("$coll").getLeaf();
   3.158 +            List<StatementTree> rest = new ArrayList<StatementTree>();
   3.159 +            for (TreePath p : multivars.get("$rest$")) {
   3.160 +                rest.add((StatementTree) p.getLeaf());
   3.161 +            }
   3.162 +            StatementTree body = tm.Block(rest, false);
   3.163 +            wc.rewrite(ctx.getPath().getLeaf(), tm.EnhancedForLoop(decl, expr, body));
   3.164 +        }
   3.165 +
   3.166 +    }
   3.167 +
   3.168 +    // adapted from org.netbeans.modules.java.hints.declarative.conditionapi.Matcher.referencedIn
   3.169 +    private static boolean uses(final HintContext ctx, Collection<? extends TreePath> statements, TreePath var) {
   3.170 +        final Element e = ctx.getInfo().getTrees().getElement(var);
   3.171 +        for (TreePath tp : statements) {
   3.172 +            boolean occurs = Boolean.TRUE.equals(new TreePathScanner<Boolean, Void>() {
   3.173 +                @Override public Boolean scan(Tree tree, Void p) {
   3.174 +                    if (tree == null) {
   3.175 +                        return false;
   3.176 +                    }
   3.177 +                    TreePath currentPath = new TreePath(getCurrentPath(), tree);
   3.178 +                    Element currentElement = ctx.getInfo().getTrees().getElement(currentPath);
   3.179 +                    if (e.equals(currentElement)) {
   3.180 +                        return true;
   3.181 +                    }
   3.182 +                    return super.scan(tree, p);
   3.183 +                }
   3.184 +                @Override public Boolean reduce(Boolean r1, Boolean r2) {
   3.185 +                    if (r1 == null) {
   3.186 +                        return r2;
   3.187 +                    }
   3.188 +                    if (r2 == null) {
   3.189 +                        return r1;
   3.190 +                    }
   3.191 +                    return r1 || r2;
   3.192 +                }
   3.193 +            }.scan(tp, null));
   3.194 +            if (occurs) {
   3.195 +                return true;
   3.196 +            }
   3.197 +        }
   3.198 +        return false;
   3.199 +    }
   3.200 +
   3.201 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/javahints/test/unit/src/org/netbeans/modules/javahints/jdk5/IteratorToForTest.java	Mon Apr 16 15:13:19 2012 -0400
     4.3 @@ -0,0 +1,183 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2012 Oracle and/or its affiliates. All rights reserved.
     4.8 + *
     4.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    4.10 + * Other names may be trademarks of their respective owners.
    4.11 + *
    4.12 + * The contents of this file are subject to the terms of either the GNU
    4.13 + * General Public License Version 2 only ("GPL") or the Common
    4.14 + * Development and Distribution License("CDDL") (collectively, the
    4.15 + * "License"). You may not use this file except in compliance with the
    4.16 + * License. You can obtain a copy of the License at
    4.17 + * http://www.netbeans.org/cddl-gplv2.html
    4.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    4.19 + * specific language governing permissions and limitations under the
    4.20 + * License.  When distributing the software, include this License Header
    4.21 + * Notice in each file and include the License file at
    4.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    4.23 + * particular file as subject to the "Classpath" exception as provided
    4.24 + * by Oracle in the GPL Version 2 section of the License file that
    4.25 + * accompanied this code. If applicable, add the following below the
    4.26 + * License Header, with the fields enclosed by brackets [] replaced by
    4.27 + * your own identifying information:
    4.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    4.29 + *
    4.30 + * If you wish your version of this file to be governed by only the CDDL
    4.31 + * or only the GPL Version 2, indicate your decision by adding
    4.32 + * "[Contributor] elects to include this software in this distribution
    4.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    4.34 + * single choice of license, a recipient has the option to distribute
    4.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    4.36 + * to extend the choice of license to its licensees as provided above.
    4.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    4.38 + * Version 2 license, then the option applies only if the new code is
    4.39 + * made subject to such option by the copyright holder.
    4.40 + *
    4.41 + * Contributor(s):
    4.42 + *
    4.43 + * Portions Copyrighted 2012 Sun Microsystems, Inc.
    4.44 + */
    4.45 +
    4.46 +package org.netbeans.modules.javahints.jdk5;
    4.47 +
    4.48 +import org.junit.Test;
    4.49 +import org.netbeans.modules.java.hints.test.api.HintTest;
    4.50 +
    4.51 +public class IteratorToForTest {
    4.52 +
    4.53 +    @Test public void whileWarning() throws Exception {
    4.54 +        HintTest.create().input("package test;\n"
    4.55 +                + "import java.util.*;"
    4.56 +                + "public class Test {\n"
    4.57 +                + "    void m(List<String> strings) {\n"
    4.58 +                + "        Iterator it = strings.iterator();\n"
    4.59 +                + "        while (it.hasNext()) {\n"
    4.60 +                + "            String s = (String) it.next();\n"
    4.61 +                + "            System.out.println(s);\n"
    4.62 +                + "            System.err.println(s);\n"
    4.63 +                + "        }\n"
    4.64 +                + "    }\n"
    4.65 +                + "}\n").run(IteratorToFor.class).assertWarnings("2:33-9:5:verifier:" + Bundle.ERR_IteratorToFor());
    4.66 +    }
    4.67 +
    4.68 +    @Test public void whileUsedSpecially() throws Exception {
    4.69 +        HintTest.create().input("package test;\n"
    4.70 +                + "import java.util.*;"
    4.71 +                + "public class Test {\n"
    4.72 +                + "    void m(List<String> strings) {\n"
    4.73 +                + "        Iterator it = strings.iterator();\n"
    4.74 +                + "        while (it.hasNext()) {\n"
    4.75 +                + "            String s = (String) it.next();\n"
    4.76 +                + "            if (s.isEmpty()) {\n"
    4.77 +                + "                it.remove();\n"
    4.78 +                + "            } else {\n"
    4.79 +                + "                System.out.println(s);\n"
    4.80 +                + "            }\n"
    4.81 +                + "        }\n"
    4.82 +                + "    }\n"
    4.83 +                + "}\n").run(IteratorToFor.class).assertWarnings();
    4.84 +    }
    4.85 +
    4.86 +    @Test public void whileFix() throws Exception {
    4.87 +        HintTest.create().input("package test;\n"
    4.88 +                + "import java.util.*;"
    4.89 +                + "public class Test {\n"
    4.90 +                + "    void m(List<String> strings) {\n"
    4.91 +                + "        Collections.reverse(strings);\n"
    4.92 +                + "        Iterator it = strings.iterator();\n"
    4.93 +                + "        while (it.hasNext()) {\n"
    4.94 +                + "            String s = (String) it.next();\n"
    4.95 +                + "            System.out.println(s);\n"
    4.96 +                + "            // OK\n"
    4.97 +                + "            System.err.println(s);\n"
    4.98 +                + "        }\n"
    4.99 +                + "        System.out.println();\n"
   4.100 +                + "    }\n"
   4.101 +                + "}\n").run(IteratorToFor.class).findWarning("2:33-12:5:verifier:" + Bundle.ERR_IteratorToFor()).
   4.102 +                applyFix().
   4.103 +                assertCompilable()
   4.104 +                .assertOutput("package test;\n"
   4.105 +                + "import java.util.*;"
   4.106 +                + "public class Test {\n"
   4.107 +                + "    void m(List<String> strings) {\n"
   4.108 +                + "        Collections.reverse(strings);\n"
   4.109 +                + "        for (String s : strings) {\n"
   4.110 +                + "            System.out.println(s);\n"
   4.111 +                + "            // OK\n"
   4.112 +                + "            System.err.println(s);\n"
   4.113 +                + "        }\n"
   4.114 +                + "        System.out.println();\n"
   4.115 +                + "    }\n"
   4.116 +                + "}\n");
   4.117 +    }
   4.118 +
   4.119 +    @Test public void forWarning() throws Exception {
   4.120 +        HintTest.create().input("package test;\n"
   4.121 +                + "import java.util.*;"
   4.122 +                + "public class Test {\n"
   4.123 +                + "    void m(List<String> strings) {\n"
   4.124 +                + "        for (Iterator it = strings.iterator(); it.hasNext(); ) {\n"
   4.125 +                + "            String s = (String) it.next();\n"
   4.126 +                + "            System.out.println(s);\n"
   4.127 +                + "            System.err.println(s);\n"
   4.128 +                + "        }\n"
   4.129 +                + "    }\n"
   4.130 +                + "}\n").run(IteratorToFor.class).assertWarnings("3:8-3:11:verifier:" + Bundle.ERR_IteratorToFor());
   4.131 +    }
   4.132 +
   4.133 +    @Test public void forUsedSpecially() throws Exception {
   4.134 +        HintTest.create().input("package test;\n"
   4.135 +                + "import java.util.*;"
   4.136 +                + "public class Test {\n"
   4.137 +                + "    void m(List<String> strings) {\n"
   4.138 +                + "        for (Iterator it = strings.iterator(); it.hasNext(); ) {\n"
   4.139 +                + "            String s = (String) it.next();\n"
   4.140 +                + "            if (s.isEmpty()) {\n"
   4.141 +                + "                it.remove();\n"
   4.142 +                + "            } else {\n"
   4.143 +                + "                System.out.println(s);\n"
   4.144 +                + "            }\n"
   4.145 +                + "        }\n"
   4.146 +                + "    }\n"
   4.147 +                + "}\n").run(IteratorToFor.class).assertWarnings();
   4.148 +    }
   4.149 +
   4.150 +    @Test public void forFix() throws Exception {
   4.151 +        HintTest.create().input("package test;\n"
   4.152 +                + "import java.util.*;"
   4.153 +                + "public class Test {\n"
   4.154 +                + "    void m(List<String> strings) {\n"
   4.155 +                + "        Collections.reverse(strings);\n"
   4.156 +                + "        for (Iterator it = strings.iterator(); it.hasNext(); ) {\n"
   4.157 +                + "            String s = (String) it.next();\n"
   4.158 +                + "            System.out.println(s);\n"
   4.159 +                + "            // OK\n"
   4.160 +                + "            System.err.println(s);\n"
   4.161 +                + "        }\n"
   4.162 +                + "        System.out.println();\n"
   4.163 +                + "    }\n"
   4.164 +                + "}\n").run(IteratorToFor.class).findWarning("4:8-4:11:verifier:" + Bundle.ERR_IteratorToFor()).
   4.165 +                applyFix().
   4.166 +                assertCompilable()
   4.167 +                .assertOutput("package test;\n"
   4.168 +                + "import java.util.*;"
   4.169 +                + "public class Test {\n"
   4.170 +                + "    void m(List<String> strings) {\n"
   4.171 +                + "        Collections.reverse(strings);\n"
   4.172 +                + "        for (String s : strings) {\n"
   4.173 +                + "            System.out.println(s);\n"
   4.174 +                + "            // OK\n"
   4.175 +                + "            System.err.println(s);\n"
   4.176 +                + "        }\n"
   4.177 +                + "        System.out.println();\n"
   4.178 +                + "    }\n"
   4.179 +                + "}\n");
   4.180 +    }
   4.181 +
   4.182 +    // XXX also ought to match: for (Iterator i = coll.iterator(); i.hasNext(); ) {use((Type) i.next());}
   4.183 +    // XXX match final modifiers on iterator and/or element vars
   4.184 +    // XXX remove import of java.util.Iterator if present
   4.185 +
   4.186 +}