diff -r 0df446056874 java.editor/src/org/netbeans/modules/java/editor/imports/JavaFixAllImports.java --- a/java.editor/src/org/netbeans/modules/java/editor/imports/JavaFixAllImports.java Sat May 02 11:43:41 2009 +0200 +++ b/java.editor/src/org/netbeans/modules/java/editor/imports/JavaFixAllImports.java Sun May 03 20:32:25 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,18 @@ } } - //XXX: copied from SourceUtils.addImports. Ideally, should be on one place only: + /** + * @param cut + * @param toImport + * @param make + * @return + * @deprecated use {@link SourceUtils#addImports(CompilationUnitTree, List, TreeMaker)} + * @throws IOException a big fat lie! Kept for legacy. + */ + @Deprecated 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()); + return SourceUtils.addImports(cut, toImport, make); } } diff -r 0df446056874 java.hints/src/org/netbeans/modules/java/hints/Bundle.properties --- a/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/Bundle.properties Sun May 03 20:32:25 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 @@ -263,6 +263,12 @@ ERR_SynchronizationOnNonFinalField=Synchronization on non-final field DN_SynchronizationOnNonFinalField=Synchronization on non-final field +DN_StaticImport=Convert method to static import +DSC_StaticImport=Convert method to static import +ERR_StaticImport=Convert method to static import +HINT_StaticImport=Convert {0} to static import +HINT_StaticImport2=Add static import for {0} + 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 0df446056874 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 Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,373 @@ +/* + * 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.ImportTree; +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 org.netbeans.api.java.queries.SourceLevelQuery; +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). + *

