--- a/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Fri Apr 24 13:16:11 2009 +0200
+++ a/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Sat Apr 25 02:04:18 2009 +0100
@@ -76,7 +76,7 @@
LBL_Imports_EXCLUDED=Import from Excluded
LBL_Imports_STAR=Star import
-DSC_Imports_DELAGATE=Delegate - non GUI
+DSC_Imports_DELEGATE=Delegate - non GUI
DSC_Imports_UNUSED=Unused Import
DSC_Imports_DUPLICATE=Multiple Import
DSC_Imports_SAME_PACKAGE=Import From The Same Package
@@ -262,6 +262,11 @@
ERR_SynchronizationOnNonFinalField=Synchronization on non-final field
DN_SynchronizationOnNonFinalField=Synchronization on non-final field
+HINT_StaticImport=Convert method to static import
+DSC_StaticImport=Convert method to static import
+ERR_StaticImport=Convert method to static import
+DN_StaticImport=Convert method to static import
+
HINT_SuspiciousCall=Suspicious call to {0}:\nExpected type {2}, actual type {1}
HINT_SuspiciousCallIncompatibleTypes=Suspicious call to {0}:\nGiven object cannot contain instances of {1} (expected {2})
DN_CollectionRemove=Suspicous method call
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ 3489508b5c9c Sat Apr 25 02:04:18 2009 +0100
@@ -0,0 +1,272 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License. When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2009 Sun Microsystems, Inc.
+ */
+package org.netbeans.modules.java.hints;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.ExpressionTree;
+import com.sun.source.tree.ImportTree;
+import com.sun.source.tree.MethodInvocationTree;
+import com.sun.source.tree.Tree.Kind;
+import com.sun.source.util.TreePath;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.Modifier;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.type.TypeMirror;
+import org.netbeans.api.java.source.CompilationInfo;
+import org.netbeans.api.java.source.ElementUtilities;
+import org.netbeans.api.java.source.JavaSource;
+import org.netbeans.api.java.source.JavaSource.Phase;
+import org.netbeans.api.java.source.Task;
+import org.netbeans.api.java.source.TreeMaker;
+import org.netbeans.api.java.source.TreePathHandle;
+import org.netbeans.api.java.source.WorkingCopy;
+import org.netbeans.modules.java.hints.spi.AbstractHint;
+import org.netbeans.spi.editor.hints.ChangeInfo;
+import org.netbeans.spi.editor.hints.ErrorDescription;
+import org.netbeans.spi.editor.hints.ErrorDescriptionFactory;
+import org.netbeans.spi.editor.hints.Fix;
+import org.openide.util.NbBundle;
+import static org.netbeans.modules.editor.java.Utilities.getElementName;
+
+/**
+ * Hint offering to convert a qualified static method into a static import. e.g.
+ * Math.abs(-1)
-> abs(-1)
.
+ *
+ * @author Sam Halliday
+ * @see RFE 89258
+ */
+public class StaticImport extends AbstractHint {
+
+ private final AtomicBoolean cancel = new AtomicBoolean();
+
+ public StaticImport() {
+ super(true, false, HintSeverity.CURRENT_LINE_WARNING);
+ }
+
+ @Override
+ public String getDescription() {
+ return NbBundle.getMessage(StaticImport.class, "DSC_StaticImport");
+ }
+
+ public Set getTreeKinds() {
+ return EnumSet.of(Kind.METHOD_INVOCATION);
+ }
+
+ public List run(CompilationInfo info, TreePath treePath) {
+ if (treePath.getLeaf().getKind() != Kind.METHOD_INVOCATION) {
+ return null;
+ }
+ cancel.set(false);
+ MethodInvocationTree tree = (MethodInvocationTree) treePath.getLeaf();
+ ExpressionTree identifier = tree.getMethodSelect();
+ Element e = info.getTrees().getElement(new TreePath(treePath, identifier));
+ if (e == null || !e.getModifiers().contains(Modifier.STATIC) || identifier.getKind() != Kind.MEMBER_SELECT) {
+ return null;
+ }
+ // TODO ignore case where containing class not imported yet (that's for errors to handle)
+
+ // TODO ignore case where source code is less than Java 1.5
+
+ String sn = e.getSimpleName().toString();
+ if (sn.length() == 0){
+ return null;
+ }
+ String fqn;
+ TreePath klassPath = getContainingClass(treePath);
+ Element klassEl = info.getTrees().getElement(klassPath);
+ if (info.getTypes().isSubtype(klassEl.asType(), e.getEnclosingElement().asType())) {
+ //if (e.getEnclosingElement().equals(klassEl)) {
+ // a local or inherited static method, no edits to the import tree necessary
+ fqn = null;
+ } else {
+ Element klassElement = info.getTrees().getElement(klassPath);
+ if (hasElementWithSimpleName(info, klassElement, sn, ElementKind.METHOD) || isStaticImportNameClash(info, e)) {
+ return null;
+ }
+ fqn = getMethodFqn(e);
+ }
+
+ List fixes = Collections.singletonList(new FixImpl(TreePathHandle.create(treePath, info), fqn, sn));
+
+ int start = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), identifier);
+ int end = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), identifier);
+ String desc = NbBundle.getMessage(AddOverrideAnnotation.class, "HINT_StaticImport");
+ ErrorDescription ed = ErrorDescriptionFactory.createErrorDescription(getSeverity().toEditorSeverity(), desc, fixes, info.getFileObject(), start, end);
+ if (cancel.get()) {
+ return null;
+ }
+ return Collections.singletonList(ed);
+ }
+
+ public String getId() {
+ return StaticImport.class.getName();
+ }
+
+ public String getDisplayName() {
+ return NbBundle.getMessage(StaticImport.class, "DSC_StaticImport");
+ }
+
+ public void cancel() {
+ cancel.set(true);
+ }
+
+ private static final class FixImpl implements Fix, Task {
+
+ private final TreePathHandle handle;
+ private final String fqn;
+ private final String sn;
+
+ private FixImpl(TreePathHandle handle, String fqn, String sn) {
+ this.handle = handle;
+ this.fqn = fqn;
+ this.sn = sn;
+ }
+
+ public String getText() {
+ return NbBundle.getMessage(StaticImport.class, "HINT_StaticImport");
+ }
+
+ public ChangeInfo implement() throws Exception {
+ JavaSource js = JavaSource.forFileObject(handle.getFileObject());
+ js.runModificationTask(this).commit();
+ return null;
+ }
+
+ public void run(WorkingCopy copy) throws Exception {
+ if (copy.toPhase(Phase.RESOLVED).compareTo(Phase.RESOLVED) < 0) {
+ return;
+ }
+ TreePath path = handle.resolve(copy);
+ if (path == null || path.getLeaf().getKind() != Kind.METHOD_INVOCATION) {
+ return;
+ }
+ MethodInvocationTree tree = (MethodInvocationTree) path.getLeaf();
+ TreeMaker make = copy.getTreeMaker();
+ copy.rewrite(tree.getMethodSelect(), make.Identifier(sn));
+ if (fqn == null) {
+ return;
+ }
+ CompilationUnitTree cut = copy.getCompilationUnit();
+
+ // XXX can't use JavaFixAllImports.addImports because no support for static imports
+ List imports = new ArrayList(cut.getImports());
+ ImportTree imp = make.Import(make.Identifier(fqn), true);
+ for (ImportTree i : imports) {
+ if (!i.isStatic()) {
+ continue;
+ }
+ String iS = i.getQualifiedIdentifier().toString();
+ if (fqn.equals(iS) || (iS.endsWith(".*") && fqn.startsWith(iS.substring(0, iS.length() - 2)))) { // NOI18N
+ return;
+ }
+ }
+ imports.add(imp); // XXX smart insertion
+ CompilationUnitTree nue = make.CompilationUnit(cut.getPackageName(), imports, cut.getTypeDecls(), cut.getSourceFile());
+ copy.rewrite(cut, nue);
+ }
+ }
+
+ public static String getMethodFqn(Element e) {
+ return getElementName(e.getEnclosingElement(), true) + "." + e.getSimpleName();
+ }
+
+ // returns the class
+ public static TreePath getContainingClass(TreePath tp) {
+ while (tp != null && tp.getLeaf().getKind() != Kind.CLASS) {
+ tp = tp.getParentPath();
+ }
+ return tp;
+ }
+
+ // returns true if an element of kind is enclosed in el with simple name sn
+ public static boolean hasElementWithSimpleName(CompilationInfo info, Element el, final String sn, final ElementKind kind) {
+ Iterable extends Element> members =
+ info.getElementUtilities().getMembers(el.asType(), new ElementUtilities.ElementAcceptor() {
+
+ public boolean accept(Element e, TypeMirror type) {
+ if (e.getKind() == kind && e.getSimpleName().toString().equals(sn)) {
+ return true;
+ }
+ return false;
+ }
+ });
+ return members.iterator().hasNext();
+ }
+
+ // return true if a static import exists with a clashing simple name, but not fqn
+ // caveat: clashes with supertype methods from other packages will return false
+ public static boolean isStaticImportNameClash(CompilationInfo info, Element e) {
+ String fqn = getMethodFqn(e);
+ String sn = e.getSimpleName().toString();
+ String pkg = info.getElements().getPackageOf(e).getQualifiedName().toString();
+ for (ImportTree i : info.getCompilationUnit().getImports()) {
+ if (!i.isStatic()) {
+ continue;
+ }
+ String q = i.getQualifiedIdentifier().toString();
+ if (q.endsWith(".*")) { //NOI18N
+ TypeElement ie = info.getElements().getTypeElement(q.substring(0, q.length() - 2));
+ if (ie == null) {
+ continue;
+ }
+ for (Element enclosed : ie.getEnclosedElements()) {
+ Set modifiers = enclosed.getModifiers();
+ if (enclosed.getKind() != ElementKind.METHOD || !modifiers.contains(Modifier.STATIC) || modifiers.contains(Modifier.PRIVATE)) {
+ continue;
+ }
+ String pkg1 = info.getElements().getPackageOf(enclosed).getQualifiedName().toString();
+ if (!pkg.endsWith(pkg1) && !modifiers.contains(Modifier.PUBLIC)) {
+ continue;
+ }
+ String sn1 = enclosed.getSimpleName().toString();
+ String fqn1 = getMethodFqn(enclosed);
+ if (sn.equals(sn1) && !fqn.equals(fqn1)) {
+ return true;
+ }
+ }
+ } else {
+ int endIndex = q.lastIndexOf("."); //NOI18N
+ if (endIndex == -1 || endIndex == q.length()) {
+ continue;
+ }
+ String fqn1 = q.substring(endIndex + 1);
+ if (q.substring(endIndex).equals(sn) && !fqn.equals(fqn1)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
--- a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Fri Apr 24 13:16:11 2009 +0200
+++ a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sat Apr 25 02:04:18 2009 +0100
@@ -137,6 +137,7 @@
+