diff -r d6cd8a584c86 java.editor/src/org/netbeans/modules/java/editor/imports/JavaFixAllImports.java --- a/java.editor/src/org/netbeans/modules/java/editor/imports/JavaFixAllImports.java Sat Apr 25 01:17:47 2009 +0200 +++ b/java.editor/src/org/netbeans/modules/java/editor/imports/JavaFixAllImports.java Sat Apr 25 19:05:59 2009 +0100 @@ -51,7 +51,6 @@ import java.awt.Toolkit; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -62,6 +61,7 @@ import org.netbeans.api.java.source.Task; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.JavaSource.Phase; +import org.netbeans.api.java.source.SourceUtils; import org.netbeans.api.java.source.TreeMaker; import org.netbeans.api.java.source.TreePathHandle; import org.netbeans.api.java.source.WorkingCopy; @@ -332,37 +332,16 @@ } } - //XXX: copied from SourceUtils.addImports. Ideally, should be on one place only: - public static CompilationUnitTree addImports(CompilationUnitTree cut, List toImport, TreeMaker make) - throws IOException { - // do not modify the list given by the caller (may be reused or immutable). - toImport = new ArrayList(toImport); - Collections.sort(toImport); - - List imports = new ArrayList(cut.getImports()); - int currentToImport = toImport.size() - 1; - int currentExisting = imports.size() - 1; - - while (currentToImport >= 0 && currentExisting >= 0) { - String currentToImportText = toImport.get(currentToImport); - - while (currentExisting >= 0 && (imports.get(currentExisting).isStatic() || imports.get(currentExisting).getQualifiedIdentifier().toString().compareTo(currentToImportText) > 0)) - currentExisting--; - - if (currentExisting >= 0) { - imports.add(currentExisting+1, make.Import(make.Identifier(currentToImportText), false)); - currentToImport--; - } - } - // we are at the head of import section and we still have some imports - // to add, put them to the very beginning - while (currentToImport >= 0) { - String importText = toImport.get(currentToImport); - imports.add(0, make.Import(make.Identifier(importText), false)); - currentToImport--; - } - // return a copy of the unit with changed imports section - return make.CompilationUnit(cut.getPackageName(), imports, cut.getTypeDecls(), cut.getSourceFile()); + /** + * @param cut + * @param toImport + * @param make + * @return + * @deprecated use {@link SourceUtils#addImports(CompilationUnitTree, List, TreeMaker)} + */ + @Deprecated + public static CompilationUnitTree addImports(CompilationUnitTree cut, List toImport, TreeMaker make) { + return SourceUtils.addImports(cut, toImport, make); } } diff -r d6cd8a584c86 java.hints/src/org/netbeans/modules/java/hints/Bundle.properties --- a/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Sat Apr 25 01:17:47 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Sat Apr 25 19:05:59 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 d6cd8a584c86 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 19:05:59 2009 +0100 @@ -0,0 +1,318 @@ +/* + * 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.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 javax.lang.model.util.Types; +import javax.tools.Diagnostic; +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.SourceUtils; +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; + } + int start = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), identifier); + int end = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), identifier); + // XXX is there a better way to ignore error cases + for (Diagnostic diagnostic : info.getDiagnostics()) { + if (diagnostic.getStartPosition() <= end && diagnostic.getEndPosition() >= start) { + return null; + } + } + // TODO ignore case where source code is less than Java 1.5 + + Element enclosingEl = e.getEnclosingElement(); + TreePath klassPath = getContainingClass(treePath); + Element klass = info.getTrees().getElement(klassPath); + + String fqn = null; + if (isSubTypeOrInnerOfSubType(info, klass, enclosingEl)) { + if (hasMethodNameClash(info, klass, e)) { + return null; + } + fqn = getMethodFqn(e); + } + String sn = e.getSimpleName().toString(); + + List fixes = Collections.singletonList(new FixImpl(TreePathHandle.create(treePath, info), fqn, sn)); + 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; + + /** + * @param handle to the METHOD_INVOCATION + * @param fqn to statically import, or null to not perform any imports + * @param sn the simple name to use in the METHOD_SELECT identifier + */ + 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 treePath = handle.resolve(copy); + if (treePath == null || treePath.getLeaf().getKind() != Kind.METHOD_INVOCATION) { + return; + } + MethodInvocationTree tree = (MethodInvocationTree) treePath.getLeaf(); + ExpressionTree identifier = tree.getMethodSelect(); + Element e = copy.getTrees().getElement(new TreePath(treePath, identifier)); + if (e == null || !e.getModifiers().contains(Modifier.STATIC) || identifier.getKind() != Kind.MEMBER_SELECT) { + return; + } + TreeMaker make = copy.getTreeMaker(); + copy.rewrite(identifier, make.Identifier(sn)); + if (fqn == null) { + return; + } + CompilationUnitTree cut = copy.getCompilationUnit(); + CompilationUnitTree nue = SourceUtils.addStaticImports(cut, Collections.singletonList(fqn), make); + copy.rewrite(cut, nue); + } + } + + // returns true if a METHOD is enclosed in element with simple name sn + private static boolean hasMethodWithSimpleName(CompilationInfo info, Element element, final String sn) { + Iterable members = + info.getElementUtilities().getMembers(element.asType(), new ElementUtilities.ElementAcceptor() { + + public boolean accept(Element e, TypeMirror type) { + if (e.getKind() == ElementKind.METHOD && 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 to e, but not fqn + // caveat: clashes with supertype protected methods from other packages will return false + private 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; + } + + /** + * @param info + * @param t1 + * @param t3 + * @return true iff the first type (or its containing class in the case of inner classes) + * is a subtype of the second. + * @see Types#isSubtype(javax.lang.model.type.TypeMirror, javax.lang.model.type.TypeMirror) + */ + public static boolean isSubTypeOrInnerOfSubType(CompilationInfo info, Element t1, Element t2) { + boolean isSubtype = info.getTypes().isSubtype(t1.asType(), t2.asType()); + boolean isInnerClass = t1.getEnclosingElement().getKind() == ElementKind.CLASS; + return isSubtype || (isInnerClass && info.getTypes().isSubtype(t1.getEnclosingElement().asType(), t2.asType())); + } + + /** + * @param info + * @param klass the element for a CLASS + * @param member the STATIC, MEMBER_SELECT Element for a MethodInvocationTree + * @return true if member has a simple name which would clash with local or inherited + * methods in klass (which may be an inner or static class). + */ + public static boolean hasMethodNameClash(CompilationInfo info, Element klass, Element member) { + assert klass != null; + assert member != null; + assert klass.getKind() == ElementKind.CLASS; + assert member.getModifiers().contains(Modifier.STATIC); + Element memberKlass = member.getEnclosingElement(); + assert memberKlass.getKind() == ElementKind.CLASS; + String sn = member.getSimpleName().toString(); + + // check the members and inherited members of the klass + if (hasMethodWithSimpleName(info, klass, sn)) { + return true; + } + Element klassEnclosing = klass.getEnclosingElement(); + if (klassEnclosing.getKind() == ElementKind.CLASS && hasMethodWithSimpleName(info, klassEnclosing, sn)) { + return true; + } + + // check the static import list + return isStaticImportNameClash(info, member); + } + + /** + * @param e + * @return the FQN for a METHOD Element + */ + public static String getMethodFqn(Element e) { + assert e.getKind() == ElementKind.METHOD; + return getElementName(e.getEnclosingElement(), true) + "." + e.getSimpleName(); + } + + /** + * @param tp + * @return the first path which is a CLASS or null if none found + */ + public static TreePath getContainingClass(TreePath tp) { + while (tp != null && tp.getLeaf().getKind() != Kind.CLASS) { + tp = tp.getParentPath(); + } + return tp; + } +} diff -r d6cd8a584c86 java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml --- a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sat Apr 25 01:17:47 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sat Apr 25 19:05:59 2009 +0100 @@ -137,6 +137,7 @@ + diff -r d6cd8a584c86 java.source/src/org/netbeans/api/java/source/SourceUtils.java --- a/java.source/src/org/netbeans/api/java/source/SourceUtils.java Sat Apr 25 01:17:47 2009 +0200 +++ b/java.source/src/org/netbeans/api/java/source/SourceUtils.java Sat Apr 25 19:05:59 2009 +0100 @@ -300,11 +300,28 @@ } /** - * - * + * @param cut + * @param toImport + * @param make + * @return + * @see #addStaticImports */ - private static CompilationUnitTree addImports(CompilationUnitTree cut, List toImport, TreeMaker make) - throws IOException { + public static CompilationUnitTree addImports(CompilationUnitTree cut, List toImport, TreeMaker make) { + return addImports(cut, toImport, make, false); + } + + /** + * @param cut + * @param toImport + * @param make + * @return + * @see #addImports + */ + public static CompilationUnitTree addStaticImports(CompilationUnitTree cut, List toImport, TreeMaker make) { + return addImports(cut, toImport, make, true); + } + + private static CompilationUnitTree addImports(CompilationUnitTree cut, List toImport, TreeMaker make, boolean doStatic) { // do not modify the list given by the caller (may be reused or immutable). toImport = new ArrayList(toImport); Collections.sort(toImport); @@ -315,12 +332,14 @@ while (currentToImport >= 0 && currentExisting >= 0) { String currentToImportText = toImport.get(currentToImport); - - while (currentExisting >= 0 && (imports.get(currentExisting).isStatic() || imports.get(currentExisting).getQualifiedIdentifier().toString().compareTo(currentToImportText) > 0)) + + boolean isStatic = (doStatic ^ imports.get(currentExisting).isStatic()); + while (currentExisting >= 0 && (isStatic || imports.get(currentExisting).getQualifiedIdentifier().toString().compareTo(currentToImportText) > 0)) { currentExisting--; + } if (currentExisting >= 0) { - imports.add(currentExisting+1, make.Import(make.Identifier(currentToImportText), false)); + imports.add(currentExisting+1, make.Import(make.Identifier(currentToImportText), doStatic)); currentToImport--; } } @@ -328,7 +347,7 @@ // to add, put them to the very beginning while (currentToImport >= 0) { String importText = toImport.get(currentToImport); - imports.add(0, make.Import(make.Identifier(importText), false)); + imports.add(0, make.Import(make.Identifier(importText), doStatic)); currentToImport--; } // return a copy of the unit with changed imports section