+ * Future versions might support other member types. + * + * @author Sam Halliday + * @see RFE 89258 + * @see getTreeKinds() { + return EnumSet.of(Kind.MEMBER_SELECT); + } + + public List run(CompilationInfo info, TreePath treePath) { + if (treePath == null || treePath.getLeaf().getKind() != Kind.MEMBER_SELECT) { + return null; + } + cancel.set(false); + TreePath mitp = treePath.getParentPath(); + if (mitp == null || mitp.getLeaf().getKind() != Kind.METHOD_INVOCATION) { + return null; + } + Element e = info.getTrees().getElement(treePath); + if (e == null || !e.getModifiers().contains(Modifier.STATIC)) { + return null; + } + if (!supportsStaticImports(info)) { + return null; + } + Element enclosingEl = e.getEnclosingElement(); + if (enclosingEl == null) { + return null; + } + String sn = e.getSimpleName().toString(); + if (!isValidStaticMethod(info, getElementName(enclosingEl, true).toString(), sn)) { + return null; + } + Element klass = info.getTrees().getElement(getContainingClass(treePath)); + String fqn = null; + String fqn1 = getMethodFqn(e); + if (!isSubTypeOrInnerOfSubType(info, klass, enclosingEl) && !isStaticallyImported(info, fqn1)) { + if (hasMethodNameClash(info, klass, sn) || hasStaticImportSimpleNameClash(info, sn)) { + return null; + } + fqn = fqn1; + } + List fixes = Collections.singletonList(new FixImpl(TreePathHandle.create(treePath, info), fqn, sn)); + String desc = NbBundle.getMessage(AddOverrideAnnotation.class, "ERR_StaticImport"); + int start = (int) info.getTrees().getSourcePositions().getStartPosition(info.getCompilationUnit(), treePath.getLeaf()); + int end = (int) info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), treePath.getLeaf()); + 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, "DN_StaticImport"); + } + + public void cancel() { + cancel.set(true); + } + + /** + * @param info + * @return true if the source level supports the static import language feature + */ + public static boolean supportsStaticImports(CompilationInfo info) { + String level = SourceLevelQuery.getSourceLevel(info.getFileObject()); + if (level == null) { + return false; + } + try { + double dLevel = Double.valueOf(level); + if (dLevel < 1.5) { + return false; + } + return true; + } catch (NumberFormatException e) { + return false; + } + } + + public static final class FixImpl implements Fix, Task { + + private final TreePathHandle handle; + private final String fqn; + private final String sn; + + /** + * @param handle to the MEMBER_SELECT + * @param fqn to static import, or null to not perform any imports + * @param sn simple name + */ + public FixImpl(TreePathHandle handle, String fqn, String sn) { + this.handle = handle; + this.fqn = fqn; + this.sn = sn; + } + + public String getText() { + if (fqn == null) { + return NbBundle.getMessage(StaticImport.class, "HINT_StaticImport", sn); + } else { + return NbBundle.getMessage(StaticImport.class, "HINT_StaticImport2", fqn); + } + } + + 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.MEMBER_SELECT) { + return; + } + TreePath mitp = treePath.getParentPath(); + if (mitp == null || mitp.getLeaf().getKind() != Kind.METHOD_INVOCATION) { + return; + } + Element e = copy.getTrees().getElement(treePath); + if (e == null || !e.getModifiers().contains(Modifier.STATIC)) { + return; + } + TreeMaker make = copy.getTreeMaker(); + copy.rewrite(treePath.getLeaf(), 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(); + } + + /** + * @param info + * @param simpleName of static method. + * @return true if a static import exists with the same simple name. + * Caveat, expect false positives on protected and default visibility methods from wildcard static imports. + */ + public static boolean hasStaticImportSimpleNameClash(CompilationInfo info, String simpleName) { + 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 sn1 = enclosed.getSimpleName().toString(); + if (simpleName.equals(sn1)) { + return true; + } + } + } else { + int endIndex = q.lastIndexOf("."); //NOI18N + if (endIndex == -1 || endIndex >= q.length() - 1) { + continue; + } + if (q.substring(endIndex).equals(simpleName)) { + 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) + */ + private 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, String simpleName) { + assert klass != null; + assert klass.getKind() == ElementKind.CLASS; + + // check the members and inherited members of the klass + if (hasMethodWithSimpleName(info, klass, simpleName)) { + return true; + } + Element klassEnclosing = klass.getEnclosingElement(); + return (klassEnclosing != null && klassEnclosing.getKind() == ElementKind.CLASS && hasMethodWithSimpleName(info, klassEnclosing, simpleName)); + } + + /** + * @param e + * @return the FQN for a METHOD Element + */ + public static String getMethodFqn(Element e) { + // XXX or alternatively, upgrade getElementName to handle METHOD + 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; + } + + // return true if the fqn already has a static import + private static boolean isStaticallyImported(CompilationInfo info, String fqn) { + for (ImportTree i : info.getCompilationUnit().getImports()) { + if (!i.isStatic()) { + continue; + } + String q = i.getQualifiedIdentifier().toString(); + if (q.endsWith(".*") && fqn.startsWith(q.substring(0, q.length() - 1))) { //NOI18N + return true; + } + if (q.equals(fqn)) { + return true; + } + } + return false; + } + + /** + * @param info + * @param fqn of the containing class + * @param simpleName of the method + * @return true if {@code fqn.simpleName} represents a valid static method + */ + public static boolean isValidStaticMethod(CompilationInfo info, String fqn, String simpleName) { + TypeElement ie = info.getElements().getTypeElement(fqn); + if (ie == null) { + return false; + } + for (Element enclosed : ie.getEnclosedElements()) { + Set modifiers = enclosed.getModifiers(); + if (enclosed.getKind() != ElementKind.METHOD || !modifiers.contains(Modifier.STATIC) || modifiers.contains(Modifier.PRIVATE)) { + continue; + } + String sn1 = enclosed.getSimpleName().toString(); + if (simpleName.equals(sn1)) { + return true; + } + } + return false; + } +} diff -r 0df446056874 java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java --- a/java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/errors/ImportClass.java Sun May 03 20:32:25 2009 +0100 @@ -43,6 +43,7 @@ import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ImportTree; +import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.Tree.Kind; import com.sun.source.util.TreePath; @@ -57,16 +58,19 @@ import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; +import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import org.netbeans.api.java.source.Task; import org.netbeans.api.java.source.CompilationInfo; 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.TreePathHandle; import org.netbeans.api.java.source.WorkingCopy; import org.netbeans.api.lexer.Token; import org.netbeans.modules.editor.java.Utilities; import org.netbeans.modules.java.editor.imports.ComputeImports; -import org.netbeans.modules.java.editor.imports.JavaFixAllImports; +import org.netbeans.modules.java.hints.StaticImport; import org.netbeans.modules.java.hints.errors.ImportClass.ImportCandidatesHolder; import org.netbeans.modules.java.hints.infrastructure.CreatorBasedLazyFixList; import org.netbeans.modules.java.hints.infrastructure.ErrorHintsProvider; @@ -164,6 +168,26 @@ List unfiltered = candidates.getB(); List fixes = new ArrayList(); + String staticSimpleName = null; + TreePath parent = null; + if (path.getLeaf().getKind() == Kind.IDENTIFIER) { + parent = path.getParentPath(); + if (parent != null && parent.getLeaf().getKind() == Kind.MEMBER_SELECT) { + TreePath mitPath = parent.getParentPath(); + if (mitPath != null && mitPath.getLeaf().getKind() == Kind.METHOD_INVOCATION) { + MemberSelectTree mst = (MemberSelectTree) parent.getLeaf(); + String sn = mst.getIdentifier().toString(); + if (sn.length() != 0) { + TreePath klass = StaticImport.getContainingClass(path); + Element klassElement = info.getTrees().getElement(klass); + if (StaticImport.supportsStaticImports(info) && !StaticImport.hasMethodNameClash(info, klassElement, sn) && !StaticImport.hasStaticImportSimpleNameClash(info, sn)) { + staticSimpleName = sn; + } + } + } + } + } + if (unfiltered != null && filtered != null) { for (String fqn : unfiltered) { StringBuilder sort = new StringBuilder(); @@ -186,6 +210,13 @@ sort.append(fqn); fixes.add(new FixImport(file, fqn, sort.toString(), prefered)); + + if (staticSimpleName != null) { + String mFqn = fqn + "." + staticSimpleName; //NOI18N + if (StaticImport.isValidStaticMethod(info, fqn, staticSimpleName)) { + fixes.add(new StaticImport.FixImpl(TreePathHandle.create(parent, info), mFqn, staticSimpleName)); + } + } } } @@ -340,7 +371,7 @@ return ; } - CompilationUnitTree cut = JavaFixAllImports.addImports( + CompilationUnitTree cut = SourceUtils.addImports( copy.getCompilationUnit(), Collections.singletonList(te.getQualifiedName().toString()), copy.getTreeMaker() diff -r 0df446056874 java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml --- a/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/src/org/netbeans/modules/java/hints/resources/layer.xml Sun May 03 20:32:25 2009 +0100 @@ -137,6 +137,7 @@ + diff -r 0df446056874 java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHint4-hints.pass --- a/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHint4-hints.pass Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHint4-hints.pass Sun May 03 20:32:25 2009 +0100 @@ -1,2 +1,3 @@ Add import for java.util.Collections Add import for java.util.LinkedList +Add static import for java.util.Collections.emptyList diff -r 0df446056874 java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic1-hints.pass --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic1-hints.pass Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,1 @@ +Add import for java.util.Calendar \ No newline at end of file diff -r 0df446056874 java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic2-hints.pass --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic2-hints.pass Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,1 @@ +Add import for javax.crypto.KeyAgreement \ No newline at end of file diff -r 0df446056874 java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic3-hints.pass --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic3-hints.pass Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,1 @@ +Add import for java.util.Collections \ No newline at end of file diff -r 0df446056874 java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic4-hints.pass --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic4-hints.pass Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,2 @@ +Add import for java.util.Collections +Add static import for java.util.Collections.emptySet diff -r 0df446056874 java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic4.pass --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/goldenfiles/org/netbeans/modules/java/hints/errors/ImportClassTest/testImportHintStatic4.pass Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,10 @@ +package org.netbeans.test.java.hints; + +import static java.util.Collections.emptySet; + +public class ImportTestStatic4 { + + public ImportTestStatic4() { + emptySet(); + } +} diff -r 0df446056874 java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic1.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic1.java Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,11 @@ +package org.netbeans.test.java.hints; + +public class ImportTestStatic1 { + + public ImportTestStatic1() { + Calendar.getInstance(); + } + + public void getInstance() { + } +} diff -r 0df446056874 java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic2.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic2.java Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,11 @@ +package org.netbeans.test.java.hints; + +import static java.util.Calendar.*; + +public class ImportTestStatic2 { + + public ImportTestStatic2() throws Exception { + getInstance(); + KeyAgreement.getInstance(""); + } +} diff -r 0df446056874 java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic3.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic3.java Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,8 @@ +package org.netbeans.test.java.hints; + +public class ImportTestStatic3 { + + public ImportTestStatic3() throws Exception { + Collections.doesNotExist(); + } +} diff -r 0df446056874 java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic4.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/data/org/netbeans/test/java/hints/ImportClassEnablerTest/ImportTestStatic4.java Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,8 @@ +package org.netbeans.test.java.hints; + +public class ImportTestStatic4 { + + public ImportTestStatic4() { + Collections.emptySet(); + } +} diff -r 0df446056874 java.hints/test/unit/src/org/netbeans/modules/java/hints/StaticImportTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/java.hints/test/unit/src/org/netbeans/modules/java/hints/StaticImportTest.java Sun May 03 20:32:25 2009 +0100 @@ -0,0 +1,142 @@ +/* + * 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]" + */ +package org.netbeans.modules.java.hints; + +import com.sun.source.util.TreePath; +import java.util.List; +import org.netbeans.api.java.source.CompilationInfo; +import org.netbeans.modules.java.hints.errors.ImportClassTest; +import org.netbeans.modules.java.hints.infrastructure.TreeRuleTestBase; +import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.Fix; +import org.openide.util.NbBundle; + +/** + * The following shell script was used to generate the code snippets + * + * cat test/unit/data/test/Test.java | tr '\n' ' ' | tr '\t' ' ' | sed -E 's| +| |g' | sed 's|"|\\"|g' + * + * but it will break if the source includes single-line comments or strange characters in Strings. + * + * @author Samuel Halliday + * @see ImportClassTest + */ +public class StaticImportTest extends TreeRuleTestBase { + + private final StaticImport computer = new StaticImport(); + + public StaticImportTest(String name) { + super(name); + } + + public void testStaticImportHint1() throws Exception { + String test = "package test; public class Test { public Test() { Math.|abs(1); } }"; + String golden = "package test; import static java.lang.Math.abs; public class Test { public Test() { abs(1); } }"; + performFixTest(test, golden); + } + + public void testStaticImportHint2() throws Exception { + String test = "package test; public class Test { public Test() { Test.get|Logger(); } public static void getLogger() { } }"; + String golden = "package test; public class Test { public Test() { getLogger(); } public static void getLogger() { } }"; + performFixTest(test, golden); + } + + public void testStaticImportHint3() throws Exception { + String test = "package test; public class Test extends Foo { public Test() { Foo.f|oo(); } } class Foo { static protected void foo() { } }"; + String golden = "package test; public class Test extends Foo { public Test() { foo(); } } class Foo { static protected void foo() { } }"; + performFixTest(test, golden); + } + + public void testStaticImportHint4() throws Exception { + String test = "package test; import java.util.Calendar; import static java.util.Calendar.*; public class Test { public Test() { Calendar.getInstance|(); } }"; + String golden = "package test; import java.util.Calendar; import static java.util.Calendar.*; public class Test { public Test() { getInstance(); } }"; + performFixTest(test, golden); + } + + public void testStaticImportHint5() throws Exception { + String test = "package test; import java.util.logging.Logger; public class Test { public Test() { Logger.getLogger|(\"\"); } public static void getLogger() { } }"; + performAnalysisTest(test); + } + + public void testStaticImportHint6() throws Exception { + String test = "package test; public class Test extends Foo { public Test() { Bar.foo|(); } } class Foo { static protected void foo() { } } class Bar { static protected void foo() { } }"; + performAnalysisTest(test); + } + + public void testStaticImportHint7() throws Exception { + String test = "package test; import javax.crypto.KeyAgreement; import static java.util.Calendar.*; public class Test { public Test() throws Exception { KeyAgreement.getInstance|(\"\"); } }"; + performAnalysisTest(test); + } + + public void testStaticImportHint8() throws Exception { + String test = "package test; public class Test extends Foo { class FooBar { FooBar() { Foo.foo|(); } } } class Foo { static protected void foo() { } }"; + String golden = "package test; public class Test extends Foo { class FooBar { FooBar() { foo(); } } } class Foo { static protected void foo() { } }"; + performFixTest(test, golden); + } + + public void testStaticImportHint9() throws Exception { + String test = "package test; public class Test extends Foo { class FooBar { FooBar() { Bar.foo|(); } } } class Foo { static protected void foo() { } } class Bar { static protected void foo() { } }"; + performAnalysisTest(test); + } + + // test is single line source code for test.Test, | in the member select, space before + // golden is the output to test against + // sn is the simple name of the static method + private void performFixTest(String test, String golden) throws Exception { + int offset = test.indexOf("|"); + assertTrue(offset != -1); + int end = test.indexOf("(", offset) - 1; + assertTrue(end > 0); + int start = test.lastIndexOf(" ", offset) + 1; + assertTrue(start > 0); + performFixTest("test/Test.java", + test.replace("|", ""), + offset, + "0:" + start + "-0:" + end + ":hint:" + NbBundle.getMessage(StaticImport.class, "DSC_StaticImport"), + StaticImport.class.getSimpleName(), + golden); + } + + // test is single line source code for test.Test, | in the member select, space before + // completes successfully if there are no hints presented + private void performAnalysisTest(String test) throws Exception { + int offset = test.indexOf("|"); + assertTrue(offset != -1); + performAnalysisTest("test/Test.java", test.replace("|", ""), offset); + } + + @Override + protected List computeErrors(CompilationInfo info, TreePath path) { + return computer.run(info, path); + } + + @Override + protected String toDebugString(CompilationInfo info, Fix f) { + if (f instanceof StaticImport.FixImpl) { + return StaticImport.class.getSimpleName(); + } else { + return super.toDebugString(info, f); + } + } +} \ No newline at end of file diff -r 0df446056874 java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/ImportClassTest.java --- a/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/ImportClassTest.java Sat May 02 11:43:41 2009 +0200 +++ b/java.hints/test/unit/src/org/netbeans/modules/java/hints/errors/ImportClassTest.java Sun May 03 20:32:25 2009 +0100 @@ -74,7 +74,7 @@ } public void testImportHint4() throws Exception { - performTest("ImportTest4", "java.util.Collections", 7, 13); + performTest("ImportTest4", "Add import for java.util.Collections", 7, 13); } public void testImportHint5() throws Exception { @@ -85,6 +85,22 @@ performTest("ImportTest6", "java.util.Collections", 7, 13); } + public void testImportHintStatic1() throws Exception { + performTestDoNotPerform("ImportTestStatic1", 6, 13); + } + + public void testImportHintStatic2() throws Exception { + performTestDoNotPerform("ImportTestStatic2", 9, 15); + } + + public void testImportHintStatic3() throws Exception { + performTestDoNotPerform("ImportTestStatic3", 6, 14); + } + + public void testImportHintStatic4() throws Exception { + performTest("ImportTestStatic4", "static import", 6, 15); + } + public void testImportHintDoNotPropose() throws Exception { performTestDoNotPerform("ImportHintDoNotPropose", 10, 24); performTestDoNotPerform("ImportHintDoNotPropose", 11, 24); diff -r 0df446056874 java.source/src/org/netbeans/api/java/source/SourceUtils.java --- a/java.source/src/org/netbeans/api/java/source/SourceUtils.java Sat May 02 11:43:41 2009 +0200 +++ b/java.source/src/org/netbeans/api/java/source/SourceUtils.java Sun May 03 20:32:25 2009 +0100 @@ -300,11 +300,29 @@ } /** - * - * + * @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) { + // XXX old (private) version claimed to throw IOException, but it didn't really. // do not modify the list given by the caller (may be reused or immutable). toImport = new ArrayList(toImport); Collections.sort(toImport); @@ -316,11 +334,13 @@ 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 ignore = (doStatic ^ imports.get(currentExisting).isStatic()); + while (currentExisting >= 0 && (ignore || 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 +348,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