diff -r 3489508b5c9c java.hints/src/org/netbeans/modules/java/hints/Bundle.properties --- a/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Fri Apr 24 13:16:11 2009 +0200 +++ b/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 diff -r 3489508b5c9c java.hints/src/org/netbeans/modules/java/hints/StaticImport.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/src/org/netbeans/modules/java/hints/StaticImport.java 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 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; + } +} diff -r 3489508b5c9c java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml --- a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Fri Apr 24 13:16:11 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sat Apr 25 02:04:18 2009 +0100 @@ -137,6 +137,7 @@ +