Adding a rudimentary fix to initialize uninitialized fields from constructor parameters
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 +}