diff -r 54c6aeec8f2d java.source/apichanges.xml --- a/java.source/apichanges.xml Tue Aug 23 15:18:52 2011 +0200 +++ b/java.source/apichanges.xml Wed Aug 24 10:37:14 2011 +0200 @@ -108,6 +108,20 @@ + + + Added GeneratorUtilities.addImports and several methods to CodeStyle to support organizing imports. + + + + + + Added GeneratorUtilities.addImports method to add import statements for given elements to a compilation unit + in a way that complies with the rules specified in CodeStyle. Added CodeStyle.importInnerClasses, + CodeStyle.getImportGroups, and CodeStyle.separateImportGroups to specify the rules. + + + Added SourceUtils.getJVMSignature to obtain the JVM signature for an ElementHandle diff -r 54c6aeec8f2d java.source/nbproject/project.properties --- a/java.source/nbproject/project.properties Tue Aug 23 15:18:52 2011 +0200 +++ b/java.source/nbproject/project.properties Wed Aug 24 10:37:14 2011 +0200 @@ -46,7 +46,7 @@ javadoc.title=Java Source javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml -spec.version.base=0.84.0 +spec.version.base=0.85.0 test.qa-functional.cp.extra=${refactoring.java.dir}/modules/ext/javac-api-nb-7.0-b07.jar test.unit.run.cp.extra=${o.n.core.dir}/core/core.jar:\ ${o.n.core.dir}/lib/boot.jar:\ diff -r 54c6aeec8f2d java.source/src/org/netbeans/api/java/source/CodeStyle.java --- a/java.source/src/org/netbeans/api/java/source/CodeStyle.java Tue Aug 23 15:18:52 2011 +0200 +++ b/java.source/src/org/netbeans/api/java/source/CodeStyle.java Wed Aug 24 10:37:14 2011 +0200 @@ -747,24 +747,77 @@ // Imports ----------------------------------------------------------------- + /** + * Returns whether to use single class import statements when adding imports. + * @return true if the single class imports should be added, + * false if the 'star' import of the entire package should be added + */ public boolean useSingleClassImport() { return preferences.getBoolean(useSingleClassImport, getDefaultAsBoolean(useSingleClassImport)); } + /** + * Returns whether to use fully qualified class names when generating code. + * @return true if the fully qualified class name should be generated + * every time the class is used, false if the import statement + * and the simple class name should be used instead. + */ public boolean useFQNs() { return preferences.getBoolean(useFQNs, getDefaultAsBoolean(useFQNs)); } + /** + * Returns whether to create import statements for the inner classes. + * @since 0.85 + */ + public boolean importInnerClasses() { + return preferences.getBoolean(importInnerClasses, getDefaultAsBoolean(importInnerClasses)); + } + + /** + * Returns the number of classes that have to be imported from a package + * to convert the single class imports to a 'star' import of the entire package. + */ public int countForUsingStarImport() { return preferences.getInt(countForUsingStarImport, getDefaultAsInt(countForUsingStarImport)); } + /** + * Returns the number of static members that have to be imported from a class + * to convert the single member static imports to a 'star' import of the entire class. + */ public int countForUsingStaticStarImport() { return preferences.getInt(countForUsingStaticStarImport, getDefaultAsInt(countForUsingStaticStarImport)); } + /** + * Returns the names of packages that should be always imported using the 'star' + * import statements. + */ public String[] getPackagesForStarImport() { - return null; + String pkgs = preferences.get(packagesForStarImport, getDefaultAsString(packagesForStarImport)); + if (pkgs == null || pkgs.length() == 0) { + return new String[0]; + } else { + return pkgs.trim().split("\\s*[,;]\\s*"); //NOI18N + } + } + + /** + * Returns an information about the desired grouping of import statements. + * Imported classes are grouped as per their packages. + * @since 0.85 + */ + public ImportGroups getImportGroups() { + return new ImportGroups(preferences.get(importGroupsOrder, getDefaultAsString(importGroupsOrder))); + } + + /** + * Returns whether to separate the import groups with blank lines. + * @since 0.85 + */ + public boolean separateImportGroups() { + return preferences.getBoolean(separateImportGroups, getDefaultAsBoolean(separateImportGroups)); } // Comments ----------------------------------------------------------------- @@ -838,10 +891,83 @@ WRAP_NEVER } + /** + * Provides an information about the desired grouping of import statements, + * including group order. + * @since 0.85 + */ + public static final class ImportGroups { + + private Info[] infos; + private boolean separateStatic; + private int defaultGroupId; + + private ImportGroups(String groups) { + if (groups == null || groups.length() == 0) { + this.infos = new Info[0]; + this.defaultGroupId = 0; + } else { + String[] order = groups.trim().split("\\s*[,;]\\s*"); //NOI18N + this.infos = new Info[order.length - 1]; + int i = 0; + for (String imp : order) { + Info info = new Info(); + if (imp.startsWith("static ")) { //NOI18N + info.isStatic = true; + this.separateStatic = true; + imp = imp.substring(7); + } + if (imp.endsWith("*")) { //NOI18N + info.prefix = imp.substring(0, imp.length() - 1); + } + if (info.prefix.length() == 0) { + this.defaultGroupId = i * 2 + 1; + } else { + this.infos[i++] = info; + } + } + } + } + + /** + * Returns the group number of the given package. Imports with the same + * number form a group. Groups with lower numbers should be positioned + * higher in the import statement list. Imports within a group should + * be sorted alphabetically. + * @param name the package name + * @return the group number + * @since 0.85 + */ + public int getGroupId(String name) { + for (int i = 0; i < infos.length; i++) { + Info info = infos[i]; + if (separateStatic ? info.check(name, false) : info.check(name)) { + return i * 2; + } + } + return defaultGroupId; + } + + private static final class Info { + + private boolean isStatic; + private String prefix; + + private boolean check(String s) { + return prefix.charAt(prefix.length() - 1) == '.' ? s.startsWith(prefix) : s.equals(prefix); + } + + private boolean check(String s, boolean b) { + return isStatic == b && check(s); + } + } + } + // Communication with non public packages ---------------------------------- private static class Producer implements FmtOptions.CodeStyleProducer { + @Override public CodeStyle create(Preferences preferences) { return new CodeStyle(preferences); } diff -r 54c6aeec8f2d java.source/src/org/netbeans/api/java/source/GeneratorUtilities.java --- a/java.source/src/org/netbeans/api/java/source/GeneratorUtilities.java Tue Aug 23 15:18:52 2011 +0200 +++ b/java.source/src/org/netbeans/api/java/source/GeneratorUtilities.java Wed Aug 24 10:37:14 2011 +0200 @@ -49,11 +49,15 @@ import com.sun.source.tree.ClassTree; import com.sun.source.tree.CompilationUnitTree; import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.ImportTree; +import com.sun.source.tree.MemberSelectTree; import com.sun.source.tree.MethodInvocationTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.ModifiersTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.PrimitiveTypeTree; +import com.sun.source.tree.Scope; import com.sun.source.tree.StatementTree; import com.sun.source.tree.Tree; import com.sun.source.tree.Tree.Kind; @@ -62,8 +66,11 @@ import com.sun.source.tree.WildcardTree; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; import com.sun.source.util.TreeScanner; +import com.sun.source.util.Trees; import com.sun.tools.javac.code.Flags; +import com.sun.tools.javac.code.Scope.Entry; import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; @@ -72,20 +79,24 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; + import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; - import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; @@ -95,6 +106,7 @@ import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.type.WildcardType; +import javax.lang.model.util.Elements; import javax.lang.model.util.Types; import javax.swing.text.Document; @@ -165,7 +177,7 @@ Tree lastMember = null; for (Tree tree : clazz.getMembers()) { TreePath path = TreePath.getPath(compilationUnit, tree); - if ((path == null || !utils.isSynthetic(path)) && ClassMemberComparator.compare(member, tree) < 0) { + if ((path == null || !utils.isSynthetic(path)) && CLASS_MEMBER_COMPARATOR.compare(member, tree) < 0) { if (gdoc == null) break; int pos = (int)(lastMember != null ? sp.getEndPosition(compilationUnit, lastMember) : sp.getStartPosition( compilationUnit,clazz)); @@ -512,6 +524,197 @@ BlockTree body = make.Block(Collections.singletonList(make.ExpressionStatement(make.Assignment(make.MemberSelect(isStatic? make.Identifier(clazz.getSimpleName()) : make.Identifier("this"), name), make.Identifier(name)))), false); //NOI18N return make.Method(make.Modifiers(mods), sb, make.Type(copy.getTypes().getNoType(TypeKind.VOID)), Collections.emptyList(), params, Collections.emptyList(), body, null); } + + /** + * Adds import statements for given elements to a compilation unit. The import section of the + * given compilation unit is modified according to the rules specified in the {@link CodeStyle}. + * + * @param cut the compilation unit to insert imports to + * @param toImport the elements to import. + * @return the modified compilation unit + * @since 0.85 + */ + public CompilationUnitTree addImports(CompilationUnitTree cut, Set toImport) { + assert cut != null && toImport != null && toImport.size() > 0; + + ArrayList elementsToImport = new ArrayList(toImport); + + Trees trees = copy.getTrees(); + Elements elements = copy.getElements(); + ElementUtilities elementUtilities = copy.getElementUtilities(); + CodeStyle cs = CodeStyle.getDefault(copy.getFileObject()); + + // check weather any conversions to star imports are needed + int treshold = cs.countForUsingStarImport(); + int staticTreshold = cs.countForUsingStaticStarImport(); + Map pkgCounts = new HashMap(); + pkgCounts.put(elements.getPackageElement("java.lang"), -2); //NOI18N + pkgCounts.put((PackageElement)trees.getElement(TreePath.getPath(cut, cut.getPackageName())), -2); + Map typeCounts = new HashMap(); + Scope scope = trees.getScope(new TreePath(cut)); + for (Element e : elementsToImport) { + boolean isStatic = false; + Element el = null; + switch (e.getKind()) { + case PACKAGE: + el = e; + break; + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + if (e.getEnclosingElement().getKind() == ElementKind.PACKAGE) + el = e.getEnclosingElement(); + break; + case METHOD: + case ENUM_CONSTANT: + case FIELD: + isStatic = true; + el = e.getEnclosingElement(); + assert e.getModifiers().contains(Modifier.STATIC) && trees.isAccessible(scope, e, (DeclaredType)el.asType()) : "Only static accessible members could be imported"; //NOI18N + break; + default: + assert false : "Illegal element kind: " + e.getKind(); //NOI18N + } + if (el != null) { + Integer cnt = isStatic ? typeCounts.get((TypeElement)el) : pkgCounts.get((PackageElement)el); + if (cnt == null) + cnt = 0; + if (el == e) { + cnt = -1; + } else if (cnt >= 0) { + cnt++; + if (isStatic ? cnt >= staticTreshold : cnt >= treshold) + cnt = -1; + } + if (isStatic) { + typeCounts.put((TypeElement)el, cnt); + } else { + pkgCounts.put((PackageElement)el, cnt); + } + } + } + List imports = new ArrayList(cut.getImports()); + for (ImportTree imp : imports) { + Element e = getImportedElement(cut, imp, trees); + Element el = imp.isStatic() + ? e.getKind().isClass() || e.getKind().isInterface() ? e : elementUtilities.enclosingTypeElement(e) + : e.getKind() == ElementKind.PACKAGE ? e : (e.getKind().isClass() || e.getKind().isInterface()) && e.getEnclosingElement().getKind() == ElementKind.PACKAGE ? e.getEnclosingElement() : null; + if (el != null) { + Integer cnt = imp.isStatic() ? typeCounts.get((TypeElement)el) : pkgCounts.get((PackageElement)el); + if (cnt != null) { + if (el == e) { + cnt = -2; + } else if (cnt >= 0) { + cnt++; + if (imp.isStatic() ? cnt >= staticTreshold : cnt >= treshold) + cnt = -1; + } + if (imp.isStatic()) { + typeCounts.put((TypeElement)el, cnt); + } else { + pkgCounts.put((PackageElement)el, cnt); + } + } + } + } + + // check for possible name clashes originating from adding the package imports + Set explicitNamedImports = new HashSet(); + Map usedTypes = null; + JCCompilationUnit unit = (JCCompilationUnit) cut; + if (unit.starImportScope != null) { + for (Map.Entry entry : pkgCounts.entrySet()) { + if (entry.getValue() == -1) { + for (Element element : entry.getKey().getEnclosedElements()) { + if (element.getKind().isClass() || element.getKind().isInterface()) { + Entry starEntry = unit.starImportScope.lookup((com.sun.tools.javac.util.Name)element.getSimpleName()); + if (starEntry.scope != null) { + if (usedTypes == null) { + usedTypes = getUsedTypes(cut, trees); + } + TypeElement te = usedTypes.get(element.getSimpleName()); + if (te != null) { + elementsToImport.add(te); + explicitNamedImports.add(te); + } + } + } + } + } + } + } + + // sort the elements to import + ImportsComparator comparator = new ImportsComparator(cs, elements); + Collections.sort(elementsToImport, comparator); + + // merge the elements to import with the existing import statemetns + TreeMaker make = copy.getTreeMaker(); + int currentToImport = elementsToImport.size() - 1; + int currentExisting = imports.size() - 1; + while (currentToImport >= 0) { + Element currentToImportElement = elementsToImport.get(currentToImport); + boolean isStatic = false; + Element el = null; + switch (currentToImportElement.getKind()) { + case PACKAGE: + el = currentToImportElement; + break; + case ANNOTATION_TYPE: + case CLASS: + case ENUM: + case INTERFACE: + if (currentToImportElement.getEnclosingElement().getKind() == ElementKind.PACKAGE) + el = currentToImportElement.getEnclosingElement(); + break; + case METHOD: + case ENUM_CONSTANT: + case FIELD: + isStatic = true; + el = currentToImportElement.getEnclosingElement(); + break; + } + Integer cnt = el == null ? Integer.valueOf(0) : isStatic ? typeCounts.get((TypeElement)el) : pkgCounts.get((PackageElement)el); + if (explicitNamedImports.contains(currentToImportElement)) + cnt = 0; + if (cnt == -2) { + currentToImport--; + } else { + if (cnt == -1) { + currentToImportElement = el; + if (isStatic) { + typeCounts.put((TypeElement)el, -2); + } else { + pkgCounts.put((PackageElement)el, -2); + } + } + boolean isStar = currentToImportElement.getKind() == ElementKind.PACKAGE + || isStatic && (currentToImportElement.getKind().isClass() || currentToImportElement.getKind().isInterface()); + while (currentExisting >= 0) { + ImportTree imp = imports.get(currentExisting); + Element impElement = getImportedElement(cut, imp, trees); + el = imp.isStatic() + ? impElement.getKind().isClass() || impElement.getKind().isInterface() ? impElement : elementUtilities.enclosingTypeElement(impElement) + : impElement.getKind() == ElementKind.PACKAGE ? impElement : (impElement.getKind().isClass() || impElement.getKind().isInterface()) && impElement.getEnclosingElement().getKind() == ElementKind.PACKAGE ? impElement.getEnclosingElement() : null; + if (currentToImportElement == impElement || isStar && currentToImportElement == el) { + imports.remove(currentExisting); + } else if (comparator.compare(currentToImportElement, impElement) > 0) { + break; + } + currentExisting--; + } + ExpressionTree qualIdent = make.QualIdent(currentToImportElement); + if (isStar) + qualIdent = make.MemberSelect(qualIdent, elements.getName("*")); //NOI18N + imports.add(currentExisting + 1, make.Import(qualIdent, isStatic)); + currentToImport--; + } + } + + // return a copy of the unit with changed imports section + return make.CompilationUnit(cut.getPackageAnnotations(), cut.getPackageName(), imports, cut.getTypeDecls(), cut.getSourceFile()); + } /** * Take a tree as a parameter, replace resolved fully qualified names with @@ -675,10 +878,56 @@ return copy.getTreeUtilities().translate(result, translate); } + + private Element getImportedElement(CompilationUnitTree cut, ImportTree imp, Trees trees) { + Tree qualIdent = imp.getQualifiedIdentifier(); + if (qualIdent.getKind() != Tree.Kind.MEMBER_SELECT) + return trees.getElement(TreePath.getPath(cut, qualIdent)); + Name name = ((MemberSelectTree)qualIdent).getIdentifier(); + if ("*".contentEquals(name)) //NOI18N + return trees.getElement(TreePath.getPath(cut, ((MemberSelectTree)qualIdent).getExpression())); + if (imp.isStatic()) { + Element parent = trees.getElement(TreePath.getPath(cut, ((MemberSelectTree)qualIdent).getExpression())); + if (parent != null && (parent.getKind().isClass() || parent.getKind().isInterface())) { + Scope s = trees.getScope(new TreePath(cut)); + for (Element e : parent.getEnclosedElements()) { + if (name == e.getSimpleName() && e.getModifiers().contains(Modifier.STATIC) && trees.isAccessible(s, e, (DeclaredType)parent.asType())); + return e; + } + return parent; + } + } + return trees.getElement(TreePath.getPath(cut, qualIdent)); + } + + private Map getUsedTypes(final CompilationUnitTree cut, final Trees trees) { + final Map map = new HashMap(); + new TreePathScanner() { - private static class ClassMemberComparator { + @Override + public Void visitIdentifier(IdentifierTree node, Void p) { + if (!map.containsKey(node.getName())) { + Element element = trees.getElement(getCurrentPath()); + if (element != null && (element.getKind().isClass() || element.getKind().isInterface())) { + map.put(node.getName(), (TypeElement) element); + } + } + return super.visitIdentifier(node, p); + } - public static int compare(Tree tree1, Tree tree2) { + @Override + public Void visitCompilationUnit(CompilationUnitTree node, Void p) { + scan(node.getPackageAnnotations(), p); + return scan(node.getTypeDecls(), p); + } + }.scan(cut, null); + return map; + } + + private static class ClassMemberComparator implements Comparator { + + @Override + public int compare(Tree tree1, Tree tree2) { if (tree1 == tree2) return 0; return getSortPriority(tree1) - getSortPriority(tree2); @@ -715,7 +964,52 @@ return ret; } } + + private static ClassMemberComparator CLASS_MEMBER_COMPARATOR = new ClassMemberComparator(); + private static class ImportsComparator implements Comparator { + + private CodeStyle.ImportGroups groups; + + private ImportsComparator(CodeStyle cs, Elements elements) { + this.groups = cs.getImportGroups(); + } + + @Override + public int compare(Element e1, Element e2) { + if (e1 == e2) + return 0; + + StringBuilder sb1 = new StringBuilder(); + if (e1.getKind().isField() || e1.getKind() == ElementKind.METHOD) { + sb1.append('.').append(e1.getSimpleName()); + e1 = e1.getEnclosingElement(); + } + if (e1.getKind().isClass() || e1.getKind().isInterface()) { + sb1.insert(0, ((TypeElement)e1).getQualifiedName()); + } else if (e1.getKind() == ElementKind.PACKAGE) { + sb1.insert(0, ((PackageElement)e1).getQualifiedName()); + } + String s1 = sb1.toString(); + + StringBuilder sb2 = new StringBuilder(); + if (e2.getKind().isField() || e2.getKind() == ElementKind.METHOD) { + sb2.append('.').append(e2.getSimpleName()); + e2 = e2.getEnclosingElement(); + } + if (e2.getKind().isClass() || e2.getKind().isInterface()) { + sb2.insert(0, ((TypeElement)e2).getQualifiedName()); + } else if (e2.getKind() == ElementKind.PACKAGE) { + sb2.insert(0, ((PackageElement)e2).getQualifiedName()); + } + String s2 = sb2.toString(); + + int bal = groups.getGroupId(s1) - groups.getGroupId(s2); + + return bal == 0 ? s1.compareTo(s2) : bal; + } + } + /** * Tags first method in the list, in order to select it later inside editor * @param methods list of methods to be implemented/overriden