Adding a rudimentary fix to initialize uninitialized fields from constructor parameters
authorJan Lahoda <jlahoda@netbeans.org>
Sat, 13 Dec 2014 21:56:24 +0100
changeset 18212652ef5852b4d
parent 18211 a1856e334df1
child 18213 4afc82a328ce
Adding a rudimentary fix to initialize uninitialized fields from constructor parameters
javahints/src/org/netbeans/modules/javahints/InitializeFinalField.java
javahints/src/org/netbeans/modules/javahints/resources/layer.xml
javahints/test/unit/src/org/netbeans/modules/javahints/Bundle_test.properties
javahints/test/unit/src/org/netbeans/modules/javahints/InitializeFinalFieldTest.java
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/javahints/src/org/netbeans/modules/javahints/InitializeFinalField.java	Sat Dec 13 21:56:24 2014 +0100
     1.3 @@ -0,0 +1,168 @@
     1.4 +/*
     1.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     1.6 + *
     1.7 + * Copyright 2014 Oracle and/or its affiliates. All rights reserved.
     1.8 + *
     1.9 + * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
    1.10 + * Other names may be trademarks of their respective owners.
    1.11 + *
    1.12 + * The contents of this file are subject to the terms of either the GNU
    1.13 + * General Public License Version 2 only ("GPL") or the Common
    1.14 + * Development and Distribution License("CDDL") (collectively, the
    1.15 + * "License"). You may not use this file except in compliance with the
    1.16 + * License. You can obtain a copy of the License at
    1.17 + * http://www.netbeans.org/cddl-gplv2.html
    1.18 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    1.19 + * specific language governing permissions and limitations under the
    1.20 + * License.  When distributing the software, include this License Header
    1.21 + * Notice in each file and include the License file at
    1.22 + * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    1.23 + * particular file as subject to the "Classpath" exception as provided
    1.24 + * by Oracle in the GPL Version 2 section of the License file that
    1.25 + * accompanied this code. If applicable, add the following below the
    1.26 + * License Header, with the fields enclosed by brackets [] replaced by
    1.27 + * your own identifying information:
    1.28 + * "Portions Copyrighted [year] [name of copyright owner]"
    1.29 + *
    1.30 + * If you wish your version of this file to be governed by only the CDDL
    1.31 + * or only the GPL Version 2, indicate your decision by adding
    1.32 + * "[Contributor] elects to include this software in this distribution
    1.33 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    1.34 + * single choice of license, a recipient has the option to distribute
    1.35 + * your version of this file under either the CDDL, the GPL Version 2 or
    1.36 + * to extend the choice of license to its licensees as provided above.
    1.37 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    1.38 + * Version 2 license, then the option applies only if the new code is
    1.39 + * made subject to such option by the copyright holder.
    1.40 + *
    1.41 + * Contributor(s):
    1.42 + *
    1.43 + * Portions Copyrighted 2014 Sun Microsystems, Inc.
    1.44 + */
    1.45 +package org.netbeans.modules.javahints;
    1.46 +
    1.47 +import com.sun.source.tree.AssignmentTree;
    1.48 +import com.sun.source.tree.MethodTree;
    1.49 +import com.sun.source.tree.StatementTree;
    1.50 +import com.sun.source.tree.Tree.Kind;
    1.51 +import com.sun.source.tree.VariableTree;
    1.52 +import com.sun.source.util.TreePath;
    1.53 +import com.sun.source.util.TreePathScanner;
    1.54 +import java.util.ArrayList;
    1.55 +import java.util.Arrays;
    1.56 +import java.util.EnumSet;
    1.57 +import java.util.HashSet;
    1.58 +import java.util.List;
    1.59 +import java.util.Set;
    1.60 +import javax.lang.model.element.ElementKind;
    1.61 +import javax.lang.model.element.ExecutableElement;
    1.62 +import javax.lang.model.element.Modifier;
    1.63 +import javax.lang.model.element.VariableElement;
    1.64 +import javax.lang.model.util.ElementFilter;
    1.65 +import org.netbeans.api.java.source.CompilationInfo;
    1.66 +import org.netbeans.api.java.source.ElementHandle;
    1.67 +import org.netbeans.api.java.source.TreeMaker;
    1.68 +import org.netbeans.modules.java.hints.spi.ErrorRule;
    1.69 +import org.netbeans.spi.editor.hints.Fix;
    1.70 +import org.netbeans.spi.java.hints.JavaFix;
    1.71 +import org.openide.util.NbBundle.Messages;
    1.72 +
    1.73 +public class InitializeFinalField implements ErrorRule<Void> {
    1.74 +
    1.75 +    private static final Set<String> ERROR_CODES = new HashSet<String>(Arrays.asList(
    1.76 +            "compiler.err.var.might.not.have.been.initialized")); // NOI18N
    1.77 +
    1.78 +    @Override
    1.79 +    public Set<String> getCodes() {
    1.80 +        return ERROR_CODES;
    1.81 +    }
    1.82 +
    1.83 +    @Override
    1.84 +    public List<Fix> run(final CompilationInfo info, String diagnosticKey, int offset, TreePath treePath, Data<Void> data) {
    1.85 +        TreePath path = info.getTreeUtilities().pathFor(offset - 1);
    1.86 +        if (path.getParentPath() != null && path.getParentPath().getLeaf().getKind() == Kind.METHOD) {
    1.87 +            ExecutableElement constr = (ExecutableElement) info.getTrees().getElement(path.getParentPath());
    1.88 +
    1.89 +            if (constr.getKind() != ElementKind.CONSTRUCTOR)
    1.90 +                return null;
    1.91 +
    1.92 +            //TODO: should use flow?
    1.93 +            final Set<VariableElement> uninits = new HashSet<VariableElement>();
    1.94 +
    1.95 +            for (VariableElement field : ElementFilter.fieldsIn(constr.getEnclosingElement().getEnclosedElements())) {
    1.96 +                if (field.getModifiers().contains(Modifier.FINAL)) {
    1.97 +                    uninits.add(field);
    1.98 +                }
    1.99 +            }
   1.100 +
   1.101 +            new TreePathScanner<Void, Void>() {
   1.102 +                @Override
   1.103 +                public Void visitAssignment(AssignmentTree node, Void p) {
   1.104 +                    uninits.remove(info.getTrees().getElement(new TreePath(getCurrentPath(), node.getVariable())));
   1.105 +                    return super.visitAssignment(node, p);
   1.106 +                }
   1.107 +            }.scan(path, null);
   1.108 +
   1.109 +            List<Fix> fixes = new ArrayList<Fix>();
   1.110 +
   1.111 +            for (VariableElement uninit : uninits) {
   1.112 +                fixes.add(new AddConstructorParameter(info, path.getParentPath(), uninit).toEditorFix());
   1.113 +            }
   1.114 +
   1.115 +            return fixes;
   1.116 +        }
   1.117 +
   1.118 +        return null;
   1.119 +    }
   1.120 +
   1.121 +    @Override
   1.122 +    public String getId() {
   1.123 +        return InitializeFinalField.class.getName();
   1.124 +    }
   1.125 +
   1.126 +    @Override
   1.127 +    @Messages("DN_InitializeFinalField=Initialize field from a new constructor parameter")
   1.128 +    public String getDisplayName() {
   1.129 +        return Bundle.DN_InitializeFinalField();
   1.130 +    }
   1.131 +
   1.132 +    @Override
   1.133 +    public void cancel() {
   1.134 +    }
   1.135 +
   1.136 +    private static final class AddConstructorParameter extends JavaFix {
   1.137 +
   1.138 +        private final ElementHandle<VariableElement> uninitializedField;
   1.139 +        private final String fieldName;
   1.140 +
   1.141 +        public AddConstructorParameter(CompilationInfo info, TreePath tp, VariableElement uninitializedField) {
   1.142 +            super(info, tp);
   1.143 +            this.uninitializedField = ElementHandle.create(uninitializedField);
   1.144 +            this.fieldName = uninitializedField.getSimpleName().toString();
   1.145 +        }
   1.146 +
   1.147 +        @Override
   1.148 +        @Messages("FIX_InitializeField=Initialize {0} from a new constructor parameter")
   1.149 +        public String getText() {
   1.150 +            return Bundle.FIX_InitializeField(fieldName);
   1.151 +        }
   1.152 +
   1.153 +        @Override
   1.154 +        protected void performRewrite(TransformationContext ctx) throws Exception {
   1.155 +            TreePath constrPath = ctx.getPath();
   1.156 +            VariableElement field = uninitializedField.resolve(ctx.getWorkingCopy());
   1.157 +            MethodTree constr = (MethodTree) constrPath.getLeaf();
   1.158 +            TreeMaker make = ctx.getWorkingCopy().getTreeMaker();
   1.159 +            //TODO: check clashes
   1.160 +            //TODO: use the verbatim field's type?
   1.161 +            VariableTree newParam = make.Variable(make.Modifiers(EnumSet.noneOf(Modifier.class)), field.getSimpleName(), make.Type(field.asType()), null);
   1.162 +            ctx.getWorkingCopy().rewrite(constr, make.addMethodParameter(constr, newParam));
   1.163 +            StatementTree assgn = make.ExpressionStatement(make.Assignment(make.MemberSelect(make.Identifier("this"),
   1.164 +                                                                                             field.getSimpleName()),
   1.165 +                                                                           make.Identifier(field.getSimpleName())));
   1.166 +            ctx.getWorkingCopy().rewrite(constr.getBody(), make.addBlockStatement(constr.getBody(), assgn));
   1.167 +        }
   1.168 +
   1.169 +    }
   1.170 +
   1.171 +}
     2.1 --- a/javahints/src/org/netbeans/modules/javahints/resources/layer.xml	Sat Dec 13 21:47:48 2014 +0100
     2.2 +++ b/javahints/src/org/netbeans/modules/javahints/resources/layer.xml	Sat Dec 13 21:56:24 2014 +0100
     2.3 @@ -51,6 +51,7 @@
     2.4                  <file name="org-netbeans-modules-javahints-TypeInForEachLoop.instance"/>
     2.5                  <file name="org-netbeans-modules-javahints-MakeStatic.instance"/>
     2.6                  <file name="org-netbeans-modules-javahints-AddConstructor.instance"/>
     2.7 +                <file name="org-netbeans-modules-javahints-InitializeFinalField.instance"/>
     2.8              </folder>
     2.9  
    2.10              <folder name="hints">
     3.1 --- a/javahints/test/unit/src/org/netbeans/modules/javahints/Bundle_test.properties	Sat Dec 13 21:47:48 2014 +0100
     3.2 +++ b/javahints/test/unit/src/org/netbeans/modules/javahints/Bundle_test.properties	Sat Dec 13 21:56:24 2014 +0100
     3.3 @@ -18,3 +18,4 @@
     3.4  FIX_AddReturn=FIX_AddReturn
     3.5  
     3.6  FIX_AddConstructor=FIX_AddConstructor:{0}
     3.7 +FIX_InitializeField=FIX_InitializeField:{0}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/javahints/test/unit/src/org/netbeans/modules/javahints/InitializeFinalFieldTest.java	Sat Dec 13 21:56:24 2014 +0100
     4.3 @@ -0,0 +1,64 @@
     4.4 +/*
     4.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     4.6 + *
     4.7 + * Copyright 2014 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 2014 Sun Microsystems, Inc.
    4.44 + */
    4.45 +package org.netbeans.modules.javahints;
    4.46 +
    4.47 +import org.netbeans.modules.java.hints.infrastructure.ErrorHintsTestBase;
    4.48 +import org.openide.util.NbBundle;
    4.49 +
    4.50 +public class InitializeFinalFieldTest extends ErrorHintsTestBase {
    4.51 +
    4.52 +    public InitializeFinalFieldTest(String name) {
    4.53 +        super(name, InitializeFinalField.class);
    4.54 +    }
    4.55 +
    4.56 +    public void testSimple() throws Exception {
    4.57 +        performFixTest("test/Test.java",
    4.58 +                       "package test; public class Test { final int i1; final int i2; public Test(int i1) { this.i1 = i1; } }",
    4.59 +                       -1,
    4.60 +                       "FIX_InitializeField:i2",
    4.61 +                       "package test; public class Test { final int i1; final int i2; public Test(int i1, int i2) { this.i1 = i1; this.i2 = i2; } }");
    4.62 +    }
    4.63 +
    4.64 +    static {
    4.65 +        NbBundle.setBranding("test");
    4.66 +    }
    4.67 +}