diff --git a/.gitignore b/.gitignore index 3f39de9..82dfc86 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ *.ipr *.iws *.swp +.idea .settings .project .classpath diff --git a/pom.xml b/pom.xml index 6c9edd3..f87b650 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ umlgraph jar UMLGraph - 5.7.3-SNAPSHOT + 6.0.0-SNAPSHOT Declarative Drawing of UML Diagrams http://www.spinellis.gr/umlgraph @@ -40,19 +40,9 @@ org.sonatype.oss oss-parent - 7 + 9 - - - sun.jdk - tools - 1.5.0 - system - ${java.home}/../lib/tools.jar - - - @@ -63,15 +53,16 @@ maven-compiler-plugin - 2.3.2 + 3.10.1 - 1.5 - 1.5 + 9 + 9 - + + org.apache.maven.plugins maven-jar-plugin - 2.3.1 + 3.3.0 @@ -84,18 +75,18 @@ org.apache.maven.plugins maven-release-plugin - 2.5 + 3.0.0-M7 - true - false - release - deploy + true + false + release + deploy org.apache.maven.plugins maven-source-plugin - 2.2.1 + 3.2.1 attach-sources @@ -108,36 +99,39 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.9 + 3.5.0 - - - depend - X - - - hidden - X - - - opt - X - - + + + depend + X + + + hidden + X + + + opt + X + + org.umlgraph.doclet.UmlGraphDoc ${project.build.directory}${file.separator}${project.build.finalName}.jar - -inferrel - -inferdep - -autosize - -collapsible - -hide java.* - -collpackages - -qualify - -postfixpackage - -nodefontsize 9 - -nodefontpackagesize 7 - -link http://docs.oracle.com/javase/7/docs/jdk/api/javadoc/doclet/ - -link http://download.oracle.com/javase/7/docs/api/ + + + + + + + + + + + + + + + true diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java new file mode 100644 index 0000000..ef3e773 --- /dev/null +++ b/src/main/java/module-info.java @@ -0,0 +1,6 @@ +module org.umlgraph.doclet { + exports org.umlgraph.doclet; + opens org.umlgraph.doclet; + + requires transitive jdk.javadoc; +} \ No newline at end of file diff --git a/src/main/java/org/umlgraph/doclet/ClassGraph.java b/src/main/java/org/umlgraph/doclet/ClassGraph.java index 83552ea..8acdc97 100644 --- a/src/main/java/org/umlgraph/doclet/ClassGraph.java +++ b/src/main/java/org/umlgraph/doclet/ClassGraph.java @@ -37,27 +37,40 @@ import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.ConstructorDoc; -import com.sun.javadoc.Doc; -import com.sun.javadoc.FieldDoc; -import com.sun.javadoc.MethodDoc; -import com.sun.javadoc.PackageDoc; -import com.sun.javadoc.Parameter; -import com.sun.javadoc.ParameterizedType; -import com.sun.javadoc.ProgramElementDoc; -import com.sun.javadoc.RootDoc; -import com.sun.javadoc.Tag; -import com.sun.javadoc.Type; -import com.sun.javadoc.TypeVariable; -import com.sun.javadoc.WildcardType; +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; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.NoType; +import javax.lang.model.type.TypeKind; +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.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.StandardLocation; + +import org.umlgraph.doclet.util.ElementUtil; +import org.umlgraph.doclet.util.TagUtil; + +import com.sun.source.util.DocTrees; + +import jdk.javadoc.doclet.DocletEnvironment; /** * Class graph generation engine @@ -71,6 +84,7 @@ import com.sun.javadoc.WildcardType; * @author Diomidis Spinellis */ class ClassGraph { + enum Align { LEFT, CENTER, RIGHT; @@ -81,20 +95,23 @@ class ClassGraph { } }; - protected Map classnames = new HashMap(); - protected Set rootClasses; - protected Map rootClassdocs = new HashMap(); + private final Elements elementUtils; + private final DocTrees docTrees; + private final Types types; + private final JavaFileManager fileManager; + protected Map classnames = new HashMap<>(); + protected Set rootClasses; + protected Map rootClassdocs = new HashMap<>(); protected OptionProvider optionProvider; protected PrintWriter w; - protected ClassDoc collectionClassDoc; - protected ClassDoc mapClassDoc; + protected TypeElement collectionClassDoc; + protected TypeElement mapClassDoc; protected String linePostfix; protected String linePrefix; // used only when generating context class diagrams in UMLDoc, to generate the - // proper - // relative links to other classes in the image map - protected final String contextPackageName; + // proper relative links to other classes in the image map + protected final Name contextPackageName; /** * Create a new ClassGraph. @@ -111,29 +128,36 @@ class ClassGraph { * @param contextDoc The current context for generating relative links, may * be a ClassDoc or a PackageDoc (used by UMLDoc) */ - public ClassGraph(RootDoc root, OptionProvider optionProvider, Doc contextDoc) { + public ClassGraph(DocletEnvironment root, OptionProvider optionProvider, Element contextDoc) { this.optionProvider = optionProvider; - this.collectionClassDoc = root.classNamed("java.util.Collection"); - this.mapClassDoc = root.classNamed("java.util.Map"); + this.elementUtils = root.getElementUtils(); + this.docTrees = root.getDocTrees(); + this.types = root.getTypeUtils(); + this.fileManager = root.getJavaFileManager(); + this.collectionClassDoc = elementUtils.getTypeElement("java.util.Collection"); + this.mapClassDoc = elementUtils.getTypeElement("java.util.Map"); // to gather the packages containing specified classes, loop thru them and // gather // package definitions. User root.specifiedPackages is not safe, since the user // may specify just a list of classes (human users usually don't, but automated // tools do) - rootClasses = new HashSet(); - for (ClassDoc classDoc : root.classes()) { - rootClasses.add(classDoc.qualifiedName()); - rootClassdocs.put(classDoc.qualifiedName(), classDoc); + rootClasses = new HashSet<>(); + for (Element classDoc : root.getIncludedElements()) { + if (classDoc instanceof TypeElement) { + rootClasses.add(((TypeElement) classDoc).getQualifiedName()); + rootClassdocs.put(((TypeElement) classDoc).getQualifiedName(), (TypeElement) classDoc); + } } // determine the context path, relative to the root - if (contextDoc instanceof ClassDoc) - contextPackageName = ((ClassDoc) contextDoc).containingPackage().name(); - else if (contextDoc instanceof PackageDoc) - contextPackageName = ((PackageDoc) contextDoc).name(); - else + if (contextDoc instanceof TypeElement) { + contextPackageName = ElementUtil.getPackageOf(elementUtils, contextDoc).getQualifiedName(); + } else if (contextDoc instanceof PackageElement) { + contextPackageName = ((PackageElement) contextDoc).getQualifiedName(); + } else { contextPackageName = null; // Not available + } Options opt = optionProvider.getGlobalOptions(); linePrefix = opt.compact ? "" : "\t"; @@ -141,18 +165,20 @@ class ClassGraph { } /** Return the class's name, possibly by stripping the leading path */ - private static String qualifiedName(Options opt, String r) { - if (opt.hideGenerics) - r = removeTemplate(r); + private String qualifiedName(Options opt, Name className) { + if (opt.hideGenerics) { + className = removeTemplate(elementUtils, className); + } // Fast path - nothing to do: - if (opt.showQualified && (opt.showQualifiedGenerics || r.indexOf('<') < 0)) - return r; - StringBuilder buf = new StringBuilder(r.length()); - qualifiedNameInner(opt, r, buf, 0, !opt.showQualified); + if (opt.showQualified && (opt.showQualifiedGenerics || className.toString().indexOf('<') < 0)) { + return className.toString(); + } + StringBuilder buf = new StringBuilder(className.length()); + qualifiedNameInner(opt, className, buf, 0, !opt.showQualified); return buf.toString(); } - private static int qualifiedNameInner(Options opt, String r, StringBuilder buf, int last, boolean strip) { + private static int qualifiedNameInner(Options opt, Name r, StringBuilder buf, int last, boolean strip) { strip = strip && last < r.length() && Character.isLowerCase(r.charAt(last)); for (int i = last; i < r.length(); i++) { char c = r.charAt(i); @@ -181,99 +207,85 @@ class ClassGraph { /** * Print the visibility adornment of element e prefixed by any stereotypes */ - private String visibility(Options opt, ProgramElementDoc e) { + private String visibility(Options opt, Element e) { return opt.showVisibility ? Visibility.get(e).symbol : " "; } /** Print the method parameter p */ - private String parameter(Options opt, Parameter p[]) { + private String parameter(Options opt, List params) { StringBuilder par = new StringBuilder(1000); - for (int i = 0; i < p.length; i++) { - par.append(p[i].name() + typeAnnotation(opt, p[i].type())); - if (i + 1 < p.length) + for (int i = 0; i < params.size(); i++) { + par.append(params.get(i).getSimpleName() + typeAnnotation(opt, params.get(i).asType())); + if (i + 1 < params.size()) { par.append(", "); + } } return par.toString(); } /** Print a a basic type t */ - private String type(Options opt, Type t, boolean generics) { + private String type(Options opt, TypeMirror t, boolean generics) { return ((generics ? opt.showQualifiedGenerics : opt.showQualified) ? // - t.qualifiedTypeName() : t.typeName()) // - + (opt.hideGenerics ? "" : typeParameters(opt, t.asParameterizedType())); + ElementUtil.getQualifiedName(types, t) : ElementUtil.getSimpleName(types, t)) // + + (opt.hideGenerics ? "" : typeParameters(opt, t instanceof DeclaredType ? (DeclaredType) t : null)); } /** Print the parameters of the parameterized type t */ - private String typeParameters(Options opt, ParameterizedType t) { - if (t == null) + private String typeParameters(Options opt, DeclaredType t) { + if (t == null || t.getTypeArguments() == null || t.getTypeArguments().isEmpty()) { return ""; + } StringBuffer tp = new StringBuffer(1000).append("<"); - Type args[] = t.typeArguments(); - for (int i = 0; i < args.length; i++) { - tp.append(type(opt, args[i], true)); - if (i != args.length - 1) + List args = t.getTypeArguments(); + for (int i = 0; i < args.size(); i++) { + tp.append(type(opt, args.get(i), true)); + if (i != args.size() - 1) { tp.append(", "); + } } return tp.append(">").toString(); } /** Annotate an field/argument with its type t */ - private String typeAnnotation(Options opt, Type t) { - if (t.typeName().equals("void")) + private String typeAnnotation(Options opt, TypeMirror t) { + if (t.getKind() == TypeKind.VOID) { return ""; - return " : " + type(opt, t, false) + t.dimension(); + } + return " : " + type(opt, t, false) + ElementUtil.dimensions(t); } /** Print the class's attributes fd */ - private void attributes(Options opt, FieldDoc fd[]) { - for (FieldDoc f : fd) { - if (hidden(f)) + private void attributes(Options opt, List fd) { + for (VariableElement f : fd) { + if (hidden(f)) { continue; + } stereotype(opt, f, Align.LEFT); - String att = visibility(opt, f) + f.name(); - if (opt.showType) - att += typeAnnotation(opt, f.type()); + String att = visibility(opt, f) + f.getSimpleName(); + if (opt.showType) { + att += typeAnnotation(opt, f.asType()); + } tableLine(Align.LEFT, att); tagvalue(opt, f); } } - /* - * The following two methods look similar, but can't be refactored into one, - * because their common interface, ExecutableMemberDoc, doesn't support - * returnType for ctors. - */ - - /** Print the class's constructors m */ - private boolean operations(Options opt, ConstructorDoc m[]) { - boolean printed = false; - for (ConstructorDoc cd : m) { - if (hidden(cd)) - continue; - stereotype(opt, cd, Align.LEFT); - String cs = visibility(opt, cd) + cd.name() // - + (opt.showType ? "(" + parameter(opt, cd.parameters()) + ")" : "()"); - tableLine(Align.LEFT, cs); - tagvalue(opt, cd); - printed = true; - } - return printed; - } - /** Print the class's operations m */ - private boolean operations(Options opt, MethodDoc m[]) { + private boolean operations(Options opt, List m) { boolean printed = false; - for (MethodDoc md : m) { - if (hidden(md)) + for (ExecutableElement md : m) { + if (hidden(md)) { continue; + } // Filter-out static initializer method - if (md.name().equals("") && md.isStatic() && md.isPackagePrivate()) + if (md.getSimpleName().toString().equals("") && md.getModifiers().contains(Modifier.STATIC) && Visibility.get(md) == Visibility.PACKAGE) { continue; + } + Name name = "".equals(md.getSimpleName().toString()) ? ElementUtil.containingTypeElement(md).getSimpleName() : md.getSimpleName(); stereotype(opt, md, Align.LEFT); - String op = visibility(opt, md) + md.name() + // - (opt.showType ? "(" + parameter(opt, md.parameters()) + ")" + typeAnnotation(opt, md.returnType()) - : "()"); - tableLine(Align.LEFT, (md.isAbstract() ? Font.ABSTRACT : Font.NORMAL).wrap(opt, op)); + String op = visibility(opt, md) + name + + (opt.showType ? "(" + parameter(opt, md.getParameters()) + ")" + typeAnnotation(opt, md.getReturnType()) : "()"); + tableLine(Align.LEFT, (md.getModifiers().contains(Modifier.ABSTRACT) ? Font.ABSTRACT : Font.NORMAL).wrap(opt, op)); printed = true; tagvalue(opt, md); @@ -284,12 +296,15 @@ class ClassGraph { /** Print the common class node's properties */ private void nodeProperties(Options opt) { Options def = opt.getGlobalOptions(); - if (opt.nodeFontName != def.nodeFontName) + if (opt.nodeFontName != def.nodeFontName) { w.print(",fontname=\"" + opt.nodeFontName + "\""); - if (opt.nodeFontColor != def.nodeFontColor) + } + if (opt.nodeFontColor != def.nodeFontColor) { w.print(",fontcolor=\"" + opt.nodeFontColor + "\""); - if (opt.nodeFontSize != def.nodeFontSize) + } + if (opt.nodeFontSize != def.nodeFontSize) { w.print(",fontsize=" + fmt(opt.nodeFontSize)); + } w.print(opt.shape.style); w.println("];"); } @@ -302,15 +317,16 @@ class ClassGraph { * @param prevterm the termination string for the previous element * @param term the termination character for each tagged value */ - private void tagvalue(Options opt, Doc c) { - Tag tags[] = c.tags("tagvalue"); - if (tags.length == 0) + private void tagvalue(Options opt, Element c) { + List tags = TagUtil.getTag(docTrees, c, "tagvalue"); + if (tags.isEmpty()) { return; + } - for (Tag tag : tags) { - String t[] = tokenize(tag.text()); + for (String tag : tags) { + String t[] = tokenize(tag); if (t.length != 2) { - System.err.println("@tagvalue expects two fields: " + tag.text()); + System.err.println("@tagvalue expects two fields: " + tag); continue; } tableLine(Align.RIGHT, Font.TAG.wrap(opt, "{" + t[0] + " = " + t[1] + "}")); @@ -318,14 +334,17 @@ class ClassGraph { } /** - * Return as a string the stereotypes associated with c terminated by the escape - * character term + * Return as a string the stereotypes associated with c terminated by the escape character term */ - private void stereotype(Options opt, Doc c, Align align) { - for (Tag tag : c.tags("stereotype")) { - String t[] = tokenize(tag.text()); + private void stereotype(Options opt, Element c, Align align) { + List tags = TagUtil.getTag(docTrees, c, "stereotype"); + if (tags.isEmpty()) { + return; + } + for (String tag : tags) { + String t[] = tokenize(tag); if (t.length != 1) { - System.err.println("@stereotype expects one field: " + tag.text()); + System.err.println("@stereotype expects one field: " + tag); continue; } tableLine(align, guilWrap(opt, t[0])); @@ -333,25 +352,27 @@ class ClassGraph { } /** Return true if c has a @hidden tag associated with it */ - private boolean hidden(ProgramElementDoc c) { - if (c.tags("hidden").length > 0 || c.tags("view").length > 0) + private boolean hidden(Element c) { + Map> tags = TagUtil.getTags(docTrees, c); + if (tags.get("hidden") != null || tags.get("view") != null) { return true; - Options opt = optionProvider.getOptionsFor(c instanceof ClassDoc ? (ClassDoc) c : c.containingClass()); - return opt.matchesHideExpression(c.toString()) // - || (opt.hidePrivateInner && c instanceof ClassDoc && c.isPrivate() - && ((ClassDoc) c).containingClass() != null); + } + Options opt = optionProvider.getOptionsFor(docTrees, c instanceof TypeElement ? (TypeElement) c : ElementUtil.containingTypeElement(c)); + return opt.matchesHideExpression(c.getSimpleName()) // + || (opt.hidePrivateInner && c instanceof TypeElement && c.getModifiers().contains(Modifier.PRIVATE) + && ((TypeElement) c).getEnclosingElement() != null); } - protected ClassInfo getClassInfo(ClassDoc cd, boolean create) { - return getClassInfo(cd, cd.toString(), create); + protected ClassInfo getClassInfo(TypeElement cd, boolean create) { + return getClassInfo(cd, cd.getQualifiedName(), create); } - protected ClassInfo getClassInfo(String className, boolean create) { + protected ClassInfo getClassInfo(Name className, boolean create) { return getClassInfo(null, className, create); } - protected ClassInfo getClassInfo(ClassDoc cd, String className, boolean create) { - className = removeTemplate(className); + protected ClassInfo getClassInfo(TypeElement cd, Name className, boolean create) { + className = removeTemplate(elementUtils, className); ClassInfo ci = classnames.get(className); if (ci == null && create) { boolean hidden = cd != null ? hidden(cd) @@ -366,8 +387,7 @@ class ClassGraph { * Return true if the class name is associated to an hidden class or matches a * hide expression */ - private boolean hidden(String className) { - className = removeTemplate(className); + private boolean hidden(CharSequence className) { ClassInfo ci = classnames.get(className); return ci != null ? ci.hidden : optionProvider.getOptionsFor(className).matchesHideExpression(className); } @@ -379,39 +399,42 @@ class ClassGraph { * RootDoc.classes(), this information is used to properly compute relative * links in diagrams for UMLDoc */ - public String printClass(ClassDoc c, boolean rootClass) { + public String printClass(TypeElement c, boolean rootClass) { ClassInfo ci = getClassInfo(c, true); if (ci.nodePrinted || ci.hidden) return ci.name; - Options opt = optionProvider.getOptionsFor(c); - if (c.isEnum() && !opt.showEnumerations) + Options opt = optionProvider.getOptionsFor(docTrees, c); + if (c.getKind() == ElementKind.ENUM && !opt.showEnumerations) { return ci.name; + } String className = c.toString(); // Associate classname's alias w.println(linePrefix + "// " + className); // Create label w.print(linePrefix + ci.name + " [label="); - boolean showMembers = (opt.showAttributes && c.fields().length > 0) - || (c.isEnum() && opt.showEnumConstants && c.enumConstants().length > 0) - || (opt.showOperations && c.methods().length > 0) - || (opt.showConstructors && c.constructors().length > 0); + boolean showMembers = (opt.showAttributes && !ElementUtil.getFields(c).isEmpty()) + || (c.getKind() == ElementKind.ENUM && opt.showEnumConstants && !ElementUtil.getEnumConstants(c).isEmpty()) + || (opt.showOperations && !ElementUtil.getMethods(c).isEmpty()) + || (opt.showConstructors && !ElementUtil.getConstructors(c).isEmpty()); final String url = classToUrl(c, rootClass); - externalTableStart(opt, c.qualifiedName(), url); + externalTableStart(opt, c.getQualifiedName(), url); firstInnerTableStart(opt); - if (c.isInterface()) + if (c.getKind() == ElementKind.INTERFACE) { tableLine(Align.CENTER, guilWrap(opt, "interface")); - if (c.isEnum()) + } + if (c.getKind() == ElementKind.ENUM) { tableLine(Align.CENTER, guilWrap(opt, "enumeration")); + } stereotype(opt, c, Align.CENTER); - Font font = c.isAbstract() && !c.isInterface() ? Font.CLASS_ABSTRACT : Font.CLASS; - String qualifiedName = qualifiedName(opt, className); + Font font = c.getModifiers().contains(Modifier.ABSTRACT) && c.getKind() != ElementKind.INTERFACE ? Font.CLASS_ABSTRACT : Font.CLASS; + String qualifiedName = qualifiedName(opt, c.getQualifiedName()); int idx = splitPackageClass(qualifiedName); - if (opt.showComment) - tableLine(Align.LEFT, Font.CLASS.wrap(opt, htmlNewline(escape(c.commentText())))); - else if (opt.postfixPackage && idx > 0 && idx < (qualifiedName.length() - 1)) { + if (opt.showComment) { + tableLine(Align.LEFT, Font.CLASS.wrap(opt, htmlNewline(escape(TagUtil.getComment(docTrees, c))))); + } else if (opt.postfixPackage && idx > 0 && idx < (qualifiedName.length() - 1)) { String packageName = qualifiedName.substring(0, idx); String cn = qualifiedName.substring(idx + 1); tableLine(Align.CENTER, font.wrap(opt, escape(cn))); @@ -430,45 +453,49 @@ class ClassGraph { if (showMembers) { if (opt.showAttributes) { innerTableStart(); - FieldDoc[] fields = c.fields(); + List fields = ElementUtil.getFields(c); // if there are no fields, print an empty line to generate proper HTML - if (fields.length == 0) + if (fields.size() == 0) { tableLine(Align.LEFT, ""); - else - attributes(opt, c.fields()); + } else { + attributes(opt, fields); + } innerTableEnd(); - } else if (!c.isEnum() && (opt.showConstructors || opt.showOperations)) { + } else if (c.getKind() != ElementKind.ENUM && (opt.showConstructors || opt.showOperations)) { // show an emtpy box if we don't show attributes but // we show operations innerTableStart(); tableLine(Align.LEFT, ""); innerTableEnd(); } - if (c.isEnum() && opt.showEnumConstants) { + if (c.getKind() == ElementKind.ENUM && opt.showEnumConstants) { innerTableStart(); - FieldDoc[] ecs = c.enumConstants(); + List ecs = ElementUtil.getEnumConstants(c); // if there are no constants, print an empty line to generate proper HTML - if (ecs.length == 0) { + if (ecs.size() == 0) { tableLine(Align.LEFT, ""); } else { - for (FieldDoc fd : c.enumConstants()) { - tableLine(Align.LEFT, fd.name()); + for (VariableElement fd : ecs) { + tableLine(Align.LEFT, fd.getSimpleName()); } } innerTableEnd(); } - if (!c.isEnum() && (opt.showConstructors || opt.showOperations)) { + if (c.getKind() != ElementKind.ENUM && (opt.showConstructors || opt.showOperations)) { innerTableStart(); boolean printedLines = false; - if (opt.showConstructors) - printedLines |= operations(opt, c.constructors()); - if (opt.showOperations) - printedLines |= operations(opt, c.methods()); + if (opt.showConstructors) { + printedLines |= operations(opt, ElementUtil.getConstructors(c)); + } + if (opt.showOperations) { + printedLines |= operations(opt, ElementUtil.getMethods(c)); + } - if (!printedLines) + if (!printedLines) { // if there are no operations nor constructors, // print an empty line to generate proper HTML tableLine(Align.LEFT, ""); + } innerTableEnd(); } @@ -480,13 +507,14 @@ class ClassGraph { // If needed, add a note for this node int ni = 0; - for (Tag t : c.tags("note")) { + List tags = TagUtil.getTag(docTrees, c, "note"); + for (String t : tags) { String noteName = "n" + ni + "c" + ci.name; w.print(linePrefix + "// Note annotation\n"); w.print(linePrefix + noteName + " [label="); - externalTableStart(UmlGraph.getCommentOptions(), c.qualifiedName(), url); + externalTableStart(UmlGraph.getCommentOptions(), c.getQualifiedName(), url); innerTableStart(); - tableLine(Align.LEFT, Font.CLASS.wrap(UmlGraph.getCommentOptions(), htmlNewline(escape(t.text())))); + tableLine(Align.LEFT, Font.CLASS.wrap(UmlGraph.getCommentOptions(), htmlNewline(escape(t)))); innerTableEnd(); externalTableEnd(); nodeProperties(UmlGraph.getCommentOptions()); @@ -505,25 +533,29 @@ class ClassGraph { * @param from the source class * @param edgetype the dot edge specification */ - private void allRelation(Options opt, RelationType rt, ClassDoc from) { + private void allRelation(Options opt, RelationType rt, TypeElement from) { String tagname = rt.lower; - for (Tag tag : from.tags(tagname)) { - String t[] = tokenize(tag.text()); // l-src label l-dst target + List tags = TagUtil.getTag(docTrees, from, tagname); + for (String tag : tags) { + String t[] = tokenize(tag); // l-src label l-dst target t = t.length == 1 ? new String[] { "-", "-", "-", t[0] } : t; // Shorthand if (t.length != 4) { System.err.println("Error in " + from + "\n" + tagname - + " expects four fields (l-src label l-dst target): " + tag.text()); + + " expects four fields (l-src label l-dst target): " + tag); return; } - ClassDoc to = from.findClass(t[3]); + TypeElement to = elementUtils.getTypeElement(t[3]); if (to != null) { - if (hidden(to)) + if (hidden(to)) { continue; + } relation(opt, rt, from, to, t[0], t[1], t[2]); } else { - if (hidden(t[3])) + Name t3 = elementUtils.getName(t[3]); + if (hidden(t3)) { continue; - relation(opt, rt, from, from.toString(), to, t[3], t[0], t[1], t[2]); + } + relation(opt, rt, from, from.getQualifiedName(), to, t3, t[0], t[1], t[2]); } } } @@ -536,7 +568,7 @@ class ClassGraph { * @param to the destination class (may be null) * @param toName the destination class's name */ - private void relation(Options opt, RelationType rt, ClassDoc from, String fromName, ClassDoc to, String toName, + private void relation(Options opt, RelationType rt, TypeElement from, Name fromName, TypeElement to, Name toName, String tailLabel, String label, String headLabel) { tailLabel = (tailLabel != null && !tailLabel.isEmpty()) ? ",taillabel=\"" + tailLabel + "\"" : ""; label = (label != null && !label.isEmpty()) ? ",label=\"" + guillemize(opt, label) + "\"" : ""; @@ -579,30 +611,43 @@ class ClassGraph { * @param from the source class * @param to the destination class */ - private void relation(Options opt, RelationType rt, ClassDoc from, ClassDoc to, String tailLabel, String label, + private void relation(Options opt, RelationType rt, TypeElement from, TypeElement to, String tailLabel, String label, String headLabel) { - relation(opt, rt, from, from.toString(), to, to.toString(), tailLabel, label, headLabel); + relation(opt, rt, from, from.getQualifiedName(), to, to.getQualifiedName(), tailLabel, label, headLabel); } /** Print a class's relations */ - public void printRelations(ClassDoc c) { - Options opt = optionProvider.getOptionsFor(c); - if (hidden(c) || c.name().equals("")) // avoid phantom classes, they may pop up when the source uses annotations + public void printRelations(TypeElement c) { + Options opt = optionProvider.getOptionsFor(docTrees, c); + if (hidden(c) || "".equals(c.getSimpleName().toString())) { // avoid phantom classes, they may pop up when the source uses annotations return; + } // Print generalization (through the Java superclass) - Type s = c.superclassType(); - ClassDoc sc = s != null && !s.qualifiedTypeName().equals(Object.class.getName()) ? s.asClassDoc() : null; - if (sc != null && !c.isEnum() && !hidden(sc)) + TypeMirror clazz = c.getSuperclass(); + Element scd; + if (clazz == null || clazz.getKind() == TypeKind.NONE || !(clazz instanceof DeclaredType)) { + scd = null; + } else { + scd = ((DeclaredType) clazz).asElement(); + } + TypeElement s = scd instanceof TypeElement ? (TypeElement) scd : null; + TypeElement sc = s != null && !s.getQualifiedName().toString().equals(Object.class.getName()) ? s : null; + if (sc != null && c.getKind() != ElementKind.ENUM && !hidden(sc)) { relation(opt, RelationType.EXTENDS, c, sc, null, null, null); + } // Print generalizations (through @extends tags) - for (Tag tag : c.tags("extends")) - if (!hidden(tag.text())) - relation(opt, RelationType.EXTENDS, c, c.findClass(tag.text()), null, null, null); + List tags = TagUtil.getTag(docTrees, c, "extends"); + for (String tag : tags) { + if (!hidden(tag)) { + relation(opt, RelationType.EXTENDS, c, elementUtils.getTypeElement(tag), null, null, null); + } + } // Print realizations (Java interfaces) - for (Type iface : c.interfaceTypes()) { - ClassDoc ic = iface.asClassDoc(); - if (!hidden(ic)) + for (TypeMirror iface : c.getInterfaces()) { + TypeElement ic = ElementUtil.getTypeElement(iface); + if (!hidden(ic)) { relation(opt, RelationType.IMPLEMENTS, c, ic, null, null, null); + } } // Print other associations allRelation(opt, RelationType.COMPOSED, c); @@ -615,13 +660,14 @@ class ClassGraph { } /** Print classes that were parts of relationships, but not parsed by javadoc */ - public void printExtraClasses(RootDoc root) { - Set names = new HashSet(classnames.keySet()); - for (String className : names) { + public void printExtraClasses(DocletEnvironment root) { + Set names = new HashSet<>(classnames.keySet()); + for (Name className : names) { ClassInfo info = getClassInfo(className, true); - if (info.nodePrinted) + if (info.nodePrinted) { continue; - ClassDoc c = root.classNamed(className); + } + TypeElement c = elementUtils.getTypeElement(className); if (c != null) { printClass(c, false); continue; @@ -660,26 +706,31 @@ class ClassGraph { * * @param classes */ - public void printInferredRelations(ClassDoc c) { + public void printInferredRelations(TypeElement c) { // check if the source is excluded from inference - if (hidden(c)) + if (hidden(c)) { return; + } - Options opt = optionProvider.getOptionsFor(c); + Options opt = optionProvider.getOptionsFor(docTrees, c); - for (FieldDoc field : c.fields(false)) { - if (hidden(field)) + for (VariableElement field : ElementUtil.getFields(c)) { + if (hidden(field)) { continue; + } // skip statics - if (field.isStatic()) + if (field.getModifiers().contains(Modifier.STATIC)) { continue; + } // skip primitives FieldRelationInfo fri = getFieldRelationInfo(field); - if (fri == null) + if (fri == null) { continue; + } // check if the destination is excluded from inference - if (hidden(fri.cd)) + if (hidden(fri.cd)) { continue; + } // if source and dest are not already linked, add a dependency RelationPattern rp = getClassInfo(c, true).getRelation(fri.cd.toString()); @@ -695,9 +746,18 @@ class ClassGraph { * deprecation warning, which is output, because the imported classed are an * implementation detail. */ - @SuppressWarnings("deprecation") - ClassDoc[] importedClasses(ClassDoc c) { - return c.importedClasses(); + List importedClasses(TypeElement c) { + JavaFileObject source; + try { + source = fileManager.getJavaFileForInput(StandardLocation.SOURCE_PATH, c.getQualifiedName().toString(), JavaFileObject.Kind.SOURCE); + } catch (IOException e) { + source = null; + } + + if (source == null) { + return Collections.emptyList(); + } + return Collections.emptyList(); // c.importedClasses(); } /** @@ -707,58 +767,65 @@ class ClassGraph { * * @param classes */ - public void printInferredDependencies(ClassDoc c) { - if (hidden(c)) + public void printInferredDependencies(TypeElement c) { + if (hidden(c)) { return; + } - Options opt = optionProvider.getOptionsFor(c); - Set types = new HashSet(); + Options opt = optionProvider.getOptionsFor(docTrees, c); + Set types = new HashSet<>(); + // harvest method return and parameter types - for (MethodDoc method : filterByVisibility(c.methods(false), opt.inferDependencyVisibility)) { - types.add(method.returnType()); - for (Parameter parameter : method.parameters()) { - types.add(parameter.type()); + for (ExecutableElement method : filterByVisibility(ElementUtil.getMethods(c), opt.inferDependencyVisibility)) { + types.add(method.getReturnType()); + for (VariableElement parameter : method.getParameters()) { + types.add(parameter.asType()); } } // and the field types if (!opt.inferRelationships) { - for (FieldDoc field : filterByVisibility(c.fields(false), opt.inferDependencyVisibility)) { - types.add(field.type()); + for (VariableElement field : filterByVisibility(ElementUtil.getFields(c), opt.inferDependencyVisibility)) { + types.add(field.asType()); } } // see if there are some type parameters - if (c.asParameterizedType() != null) { - ParameterizedType pt = c.asParameterizedType(); - types.addAll(Arrays.asList(pt.typeArguments())); + if (c.asType() instanceof DeclaredType) { + DeclaredType pt = (DeclaredType) c.asType(); + types.addAll(pt.getTypeArguments()); } // see if type parameters extend something - for (TypeVariable tv : c.typeParameters()) { - if (tv.bounds().length > 0) - types.addAll(Arrays.asList(tv.bounds())); + for (TypeParameterElement tv : c.getTypeParameters()) { + if (tv.getBounds().size() > 0) { + types.addAll(tv.getBounds()); + } } // and finally check for explicitly imported classes (this // assumes there are no unused imports...) - if (opt.useImports) - types.addAll(Arrays.asList(importedClasses(c))); + if (opt.useImports) { + types.addAll(importedClasses(c)); + } // compute dependencies - for (Type type : types) { + for (TypeMirror type : types) { // skip primitives and type variables, as well as dependencies // on the source class - if (type.isPrimitive() || type instanceof WildcardType || type instanceof TypeVariable - || c.toString().equals(type.asClassDoc().toString())) + if (type.getKind().isPrimitive() || type instanceof NoType || type instanceof WildcardType || type instanceof TypeVariable + || c.toString().equals(ElementUtil.getTypeElement(type).toString())) { continue; + } // check if the destination is excluded from inference - ClassDoc fc = type.asClassDoc(); - if (hidden(fc)) + TypeElement fc = ElementUtil.getTypeElement(type); + if (hidden(fc)) { continue; + } // check if source and destination are in the same package and if we are allowed // to infer dependencies between classes in the same package - if (!opt.inferDepInPackage && c.containingPackage().equals(fc.containingPackage())) + if (!opt.inferDepInPackage && ElementUtil.getPackageOf(elementUtils, c).equals(ElementUtil.getPackageOf(elementUtils, fc))) { continue; + } // if source and dest are not already linked, add a dependency RelationPattern rp = getClassInfo(c, true).getRelation(fc.toString()); @@ -773,95 +840,97 @@ class ClassGraph { * Returns all program element docs that have a visibility greater or equal than * the specified level */ - private List filterByVisibility(T[] docs, Visibility visibility) { - if (visibility == Visibility.PRIVATE) - return Arrays.asList(docs); + private List filterByVisibility(List docs, Visibility visibility) { + if (visibility == Visibility.PRIVATE) { + return docs; + } - List filtered = new ArrayList(); + List filtered = new ArrayList<>(); for (T doc : docs) { - if (Visibility.get(doc).compareTo(visibility) > 0) + if (Visibility.get(doc).compareTo(visibility) > 0) { filtered.add(doc); + } } return filtered; } - private FieldRelationInfo getFieldRelationInfo(FieldDoc field) { - Type type = field.type(); - if (type.isPrimitive() || type instanceof WildcardType || type instanceof TypeVariable) + private FieldRelationInfo getFieldRelationInfo(VariableElement field) { + TypeMirror type = field.asType(); + if (type.getKind().isPrimitive() || type.getKind() == TypeKind.WILDCARD || type.getKind() == TypeKind.TYPEVAR) { return null; - - if (type.dimension().endsWith("[]")) { - return new FieldRelationInfo(type.asClassDoc(), true); } - Options opt = optionProvider.getOptionsFor(type.asClassDoc()); - if (opt.matchesCollPackageExpression(type.qualifiedTypeName())) { - Type[] argTypes = getInterfaceTypeArguments(collectionClassDoc, type); - if (argTypes != null && argTypes.length == 1 && !argTypes[0].isPrimitive()) - return new FieldRelationInfo(argTypes[0].asClassDoc(), true); + if (ElementUtil.dimensions(type).endsWith("[]")) { + return new FieldRelationInfo(ElementUtil.getTypeElement(type), true); + } + + Options opt = optionProvider.getOptionsFor(docTrees, ElementUtil.getTypeElement(type)); + if (opt.matchesCollPackageExpression(ElementUtil.getQualifiedName(types, type))) { + List argTypes = getInterfaceTypeArguments(collectionClassDoc, type); + if (argTypes != null && argTypes.size() == 1 && !argTypes.get(0).getKind().isPrimitive()) { + return new FieldRelationInfo(ElementUtil.getTypeElement(argTypes.get(0)), true); + } argTypes = getInterfaceTypeArguments(mapClassDoc, type); - if (argTypes != null && argTypes.length == 2 && !argTypes[1].isPrimitive()) - return new FieldRelationInfo(argTypes[1].asClassDoc(), true); + if (argTypes != null && argTypes.size() == 2 && !argTypes.get(1).getKind().isPrimitive()) { + return new FieldRelationInfo(ElementUtil.getTypeElement(argTypes.get(1)), true); + } } - return new FieldRelationInfo(type.asClassDoc(), false); + return new FieldRelationInfo(ElementUtil.getTypeElement(type), false); } - private Type[] getInterfaceTypeArguments(ClassDoc iface, Type t) { - if (t instanceof ParameterizedType) { - ParameterizedType pt = (ParameterizedType) t; - if (iface != null && iface.equals(t.asClassDoc())) { - return pt.typeArguments(); + private List getInterfaceTypeArguments(TypeElement iface, TypeMirror t) { + if (t instanceof DeclaredType) { + DeclaredType pt = (DeclaredType) t; + if (iface != null && iface.equals(pt.asElement())) { + return pt.getTypeArguments(); } else { - for (Type pti : pt.interfaceTypes()) { - Type[] result = getInterfaceTypeArguments(iface, pti); - if (result != null) + for (TypeMirror pti : ElementUtil.getInterfacesTypes(iface)) { + List result = getInterfaceTypeArguments(iface, pti); + if (result != null) { return result; + } + } + if (ElementUtil.getSuperclassType(pt) != null) { + return getInterfaceTypeArguments(iface, ElementUtil.getSuperclassType(pt)); } - if (pt.superclassType() != null) - return getInterfaceTypeArguments(iface, pt.superclassType()); } - } else if (t instanceof ClassDoc) { - ClassDoc cd = (ClassDoc) t; - for (Type pti : cd.interfaceTypes()) { - Type[] result = getInterfaceTypeArguments(iface, pti); - if (result != null) - return result; - } - if (cd.superclassType() != null) - return getInterfaceTypeArguments(iface, cd.superclassType()); } return null; } /** Convert the class name into a corresponding URL */ - public String classToUrl(ClassDoc cd, boolean rootClass) { + public String classToUrl(TypeElement cd, boolean rootClass) { // building relative path for context and package diagrams - if (contextPackageName != null && rootClass) - return buildRelativePathFromClassNames(contextPackageName, cd.containingPackage().name()) + cd.name() - + ".html"; - return classToUrl(cd.qualifiedName()); + if (contextPackageName != null && rootClass) { + return buildRelativePathFromClassNames( + contextPackageName, ElementUtil.getPackageOf(elementUtils, cd).getQualifiedName().toString()) + cd.getSimpleName() + ".html"; + } + return classToUrl(cd.getQualifiedName()); } /** Convert the class name into a corresponding URL */ - public String classToUrl(String className) { - ClassDoc classDoc = rootClassdocs.get(className); + public String classToUrl(Name className) { + TypeElement classDoc = rootClassdocs.get(className); if (classDoc != null) { String docRoot = optionProvider.getGlobalOptions().apiDocRoot; - if (docRoot == null) + if (docRoot == null) { return null; + } return new StringBuilder(docRoot.length() + className.length() + 10).append(docRoot) // - .append(classDoc.containingPackage().name().replace('.', '/')) // - .append('/').append(classDoc.name()).append(".html").toString(); + .append(ElementUtil.getPackageOf(elementUtils, classDoc).getQualifiedName().toString().replace('.', '/')) // + .append('/').append(classDoc.getSimpleName()).append(".html").toString(); } String docRoot = optionProvider.getGlobalOptions().getApiDocRoot(className); - if (docRoot == null) + if (docRoot == null) { return null; + } int split = splitPackageClass(className); StringBuilder buf = new StringBuilder(docRoot.length() + className.length() + 10).append(docRoot); - if (split > 0) // Avoid -1, and the extra slash then. - buf.append(className.substring(0, split).replace('.', '/')).append('/'); + if (split > 0) { // Avoid -1, and the extra slash then. + buf.append(className.toString().substring(0, split).replace('.', '/')).append('/'); + } return buf.append(className, Math.min(split + 1, className.length()), className.length()) // .append(".html").toString(); } @@ -875,16 +944,17 @@ class ClassGraph { Options opt = optionProvider.getGlobalOptions(); OutputStream os; - if (opt.outputFileName.equals("-")) + if (opt.outputFileName.equals("-")) { os = System.out; - else { + } else { // prepare output file. Use the output file name as a full path unless the // output // directory is specified File file = new File(opt.outputDirectory, opt.outputFileName); // make sure the output directory are there, otherwise create them - if (file.getParentFile() != null && !file.getParentFile().exists()) + if (file.getParentFile() != null && !file.getParentFile().exists()) { file.getParentFile().mkdirs(); + } os = new FileOutputStream(file); } @@ -900,10 +970,12 @@ class ClassGraph { w.println(linePrefix + "nodesep=" + opt.nodeSep + ";"); w.println(linePrefix + "ranksep=" + opt.rankSep + ";"); - if (opt.horizontal) + if (opt.horizontal) { w.println(linePrefix + "rankdir=LR;"); - if (opt.bgColor != null) + } + if (opt.bgColor != null) { w.println(linePrefix + "bgcolor=\"" + opt.bgColor + "\";\n"); + } } /** Dot epilogue */ @@ -913,7 +985,7 @@ class ClassGraph { w.close(); } - private void externalTableStart(Options opt, String name, String url) { + private void externalTableStart(Options opt, Name name, String url) { String bgcolor = opt.nodeFillColor == null ? "" : (" bgcolor=\"" + opt.nodeFillColor + "\""); String href = url == null ? "" : (" href=\"" + url + "\" target=\"_parent\""); w.print("<" + opt.shape.extraColumn() + "" + linePostfix); } - private void tableLine(Align align, String text) { + private void tableLine(Align align, CharSequence text) { w.print(linePrefix + linePrefix // + "
" // + text // MAY contain markup! @@ -956,10 +1028,10 @@ class ClassGraph { } private static class FieldRelationInfo { - ClassDoc cd; + TypeElement cd; boolean multiple; - public FieldRelationInfo(ClassDoc cd, boolean multiple) { + public FieldRelationInfo(TypeElement cd, boolean multiple) { this.cd = cd; this.multiple = multiple; } diff --git a/src/main/java/org/umlgraph/doclet/ClassInfo.java b/src/main/java/org/umlgraph/doclet/ClassInfo.java index ccb67e0..5939a0c 100644 --- a/src/main/java/org/umlgraph/doclet/ClassInfo.java +++ b/src/main/java/org/umlgraph/doclet/ClassInfo.java @@ -22,6 +22,8 @@ package org.umlgraph.doclet; import java.util.HashMap; import java.util.Map; +import javax.lang.model.element.Name; + /** * Class's dot-compatible alias name (for fully qualified class names) and * printed information @@ -42,7 +44,7 @@ class ClassInfo { * classes linked with a bi-directional relation , and the ones referred by a * directed relation */ - Map relatedClasses = new HashMap(); + Map relatedClasses = new HashMap<>(); ClassInfo(boolean h) { hidden = h; @@ -50,7 +52,7 @@ class ClassInfo { classNumber++; } - public void addRelation(String dest, RelationType rt, RelationDirection d) { + public void addRelation(Name dest, RelationType rt, RelationDirection d) { RelationPattern ri = relatedClasses.get(dest); if (ri == null) { ri = new RelationPattern(RelationDirection.NONE); @@ -59,7 +61,7 @@ class ClassInfo { ri.addRelation(rt, d); } - public RelationPattern getRelation(String dest) { + public RelationPattern getRelation(CharSequence dest) { return relatedClasses.get(dest); } diff --git a/src/main/java/org/umlgraph/doclet/ClassMatcher.java b/src/main/java/org/umlgraph/doclet/ClassMatcher.java index d89df36..294fcfa 100644 --- a/src/main/java/org/umlgraph/doclet/ClassMatcher.java +++ b/src/main/java/org/umlgraph/doclet/ClassMatcher.java @@ -17,7 +17,7 @@ package org.umlgraph.doclet; -import com.sun.javadoc.ClassDoc; +import javax.lang.model.element.TypeElement; /** * A ClassMatcher is used to check if a class definition matches a specific @@ -29,10 +29,10 @@ public interface ClassMatcher { /** * Returns the options for the specified class. */ - public boolean matches(ClassDoc cd); + public boolean matches(TypeElement cd); /** * Returns the options for the specified class. */ - public boolean matches(String name); + public boolean matches(CharSequence name); } diff --git a/src/main/java/org/umlgraph/doclet/ContextMatcher.java b/src/main/java/org/umlgraph/doclet/ContextMatcher.java index 626fab0..eafdf1b 100644 --- a/src/main/java/org/umlgraph/doclet/ContextMatcher.java +++ b/src/main/java/org/umlgraph/doclet/ContextMatcher.java @@ -26,8 +26,10 @@ import java.util.List; import java.util.Set; import java.util.regex.Pattern; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.RootDoc; +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; /** * Matches classes that are directly connected to one of the classes matched by @@ -46,11 +48,11 @@ import com.sun.javadoc.RootDoc; public class ContextMatcher implements ClassMatcher { ClassGraphHack cg; Pattern pattern; - List matched; - Set visited = new HashSet(); + List matched; + Set visited = new HashSet<>(); /** The options will be used to decide on inference */ Options opt; - RootDoc root; + DocletEnvironment root; boolean keepParentHide; /** @@ -64,7 +66,7 @@ public class ContextMatcher implements ClassMatcher { * the context * @throws IOException */ - public ContextMatcher(RootDoc root, Pattern pattern, Options options, boolean keepParentHide) throws IOException { + public ContextMatcher(DocletEnvironment root, Pattern pattern, Options options, boolean keepParentHide) throws IOException { this.pattern = pattern; this.root = root; this.keepParentHide = keepParentHide; @@ -89,11 +91,11 @@ public class ContextMatcher implements ClassMatcher { // build up the classgraph printing the relations for all of the // classes that make up the "center" of this context this.pattern = pattern; - matched = new ArrayList(); - for (ClassDoc cd : root.classes()) { - if (pattern.matcher(cd.toString()).matches()) { - matched.add(cd); - addToGraph(cd); + matched = new ArrayList<>(); + for (Element cd : root.getIncludedElements()) { + if (cd instanceof TypeElement && pattern.matcher(cd.toString()).matches()) { + matched.add((TypeElement) cd); + addToGraph((TypeElement) cd); } } } @@ -105,31 +107,36 @@ public class ContextMatcher implements ClassMatcher { * * @param cd */ - private void addToGraph(ClassDoc cd) { + private void addToGraph(TypeElement cd) { // avoid adding twice the same class, but don't rely on cg.getClassInfo // since there are other ways to add a classInfor than printing the class - if (visited.contains(cd.toString())) + if (visited.contains(cd.toString())) { return; + } visited.add(cd.toString()); cg.printClass(cd, false); cg.printRelations(cd); - if (opt.inferRelationships) + if (opt.inferRelationships) { cg.printInferredRelations(cd); - if (opt.inferDependencies) + } + if (opt.inferDependencies) { cg.printInferredDependencies(cd); + } } /** - * @see org.umlgraph.doclet.ClassMatcher#matches(com.sun.javadoc.ClassDoc) + * @see org.umlgraph.doclet.ClassMatcher#matches(TypeElement) */ - public boolean matches(ClassDoc cd) { - if (keepParentHide && opt.matchesHideExpression(cd.toString())) + public boolean matches(TypeElement cd) { + if (keepParentHide && opt.matchesHideExpression(cd.getQualifiedName())) { return false; + } // if the class is matched, it's in by default. - if (matched.contains(cd)) + if (matched.contains(cd)) { return true; + } // otherwise, add the class to the graph and see if it's associated // with any of the matched classes using the classgraph hack @@ -138,16 +145,18 @@ public class ContextMatcher implements ClassMatcher { } /** - * @see org.umlgraph.doclet.ClassMatcher#matches(java.lang.String) + * @see org.umlgraph.doclet.ClassMatcher#matches(CharSequence) */ - public boolean matches(String name) { - if (pattern.matcher(name).matches()) + public boolean matches(CharSequence name) { + if (pattern.matcher(name).matches()) { return true; + } - for (ClassDoc mcd : matched) { + for (TypeElement mcd : matched) { RelationPattern rp = cg.getClassInfo(mcd, true).getRelation(name); - if (rp != null && opt.contextRelationPattern.matchesOne(rp)) + if (rp != null && opt.contextRelationPattern.matchesOne(rp)) { return true; + } } return false; } @@ -162,7 +171,7 @@ public class ContextMatcher implements ClassMatcher { */ private static class ClassGraphHack extends ClassGraph { - public ClassGraphHack(RootDoc root, OptionProvider optionProvider) throws IOException { + public ClassGraphHack(DocletEnvironment root, OptionProvider optionProvider) throws IOException { super(root, optionProvider, null); prologue(); } diff --git a/src/main/java/org/umlgraph/doclet/ContextView.java b/src/main/java/org/umlgraph/doclet/ContextView.java index 48e9737..5202315 100755 --- a/src/main/java/org/umlgraph/doclet/ContextView.java +++ b/src/main/java/org/umlgraph/doclet/ContextView.java @@ -3,8 +3,14 @@ package org.umlgraph.doclet; import java.io.IOException; import java.util.regex.Pattern; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.RootDoc; +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.TypeElement; + +import org.umlgraph.doclet.util.ElementUtil; + +import com.sun.source.util.DocTrees; /** * A view designed for UMLDoc, filters out everything that it's not directly @@ -20,7 +26,8 @@ import com.sun.javadoc.RootDoc; */ public class ContextView implements OptionProvider { - private ClassDoc cd; + private TypeElement cd; + private DocletEnvironment root; private ContextMatcher matcher; private Options globalOptions; private Options myGlobalOptions; @@ -29,9 +36,12 @@ public class ContextView implements OptionProvider { private Options packageOptions; private static final String[] HIDE_OPTIONS = new String[] { "hide" }; - public ContextView(String outputFolder, ClassDoc cd, RootDoc root, Options parent) throws IOException { + public ContextView(String outputFolder, TypeElement cd, DocletEnvironment root, Options parent) throws IOException { this.cd = cd; - String outputPath = cd.containingPackage().name().replace('.', '/') + "/" + cd.name() + ".dot"; + this.root = root; + ModuleElement md = ElementUtil.getModuleOf(root, cd); + String pathPrefix = Runtime.version().major() > 10 && md != null ? md.getQualifiedName().toString() + "/" : ""; + String outputPath = pathPrefix + ElementUtil.getPackageOf(root, cd).getQualifiedName().toString().replace('.', '/') + "/" + cd.getSimpleName() + ".dot"; // setup options statically, so that we won't need to change them so // often @@ -51,13 +61,15 @@ public class ContextView implements OptionProvider { this.centerOptions.nodeFillColor = "lemonChiffon"; this.centerOptions.showQualified = false; - this.matcher = new ContextMatcher(root, Pattern.compile(Pattern.quote(cd.toString())), myGlobalOptions, true); + this.matcher = new ContextMatcher(root, Pattern.compile(Pattern.quote(cd.getQualifiedName().toString())), myGlobalOptions, true); } - public void setContextCenter(ClassDoc contextCenter) { + public void setContextCenter(TypeElement contextCenter) { this.cd = contextCenter; - String outputPath = cd.containingPackage().name().replace('.', '/') + "/" + cd.name() + ".dot"; + ModuleElement md = ElementUtil.getModuleOf(root, contextCenter); + String pathPrefix = Runtime.version().major() > 10 && md != null ? md.getQualifiedName().toString() + "/" : ""; + String outputPath = pathPrefix + ElementUtil.getPackageOf(root, cd).getQualifiedName().toString().replace('.', '/') + "/" + cd.getSimpleName() + ".dot"; this.myGlobalOptions.setOption(new String[] { "output", outputPath }); matcher.setContextCenter(Pattern.compile(Pattern.quote(cd.toString()))); } @@ -70,14 +82,14 @@ public class ContextView implements OptionProvider { return myGlobalOptions; } - public Options getOptionsFor(ClassDoc cd) { + public Options getOptionsFor(DocTrees dt, TypeElement cd) { Options opt; - if (globalOptions.matchesHideExpression(cd.qualifiedName()) - || !(matcher.matches(cd) || globalOptions.matchesIncludeExpression(cd.qualifiedName()))) { + if (globalOptions.matchesHideExpression(cd.getQualifiedName()) + || !(matcher.matches(cd) || globalOptions.matchesIncludeExpression(cd.getQualifiedName()))) { opt = hideOptions; } else if (cd.equals(this.cd)) { opt = centerOptions; - } else if (cd.containingPackage().equals(this.cd.containingPackage())) { + } else if (root.getElementUtils().getPackageOf(cd).equals(root.getElementUtils().getPackageOf(this.cd))) { opt = packageOptions; } else { opt = globalOptions; @@ -87,31 +99,35 @@ public class ContextView implements OptionProvider { return optionClone; } - public Options getOptionsFor(String name) { + public Options getOptionsFor(CharSequence name) { Options opt; - if (!matcher.matches(name)) + if (!matcher.matches(name)) { opt = hideOptions; - else if (name.equals(cd.name())) + } else if (name.equals(cd.getQualifiedName())) { opt = centerOptions; - else + } else { opt = globalOptions; + } Options optionClone = (Options) opt.clone(); overrideForClass(optionClone, name); return optionClone; } - public void overrideForClass(Options opt, ClassDoc cd) { - opt.setOptions(cd); - if (opt.matchesHideExpression(cd.qualifiedName()) - || !(matcher.matches(cd) || opt.matchesIncludeExpression(cd.qualifiedName()))) + public void overrideForClass(Options opt, TypeElement cd) { + opt.setOptions(root.getDocTrees(), cd); + if (opt.matchesHideExpression(cd.getQualifiedName()) + || !(matcher.matches(cd) || opt.matchesIncludeExpression(cd.getQualifiedName()))) { opt.setOption(HIDE_OPTIONS); - if (cd.equals(this.cd)) + } + if (cd.equals(this.cd)) { opt.nodeFillColor = "lemonChiffon"; + } } - public void overrideForClass(Options opt, String className) { - if (!(matcher.matches(className) || opt.matchesIncludeExpression(className))) + public void overrideForClass(Options opt, CharSequence className) { + if (!(matcher.matches(className) || opt.matchesIncludeExpression(className))) { opt.setOption(HIDE_OPTIONS); + } } } diff --git a/src/main/java/org/umlgraph/doclet/Font.java b/src/main/java/org/umlgraph/doclet/Font.java index f9bcc0d..0e2b0a7 100644 --- a/src/main/java/org/umlgraph/doclet/Font.java +++ b/src/main/java/org/umlgraph/doclet/Font.java @@ -4,7 +4,7 @@ package org.umlgraph.doclet; * Class to represent a font for graphviz. *

* This is a fairly complicated model, because it is rather an API into graphviz - * formatting strings rather than a standalone thing.

Some fonts (edge, node, * abstract) are set on the top level elements, whereas others (class name, tag, * package) are inserted as {@code } tags, and these can be omitted if not * set. Inheritance of properties then happens in graphviz. diff --git a/src/main/java/org/umlgraph/doclet/InterfaceMatcher.java b/src/main/java/org/umlgraph/doclet/InterfaceMatcher.java index e39518d..a4a6f13 100644 --- a/src/main/java/org/umlgraph/doclet/InterfaceMatcher.java +++ b/src/main/java/org/umlgraph/doclet/InterfaceMatcher.java @@ -2,8 +2,13 @@ package org.umlgraph.doclet; import java.util.regex.Pattern; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.RootDoc; +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.TypeMirror; + +import org.umlgraph.doclet.util.ElementUtil; /** * Matches every class that implements (directly or indirectly) an interfaces @@ -11,31 +16,36 @@ import com.sun.javadoc.RootDoc; */ public class InterfaceMatcher implements ClassMatcher { - protected RootDoc root; + protected DocletEnvironment root; protected Pattern pattern; - public InterfaceMatcher(RootDoc root, Pattern pattern) { + public InterfaceMatcher(DocletEnvironment root, Pattern pattern) { this.root = root; this.pattern = pattern; } - public boolean matches(ClassDoc cd) { + public boolean matches(TypeElement cd) { // if it's the interface we're looking for, match - if (cd.isInterface() && pattern.matcher(cd.toString()).matches()) + if (cd.getKind() == ElementKind.INTERFACE && pattern.matcher(cd.toString()).matches()) { return true; + } // for each interface, recurse, since classes and interfaces // are treated the same in the doclet API - for (ClassDoc iface : cd.interfaces()) - if (matches(iface)) + for (TypeMirror type : cd.getInterfaces()) { + TypeElement iType = ElementUtil.getTypeElement(type); + if (iType != null && matches(iType)) { return true; + } + } - // recurse on supeclass, if available - return cd.superclass() == null ? false : matches(cd.superclass()); + // recurse on superclass, if available + TypeElement scd = ElementUtil.getTypeElement(cd.getSuperclass()); + return scd == null ? false : matches(scd); } - public boolean matches(String name) { - ClassDoc cd = root.classNamed(name); + public boolean matches(CharSequence name) { + TypeElement cd = root.getElementUtils().getTypeElement(name); return cd == null ? false : matches(cd); } diff --git a/src/main/java/org/umlgraph/doclet/Option.java b/src/main/java/org/umlgraph/doclet/Option.java new file mode 100644 index 0000000..e419601 --- /dev/null +++ b/src/main/java/org/umlgraph/doclet/Option.java @@ -0,0 +1,53 @@ +package org.umlgraph.doclet; + +import java.util.List; + +import jdk.javadoc.doclet.Doclet; + +/** + * A base class for declaring options. Subtypes for specific options should + * implement the {@link #process(String,List) process} method to handle + * instances of the option found on the command line. + */ +public abstract class Option implements Doclet.Option { + private final String name; + private final int argumentCount; + private final String description; + private final String parameters; + + public Option(String name, boolean hasArg, String description, String parameters) { + this(name, hasArg ? 1 : 0, description, parameters); + } + + public Option(String name, int argumentCount, String description, String parameters) { + this.name = name; + this.argumentCount = argumentCount; + this.description = description; + this.parameters = parameters; + } + + @Override + public int getArgumentCount() { + return argumentCount; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public Kind getKind() { + return Kind.STANDARD; + } + + @Override + public List getNames() { + return List.of(name); + } + + @Override + public String getParameters() { + return argumentCount == 0 ? "" : parameters; + } +} \ No newline at end of file diff --git a/src/main/java/org/umlgraph/doclet/OptionProvider.java b/src/main/java/org/umlgraph/doclet/OptionProvider.java index 151d2b7..e3f8e89 100644 --- a/src/main/java/org/umlgraph/doclet/OptionProvider.java +++ b/src/main/java/org/umlgraph/doclet/OptionProvider.java @@ -17,22 +17,23 @@ package org.umlgraph.doclet; -import com.sun.javadoc.ClassDoc; +import javax.lang.model.element.TypeElement; + +import com.sun.source.util.DocTrees; /** - * A factory class that builds Options object for general use or for a specific - * class + * A factory class that builds Options object for general use or for a specific class */ public interface OptionProvider { /** * Returns the options for the specified class. */ - public Options getOptionsFor(ClassDoc cd); + public Options getOptionsFor(DocTrees dt, TypeElement cd); /** * Returns the options for the specified class. */ - public Options getOptionsFor(String name); + public Options getOptionsFor(CharSequence name); /** * Returns the global options (the class independent definition) @@ -42,12 +43,12 @@ public interface OptionProvider { /** * Gets a base Options and applies the overrides for the specified class */ - public void overrideForClass(Options opt, ClassDoc cd); + public void overrideForClass(Options opt, TypeElement cd); /** * Gets a base Options and applies the overrides for the specified class */ - public void overrideForClass(Options opt, String className); + public void overrideForClass(Options opt, CharSequence className); /** * Returns user displayable name for this option provider. diff --git a/src/main/java/org/umlgraph/doclet/Options.java b/src/main/java/org/umlgraph/doclet/Options.java index 7018da8..7b5cc98 100644 --- a/src/main/java/org/umlgraph/doclet/Options.java +++ b/src/main/java/org/umlgraph/doclet/Options.java @@ -29,18 +29,26 @@ import java.io.InputStreamReader; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.Doc; -import com.sun.javadoc.Tag; +import javax.lang.model.element.Element; +import javax.lang.model.element.Name; +import javax.lang.model.element.TypeElement; + +import org.umlgraph.doclet.util.TagUtil; + +import com.sun.source.util.DocTrees; + +import jdk.javadoc.doclet.Doclet; /** * Represent the program options @@ -49,13 +57,550 @@ import com.sun.javadoc.Tag; * @author Diomidis Spinellis */ public class Options implements Cloneable, OptionProvider { + + public final Set OPTIONS = Set.of( + new Option("--d", true, "Specify the output directory (defaults to the current directory).", "") { + @Override + public boolean process(String option, List arguments) { + outputDirectory = arguments.get(0); + return true; + } + }, + new Option("-qualify", false, "Produce fully-qualified class names.", null) { + @Override + public boolean process(String option, List arguments) { + showQualified = true; + return true; + } + }, + new Option("-qualifyGenerics", false, "Use fully-qualified class names in Java generics.", null) { + @Override + public boolean process(String option, List arguments) { + showQualifiedGenerics = true; + return true; + } + }, + new Option("-hideGenerics", false, "FIXME Missing doc", null) { + @Override + public boolean process(String option, List arguments) { + hideGenerics = true; + return true; + } + }, + new Option("-horizontal", false, "Layout the graph in the horizontal direction.", null) { + @Override + public boolean process(String option, List arguments) { + horizontal = true; + return true; + } + }, + new Option("-attributes", false, "Show class attributes (Java fields)", null) { + @Override + public boolean process(String option, List arguments) { + showAttributes = true; + return true; + } + }, + new Option("-enumconstants", false, "When showing enumerations, also show the values they can take", null) { + @Override + public boolean process(String option, List arguments) { + showEnumConstants = true; + return true; + } + }, + new Option("-operations", false, "Show class operations (Java methods)", null) { + @Override + public boolean process(String option, List arguments) { + showOperations = true; + return true; + } + }, + new Option("-enumerations", false, "Show enumarations as separate stereotyped primitive types", null) { + @Override + public boolean process(String option, List arguments) { + showEnumerations = true; + return true; + } + }, + new Option("-constructors", false, "Show a class's constructors", null) { + @Override + public boolean process(String option, List arguments) { + showConstructors = true; + return true; + } + }, + new Option("-visibility", false, "Adorn class elements according to their visibility (private, public, protected, package)", null) { + @Override + public boolean process(String option, List arguments) { + showVisibility = true; + return true; + } + }, + new Option("-types", false, "Add type information to attributes and operations", null) { + @Override + public boolean process(String option, List arguments) { + showType = true; + return true; + } + }, + new Option("-autosize", false, "Fits generated graph to the width of the page/window. Defaults to true.", null) { + @Override + public boolean process(String option, List arguments) { + autoSize = true; + return true; + } + }, + new Option("-commentname", false, "Name the element using the text in the javadoc comment, instead of the name of its class.", null) { + @Override + public boolean process(String option, List arguments) { + showComment = true; + return true; + } + }, + new Option("-all", false, "Same as -attributes -operations -visibility -types -enumerations -enumconstants", null) { + @Override + public boolean process(String option, List arguments) { + setAll(); + return true; + } + }, + new Option("--bgcolor", true, "Specify the graph's background color.", "") { + @Override + public boolean process(String option, List arguments) { + bgColor = arguments.get(0); + return true; + } + }, + new Option("--edgecolor", true, "Specify the color for drawing edges.", "") { + @Override + public boolean process(String option, List arguments) { + edgeColor = arguments.get(0); + return true; + } + }, + new Option("--edgefontcolor", true, "Specify the font color to use for edge labels.", "") { + @Override + public boolean process(String option, List arguments) { + edgeFontColor = arguments.get(0); + return true; + } + }, + new Option("--edgefontname", true, "Specify the font name to use for edge labels.", "") { + @Override + public boolean process(String option, List arguments) { + edgeFontName = arguments.get(0); + return true; + } + }, + new Option("-edgefontsize", true, "Specify the font size to use for edge labels.", "") { + @Override + public boolean process(String option, List arguments) { + edgeFontSize = Double.parseDouble(arguments.get(0)); + return true; + } + }, + new Option("-nodefontcolor", true, "Specify the font color to use inside nodes", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontColor = arguments.get(0); + return true; + } + }, + new Option("-nodefontname", true, "Specify the font name to use inside nodes", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontName = arguments.get(0); + return true; + } + }, + new Option("-nodefontabstractitalic", false, "FIXME no documentation", null) { + @Override + public boolean process(String option, List arguments) { + nodeFontAbstractItalic = true; + return true; + } + }, + new Option("-nodefontsize", true, "Specify the font size to use inside nodes", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontSize = Double.parseDouble(arguments.get(0)); + return true; + } + }, + new Option("-nodefontclassname", true, "Specify the font name to use for the class names", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontClassName = arguments.get(0); + return true; + } + }, + new Option("-nodefontclasssize", true, "Specify the font size to use for the class names.", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontClassSize = Double.parseDouble(arguments.get(0)); + return true; + } + }, + new Option("-nodefonttagname", true, "Specify the font name to use for the tag names.", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontTagName = arguments.get(0); + return true; + } + }, + new Option("-nodefonttagsize", true, "Specify the font size to use for the tag names", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontTagSize = Double.parseDouble(arguments.get(0)); + return true; + } + }, + new Option("-nodefontpackagename", true, "Specify the font name to use for the package names (used only when the package name is postfixed, see -postfixpackage).", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontPackageName = arguments.get(0); + return true; + } + }, + new Option("-nodefontpackagesize", true, "Specify the font size to use for the package names (used only when it package name is postfixed, see -postfixpackage).", "") { + @Override + public boolean process(String option, List arguments) { + nodeFontPackageSize = Double.parseDouble(arguments.get(0)); + return true; + } + }, + new Option("-nodefillcolor", true, "Specify the color to use to fill the shapes", "") { + @Override + public boolean process(String option, List arguments) { + nodeFillColor = arguments.get(0); + return true; + } + }, + new Option("-shape", true, "Specify the shape to use for the rendered element(s).\n" + + " The following UML shapes are available: class (default), node," + + " component, package, collaboration, usecase, activeclass", "") { + @Override + public boolean process(String option, List arguments) { + shape = Shape.of(arguments.get(0)); + return true; + } + }, + new Option("-output", true, "Specify the output file (default graph.dot).\n" + + "If the output directory is provided, -output can only specify a file name,\n" + + "otherwise a full path is accepted as well.\n" + + "If the filename specified is a dash, then the results are printed on the\n" + + "standard output, and can be directly piped into dot.\n" + + "Note that, in order to avoid javadoc messages to contaminate\n" + + "UMLGraph's output, you must execute UMLGraph directly as a jar,\n" + + "not through javadoc.", "") { + @Override + public boolean process(String option, List arguments) { + outputFileName = arguments.get(0); + return true; + } + }, + new Option("-outputencoding", true, "Specify the output encoding character set (default UTF-8).\n" + + "When using dot to generate SVG diagrams you should specify\n" + + "UTF-8 as the output encoding, to have guillemots correctly\n" + + "appearing in the resulting SVG.", "") { + @Override + public boolean process(String option, List arguments) { + outputEncoding = arguments.get(0); + return true; + } + }, + new Option("--hide", true, "Specify entities to hide from the graph." + + " Matching is done using a non-anchored regular match." + + " For instance, \"-hide (Big|\\.)Widget\" would hide \"com.foo.widgets.Widget\" and " + + " \"com.foo.widgets.BigWidget\". Can also be used without arguments, " + + " in this case it will hide everything (useful in the context of views " + + " to selectively unhide some portions of the graph, see the view chapter for " + + " further details).", "") { + @Override + public boolean process(String option, List arguments) { + if (arguments == null || arguments.isEmpty()) { + hidePatterns.clear(); + hidePatterns.add(allPattern); + } else { + try { + hidePatterns.add(Pattern.compile(arguments.get(0))); + } catch (PatternSyntaxException e) { + System.err.println("Skipping invalid pattern " + arguments.get(0)); + } + } + return true; + } + }, + new Option("--include", true, "Match classes to include with a non-anchored match. This is weaker than\n" + + " the -hide option, but can be used to include classes from foreign packages\n" + + " in the package view (which would by default filter to only include package members).", "") { + @Override + public boolean process(String option, List arguments) { + try { + includePatterns.add(Pattern.compile(arguments.get(0))); + } catch (PatternSyntaxException e) { + System.err.println("Skipping invalid pattern " + arguments.get(0)); + } + return true; + } + }, + new Option("-apidocroot", true, "Specify the URL that should be used as the \"root\" for local classes.\n" + + "This URL will be used as a prefix, to which the page name for the local class or\n" + + "package will be appended (following the JavaDoc convention).\n" + + "For example, if the value http://www.acme.org/apidocs is\n" + + "provided, the class org.acme.util.MyClass will be mapped to the URL\n" + + "http://www.acme.org/apidocs/org/acme/util/MyClass.html.\n" + + "This URL will then be added to .dot diagram and can be surfaced in the\n" + + "final class diagram by setting the output to SVG, or by creating an HTML page\n" + + "that associates the diagram static image (a .gif or .png) with a client-side\n" + + "image map.", "") { + @Override + public boolean process(String option, List arguments) { + apiDocRoot = fixApiDocRoot(arguments.get(0)); + return true; + } + }, + new Option("-apidocmap", true, "Specify the file name of the URL mapping table. \n" + + "The is a standard Java property file, where the property name is a regular\n" + + "expression (as defined in the java.util.regex package) and the property value is\n" + + "an URL \"root\" as described above.\n" + + "This table is used to resolved external class names (class names that do not\n" + + "belong to the current package being processed by UMLGraph). If no file is provided,\n" + + "external classes will just be mapped to the on-line Java API documentation.", "") { + @Override + public boolean process(String option, List arguments) { + setApiDocMapFile(arguments.get(0)); + return true; + } + }, + new Option("-noguillemot", false, "Specify that guillemot characters should not be used to denote " + + "special terms like \"interface\" and stereotype names." + + "This is used on some platforms to circumvent problems associated with displaying non-ASCII characters.", null) { + @Override + public boolean process(String option, List arguments) { + guilOpen = "<<"; + guilClose = ">>"; + return true; + } + }, + new Option("-view", true, "Specify the fully qualified name of a class that contains\n" + + " a view definition. Only the class diagram specified by this view will be generated. \n" + + "
See the views chapter for more details.", "") { + @Override + public boolean process(String option, List arguments) { + viewName = arguments.get(0); + return true; + } + }, + new Option("-views", false, "Generate a class diagram for every view found in the source path..", null) { + @Override + public boolean process(String option, List arguments) { + findViews = true; + return true; + } + }, + new Option("-inferrel", false, "Try to automatically infer relationships between classes by inspecting " + + "field values. See the class diagram inference chapter for further details. Disabled by default.", null) { + @Override + public boolean process(String option, List arguments) { + inferRelationships = true; + return true; + } + }, + new Option("-inferreltype", true, "The type of relationship inferred when -inferrel is activated. \n" + + "Defaults to \"navassoc\" (see the class modelling chapter for a list of relationship types).", "") { + @Override + public boolean process(String option, List arguments) { + try { + inferRelationshipType = RelationType.valueOf(arguments.get(0).toUpperCase()); + } catch (IllegalArgumentException e) { + System.err.println("Unknown association type " + arguments.get(0)); + } + return true; + } + }, + new Option("-inferdepvis", true, "Specifies the lowest visibility level of elements used to infer\n" + + "dependencies among classes. Possible values are private, package, protected, public, in this\n" + + "order. The default value is private. Use higher levels to limit the number of inferred dependencies", "") { + @Override + public boolean process(String option, List arguments) { + try { + inferDependencyVisibility = Visibility.valueOf(arguments.get(0).toUpperCase()); + } catch (IllegalArgumentException e) { + System.err.println("Ignoring invalid visibility specification for " + "dependency inference: " + arguments.get(0)); + } + return true; + } + }, + new Option("-collapsible", false, "Enhance the javadoc HTML files containing UML diagrams with Javascript " + + "that provides a link for showing the (initially collapsed) diagrams.", null) { + @Override + public boolean process(String option, List arguments) { + collapsibleDiagrams = true; + return true; + } + }, + new Option("-inferdep", false, "Try to automatically infer dependencies between classes by inspecting " + + "methods and fields. See the class diagram inference chapter for more details. Disabled by default.", null) { + @Override + public boolean process(String option, List arguments) { + inferDependencies = true; + return true; + } + }, + new Option("-inferdepinpackage", false, "Enable or disable dependency inference among classes in the\n" + + "same package. This option is disabled by default, because classes in the same package are supposed\n" + + "to be related anyway, and also because there's no working mechanism to actually detect all\n" + + "of these dependencies since imports are not required to use classes in the same package.", null) { + @Override + public boolean process(String option, List arguments) { + inferDepInPackage = true; + return true; + } + }, + new Option("-hideprivateinner", false, "FIXME NO DOC", null) { + @Override + public boolean process(String option, List arguments) { + hidePrivateInner = true; + return true; + } + }, + new Option("-useimports", false, "Will also use imports to infer dependencies. \n" + + "Disabled by default, since it does not work properly if there are multiple\n" + + "classes in the same source file (will add dependencies to every class in\n" + + "the source file).", null) { + @Override + public boolean process(String option, List arguments) { + System.err.println("useimports has been set but will have no effect since not (yet) available"); + useImports = true; + return true; + } + }, + new Option("--collpackages", true, "Specify the classes that will be treated as " + + "containers for one to many relationships when inference is enabled. " + + "Matching is done using a non-anchored regular match. Empty by default.", "") { + @Override + public boolean process(String option, List arguments) { + try { + collPackages.add(Pattern.compile(arguments.get(0))); + } catch (PatternSyntaxException e) { + System.err.println("Skipping invalid pattern " + arguments.get(0)); + } + return true; + } + }, + new Option("-compact", false, "Generate compact dot files, that is, print HTML labels\n" + + "in a single line instead of \"pretty printing\" them. Useful if the dot file\n" + + "has to be manipulated by an automated tool\n" + + "(e.g., the UMLGraph regression test suite).", null) { + @Override + public boolean process(String option, List arguments) { + compact = true; + return true; + } + }, + new Option("-postfixpackage", false, "When using qualified class names, put the package name in the line after the class name, in order to reduce the width of class nodes.", null) { + @Override + public boolean process(String option, List arguments) { + postfixPackage = true; + return true; + } + }, + + new Option("-hideGenerics", false, "?", null) { + @Override + public boolean process(String option, List arguments) { + hideGenerics = true; + return true; + } + }, + new Option("--link", true, "A clone of the standard doclet\n" + + "-link\n" + + "option, allows UMLGraph to generate links from class symbols to their external javadoc\n" + + "documentation (image maps are automatically generated in UMLGraphDoc, you'll have to generate them\n" + + "manually with graphviz if using UMLGraph).", "") { + @Override + public boolean process(String option, List arguments) { + System.err.println("add -link option with args : " + arguments.get(0)); + addApiDocRoots(arguments.get(0)); + return true; + } + }, + new Option("-linkoffline", 2, "Specify\n" + + "links to javadoc-generated documentation for external referenced classes.\n" + + "The -linkoffline option takes two arguments:\n" + + "the first for the string to be embedded in the href\n" + + "links, the second telling it where to find the package-list.\n" + + "Example:\n" + + "

\n"
+                + "-linkoffline http://developer.android.com/reference file:/home/doc/android/\n"
+                + "
\n" + + "See the javadoc documentation for more details.", " ") { + @Override + public boolean process(String option, List arguments) { + addApiDocRootsOffline(arguments.get(0), arguments.get(1)); + return true; + } + }, + new Option("-contextPattern", 2, "FIXME MISSING DOC.", " ") { + @Override + public boolean process(String option, List arguments) { + RelationDirection d; + RelationType rt; + try { + d = RelationDirection.valueOf(arguments.get(1).toUpperCase()); + if ("all".equalsIgnoreCase(arguments.get(0))) { + contextRelationPattern = new RelationPattern(d); + } else { + rt = RelationType.valueOf(arguments.get(0).toUpperCase()); + contextRelationPattern.addRelation(rt, d); + } + } catch (IllegalArgumentException e) { + + } + return true; + } + }, + new Option("-nodesep", true, "Specify the horizontal separation between the class nodes (0.25 by default). Decreasing this can make a diagram more compact.", "") { + @Override + public boolean process(String option, List arguments) { + try { + nodeSep = Double.parseDouble(arguments.get(0)); + } catch (NumberFormatException e) { + System.err.println("Skipping invalid nodesep " + arguments.get(0)); + } + return true; + } + }, + new Option("-ranksep", true, "Specify the vertical separation between the class nodes (0.5 by default). Decreasing this can make a diagram more compact.", "") { + @Override + public boolean process(String option, List arguments) { + try { + rankSep = Double.parseDouble(arguments.get(0)); + } catch (NumberFormatException e) { + System.err.println("Skipping invalid ranksep " + arguments.get(0)); + } + return true; + } + }, + new Option("-dotexecutable", true, "Specify the path of the dot executable.", "") { + @Override + public boolean process(String option, List arguments) { + dotExecutable = arguments.get(0); + return true; + } + } + + + ); + // reused often, especially in UmlGraphDoc, worth creating just once and reusing private static final Pattern allPattern = Pattern.compile(".*"); protected static final String DEFAULT_EXTERNAL_APIDOC = "http://docs.oracle.com/javase/7/docs/api/"; // instance fields - List hidePatterns = new ArrayList(); - List includePatterns = new ArrayList(); + List hidePatterns = new ArrayList<>(); + List includePatterns = new ArrayList<>(); boolean showQualified = false; boolean showQualifiedGenerics = false; boolean hideGenerics = false; @@ -87,8 +632,8 @@ public class Options implements Cloneable, OptionProvider { Shape shape = Shape.CLASS; String bgColor = null; public String outputFileName = "graph.dot"; - String outputEncoding = "ISO-8859-1"; // TODO: default to UTF-8 now? - Map apiDocMap = new HashMap(); + String outputEncoding = StandardCharsets.UTF_8.name(); + Map apiDocMap = new HashMap<>(); String apiDocRoot = null; boolean postfixPackage = false; boolean useGuillemot = true; @@ -96,7 +641,7 @@ public class Options implements Cloneable, OptionProvider { String viewName = null; double nodeSep = 0.25; double rankSep = 0.5; - public String outputDirectory = null; + public String outputDirectory = "."; /* * Numeric values are preferable to symbolic here. Symbolic reportedly fail on * MacOSX, and also are more difficult to verify with XML tools. @@ -113,26 +658,23 @@ public class Options implements Cloneable, OptionProvider { Visibility inferDependencyVisibility = Visibility.PRIVATE; boolean inferDepInPackage = false; RelationType inferRelationshipType = RelationType.NAVASSOC; - private List collPackages = new ArrayList(); + private List collPackages = new ArrayList<>(); boolean compact = false; boolean hidePrivateInner = false; // internal option, used by UMLDoc to generate relative links between classes boolean relativeLinksForSourcePackages = false; // internal option, used by UMLDoc to force strict matching on the class names - // and avoid problems with packages in the template declaration making UmlGraph - // hide - // classes outside of them (for example, class gr.spinellis.Foo + // and avoid problems with packages in the template declaration making UmlGraph hide + // classes outside of them (for example, class gr.spinellis.Foo // would have been hidden by the hide pattern "java.*" - // TODO: consider making this standard behaviour - boolean strictMatching = false; + boolean strictMatching = true; String dotExecutable = "dot"; Options() { } @Override - public Object clone() { + public Options clone() { Options clone = null; try { clone = (Options) super.clone(); @@ -140,10 +682,10 @@ public class Options implements Cloneable, OptionProvider { throw new RuntimeException("Cannot clone?!?", e); // Should not happen } // deep clone the hide and collection patterns - clone.hidePatterns = new ArrayList(hidePatterns); - clone.includePatterns = new ArrayList(includePatterns); - clone.collPackages = new ArrayList(collPackages); - clone.apiDocMap = new HashMap(apiDocMap); + clone.hidePatterns = new ArrayList<>(hidePatterns); + clone.includePatterns = new ArrayList<>(includePatterns); + clone.collPackages = new ArrayList<>(collPackages); + clone.apiDocMap = new HashMap<>(apiDocMap); return clone; } @@ -159,7 +701,7 @@ public class Options implements Cloneable, OptionProvider { } /** - * Match strings, ignoring leading -, -!, and !. + * Match strings, ignoring leading -, --, -!, and !. * * @param given Given string * @param expect Expected string @@ -170,7 +712,7 @@ public class Options implements Cloneable, OptionProvider { } /** - * Match strings, ignoring leading -, -!, and !. + * Match strings, ignoring leading -, --, -!, and !. * * @param given Given string * @param expect Expected string @@ -179,10 +721,16 @@ public class Options implements Cloneable, OptionProvider { */ protected static boolean matchOption(String given, String expect, boolean negative) { int begin = 0, end = given.length(); - if (begin < end && given.charAt(begin) == '-') + if (begin < end && given.charAt(begin) == '-') { ++begin; - if (negative && begin < end && given.charAt(begin) == '!') + } + // double dashed option + if (begin < end && given.charAt(begin) == '-') { ++begin; + } + if (negative && begin < end && given.charAt(begin) == '!') { + ++begin; + } return expect.length() == end - begin && expect.regionMatches(0, given, begin, end - begin); } @@ -204,10 +752,10 @@ public class Options implements Cloneable, OptionProvider { || matchOption(option, "inferrel", true) || matchOption(option, "useimports", true) || matchOption(option, "collapsible", true) || matchOption(option, "inferdep", true) || matchOption(option, "inferdepinpackage", true) || matchOption(option, "hideprivateinner", true) - || matchOption(option, "compact", true)) + || matchOption(option, "compact", true)) { return 1; - else if (matchOption(option, "nodefillcolor") || matchOption(option, "nodefontcolor") + } else if (matchOption(option, "nodefillcolor") || matchOption(option, "nodefontcolor") || matchOption(option, "nodefontsize") || matchOption(option, "nodefontname") || matchOption(option, "nodefontclasssize") || matchOption(option, "nodefontclassname") || matchOption(option, "nodefonttagsize") || matchOption(option, "nodefonttagname") @@ -221,12 +769,13 @@ public class Options implements Cloneable, OptionProvider { || matchOption(option, "inferreltype") || matchOption(option, "inferdepvis") || matchOption(option, "collpackages") || matchOption(option, "nodesep") || matchOption(option, "ranksep") || matchOption(option, "dotexecutable") - || matchOption(option, "link")) + || matchOption(option, "link")) { return 2; - else if (matchOption(option, "contextPattern") || matchOption(option, "linkoffline")) + } else if (matchOption(option, "contextPattern") || matchOption(option, "linkoffline")) { return 3; - else + } else { return 0; + } } /** Set the options based on a single option and its arguments */ @@ -436,7 +985,7 @@ public class Options implements Cloneable, OptionProvider { BufferedReader br = null; packageListUrl = fixApiDocRoot(packageListUrl); try { - URL url = new URL(packageListUrl + "/package-list"); + URL url = new URL(packageListUrl + "package-list"); br = new BufferedReader(new InputStreamReader(url.openStream())); String line; while ((line = br.readLine()) != null) { @@ -447,11 +996,12 @@ public class Options implements Cloneable, OptionProvider { } catch (IOException e) { System.err.println("Errors happened while accessing the package-list file at " + packageListUrl); } finally { - if (br != null) + if (br != null) { try { br.close(); } catch (IOException e) { } + } } } @@ -468,7 +1018,7 @@ public class Options implements Cloneable, OptionProvider { BufferedReader br = null; packageListUrl = fixApiDocRoot(packageListUrl); try { - URL url = new URL(packageListUrl + "/package-list"); + URL url = new URL(packageListUrl + "package-list"); br = new BufferedReader(new InputStreamReader(url.openStream())); String line; while ((line = br.readLine()) != null) { @@ -479,11 +1029,12 @@ public class Options implements Cloneable, OptionProvider { } catch (IOException e) { System.err.println("Unable to access the package-list file at " + packageListUrl); } finally { - if (br != null) + if (br != null) { try { br.close(); } catch (IOException e) { } + } } } @@ -528,44 +1079,47 @@ public class Options implements Cloneable, OptionProvider { * the constructor of the api doc root, so it depends on the order of "-link" * and "-apiDocMap" parameters. */ - public String getApiDocRoot(String className) { - if (apiDocMap.isEmpty()) + public String getApiDocRoot(Name className) { + if (apiDocMap.isEmpty()) { apiDocMap.put(Pattern.compile(".*"), DEFAULT_EXTERNAL_APIDOC); + } for (Map.Entry mapEntry : apiDocMap.entrySet()) { - if (mapEntry.getKey().matcher(className).matches()) + if (mapEntry.getKey().matcher(className).matches()) { return mapEntry.getValue(); + } } return null; } /** Trim and append a file separator to the string */ private String fixApiDocRoot(String str) { - if (str == null) + if (str == null) { return null; + } String fixed = str.trim(); - if (fixed.isEmpty()) + if (fixed.isEmpty()) { return ""; - if (File.separatorChar != '/') + } + if (File.separatorChar != '/') { fixed = fixed.replace(File.separatorChar, '/'); - if (!fixed.endsWith("/")) + } + if (!fixed.endsWith("/")) { fixed = fixed + "/"; + } return fixed; } - /** Set the options based on the command line parameters */ - public void setOptions(String[][] options) { - for (String s[] : options) - setOption(s); - } - /** Set the options based on the tag elements of the ClassDoc parameter */ - public void setOptions(Doc p) { - if (p == null) + public void setOptions(DocTrees docTrees, Element p) { + if (p == null) { return; + } - for (Tag tag : p.tags("opt")) - setOption(StringUtil.tokenize(tag.text())); + List tags = TagUtil.getTag(docTrees, p, "opt"); + for (String tag : tags) { + setOption(StringUtil.tokenize(tag)); + } } /** @@ -574,15 +1128,17 @@ public class Options implements Cloneable, OptionProvider { * * @return true if the string matches. */ - public boolean matchesHideExpression(String s) { + public boolean matchesHideExpression(CharSequence s) { for (Pattern hidePattern : hidePatterns) { // micro-optimization because the "all pattern" is heavily used in UmlGraphDoc - if (hidePattern == allPattern) + if (hidePattern == allPattern) { return true; + } Matcher m = hidePattern.matcher(s); - if (strictMatching ? m.matches() : m.find()) + if (strictMatching ? m.matches() : m.find()) { return true; + } } return false; } @@ -593,11 +1149,12 @@ public class Options implements Cloneable, OptionProvider { * * @return true if the string matches. */ - public boolean matchesIncludeExpression(String s) { + public boolean matchesIncludeExpression(CharSequence s) { for (Pattern includePattern : includePatterns) { Matcher m = includePattern.matcher(s); - if (strictMatching ? m.matches() : m.find()) + if (strictMatching ? m.matches() : m.find()) { return true; + } } return false; } @@ -608,7 +1165,7 @@ public class Options implements Cloneable, OptionProvider { * * @return true if the string matches. */ - public boolean matchesCollPackageExpression(String s) { + public boolean matchesCollPackageExpression(CharSequence s) { for (Pattern collPattern : collPackages) { Matcher m = collPattern.matcher(s); if (strictMatching ? m.matches() : m.find()) @@ -621,13 +1178,13 @@ public class Options implements Cloneable, OptionProvider { // OptionProvider methods // ---------------------------------------------------------------- - public Options getOptionsFor(ClassDoc cd) { + public Options getOptionsFor(DocTrees docTrees, TypeElement cd) { Options localOpt = getGlobalOptions(); - localOpt.setOptions(cd); + localOpt.setOptions(docTrees, cd); return localOpt; } - public Options getOptionsFor(String name) { + public Options getOptionsFor(CharSequence name) { return getGlobalOptions(); } @@ -635,11 +1192,11 @@ public class Options implements Cloneable, OptionProvider { return (Options) clone(); } - public void overrideForClass(Options opt, ClassDoc cd) { + public void overrideForClass(Options opt, TypeElement cd) { // nothing to do } - public void overrideForClass(Options opt, String className) { + public void overrideForClass(Options opt, CharSequence className) { // nothing to do } diff --git a/src/main/java/org/umlgraph/doclet/PackageMatcher.java b/src/main/java/org/umlgraph/doclet/PackageMatcher.java index ecc3002..6a2d268 100644 --- a/src/main/java/org/umlgraph/doclet/PackageMatcher.java +++ b/src/main/java/org/umlgraph/doclet/PackageMatcher.java @@ -1,24 +1,32 @@ package org.umlgraph.doclet; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.PackageDoc; +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.Element; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import org.umlgraph.doclet.util.ElementUtil; public class PackageMatcher implements ClassMatcher { - protected PackageDoc packageDoc; + protected DocletEnvironment root; + protected PackageElement packageDoc; - public PackageMatcher(PackageDoc packageDoc) { - super(); + public PackageMatcher(DocletEnvironment root, PackageElement packageDoc) { + this.root = root; this.packageDoc = packageDoc; } - public boolean matches(ClassDoc cd) { - return cd.containingPackage().equals(packageDoc); + public boolean matches(TypeElement cd) { + return packageDoc.equals(ElementUtil.getPackageOf(root, cd)); } - public boolean matches(String name) { - for (ClassDoc cd : packageDoc.allClasses()) - if (cd.qualifiedName().equals(name)) + public boolean matches(CharSequence name) { + for (Element cd : packageDoc.getEnclosedElements()) { + if (cd instanceof TypeElement && ((TypeElement) cd).getQualifiedName().equals(name)) { return true; + } + } return false; } diff --git a/src/main/java/org/umlgraph/doclet/PackageView.java b/src/main/java/org/umlgraph/doclet/PackageView.java index 83af937..e528e8a 100755 --- a/src/main/java/org/umlgraph/doclet/PackageView.java +++ b/src/main/java/org/umlgraph/doclet/PackageView.java @@ -1,8 +1,14 @@ package org.umlgraph.doclet; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.PackageDoc; -import com.sun.javadoc.RootDoc; +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; + +import org.umlgraph.doclet.util.ElementUtil; + +import com.sun.source.util.DocTrees; /** * A view designed for UMLDoc, filters out everything that it's not contained in @@ -19,19 +25,23 @@ import com.sun.javadoc.RootDoc; public class PackageView implements OptionProvider { private static final String[] HIDE = new String[] { "hide" }; - private PackageDoc pd; + private PackageElement pd; private OptionProvider parent; private ClassMatcher matcher; private String outputPath; private Options opt; + private DocTrees docTrees; - public PackageView(String outputFolder, PackageDoc pd, RootDoc root, OptionProvider parent) { + public PackageView(String outputFolder, PackageElement pd, DocletEnvironment root, OptionProvider parent) { this.parent = parent; this.pd = pd; - this.matcher = new PackageMatcher(pd); + this.docTrees = root.getDocTrees(); + this.matcher = new PackageMatcher(root, pd); this.opt = parent.getGlobalOptions(); - this.opt.setOptions(pd); - this.outputPath = pd.name().replace('.', '/') + "/" + pd.name() + ".dot"; + this.opt.setOptions(docTrees, pd); + ModuleElement md = ElementUtil.getModuleOf(root, pd); + String pathPrefix = Runtime.version().major() > 10 && md != null ? md.getQualifiedName().toString() + "/" : ""; + this.outputPath = pathPrefix + pd.getQualifiedName().toString().replace('.', '/') + "/" + pd.getSimpleName().toString() + ".dot"; } public String getDisplayName() { @@ -47,36 +57,39 @@ public class PackageView implements OptionProvider { return go; } - public Options getOptionsFor(ClassDoc cd) { + public Options getOptionsFor(DocTrees dt, TypeElement cd) { Options go = parent.getGlobalOptions(); overrideForClass(go, cd); return go; } - public Options getOptionsFor(String name) { + public Options getOptionsFor(CharSequence name) { Options go = parent.getGlobalOptions(); overrideForClass(go, name); return go; } - public void overrideForClass(Options opt, ClassDoc cd) { - opt.setOptions(cd); + public void overrideForClass(Options opt, TypeElement cd) { + opt.setOptions(docTrees, cd); boolean inPackage = matcher.matches(cd); - if (inPackage) + if (inPackage) { opt.showQualified = false; - boolean included = inPackage || this.opt.matchesIncludeExpression(cd.qualifiedName()); - if (!included || this.opt.matchesHideExpression(cd.qualifiedName())) + } + boolean included = inPackage || this.opt.matchesIncludeExpression(cd.getQualifiedName()); + if (!included || this.opt.matchesHideExpression(cd.getQualifiedName())) { opt.setOption(HIDE); + } } - public void overrideForClass(Options opt, String className) { + public void overrideForClass(Options opt, CharSequence className) { opt.showQualified = false; boolean inPackage = matcher.matches(className); if (inPackage) opt.showQualified = false; boolean included = inPackage || this.opt.matchesIncludeExpression(className); - if (!included || this.opt.matchesHideExpression(className)) + if (!included || this.opt.matchesHideExpression(className)) { opt.setOption(HIDE); + } } } diff --git a/src/main/java/org/umlgraph/doclet/PatternMatcher.java b/src/main/java/org/umlgraph/doclet/PatternMatcher.java index a809db6..7e5c4b5 100644 --- a/src/main/java/org/umlgraph/doclet/PatternMatcher.java +++ b/src/main/java/org/umlgraph/doclet/PatternMatcher.java @@ -18,7 +18,7 @@ package org.umlgraph.doclet; import java.util.regex.Pattern; -import com.sun.javadoc.ClassDoc; +import javax.lang.model.element.TypeElement; /** * Matches classes performing a regular expression match on the qualified class @@ -33,11 +33,11 @@ public class PatternMatcher implements ClassMatcher { this.pattern = pattern; } - public boolean matches(ClassDoc cd) { - return matches(cd.toString()); + public boolean matches(TypeElement cd) { + return matches(cd.getQualifiedName()); } - public boolean matches(String name) { + public boolean matches(CharSequence name) { return pattern.matcher(name).matches(); } diff --git a/src/main/java/org/umlgraph/doclet/StringUtil.java b/src/main/java/org/umlgraph/doclet/StringUtil.java index 1aee85b..441e4d9 100644 --- a/src/main/java/org/umlgraph/doclet/StringUtil.java +++ b/src/main/java/org/umlgraph/doclet/StringUtil.java @@ -19,6 +19,9 @@ package org.umlgraph.doclet; +import javax.lang.model.element.Name; +import javax.lang.model.util.Elements; + import java.util.*; import java.util.regex.Pattern; @@ -148,10 +151,11 @@ class StringUtil { } /** Removes the template specs from a class name. */ - public static String removeTemplate(String name) { - int openIdx = name.indexOf('<'); - if (openIdx == -1) + public static Name removeTemplate(Elements elements, Name name) { + int openIdx = name.toString().indexOf('<'); + if (openIdx == -1) { return name; + } StringBuilder buf = new StringBuilder(name.length()); for (int i = 0, depth = 0; i < name.length(); i++) { char c = name.charAt(i); @@ -162,26 +166,29 @@ class StringUtil { else if (depth == 0) buf.append(c); } - return buf.toString(); + return elements.getName(buf.toString()); } - public static String buildRelativePathFromClassNames(String contextPackageName, String classPackageName) { + public static String buildRelativePathFromClassNames(Name contextPackageName, String classPackageName) { // path, relative to the root, of the destination class - String[] contextClassPath = contextPackageName.split("\\."); + String[] contextClassPath = contextPackageName.toString().split("\\."); String[] currClassPath = classPackageName.split("\\."); // compute relative path between the context and the destination // ... first, compute common part int i = 0, e = Math.min(contextClassPath.length, currClassPath.length); - while (i < e && contextClassPath[i].equals(currClassPath[i])) + while (i < e && contextClassPath[i].equals(currClassPath[i])) { i++; + } // ... go up with ".." to reach the common root StringBuilder buf = new StringBuilder(classPackageName.length()); - for (int j = i; j < contextClassPath.length; j++) + for (int j = i; j < contextClassPath.length; j++) { buf.append("../"); + } // ... go down from the common root to the destination - for (int j = i; j < currClassPath.length; j++) + for (int j = i; j < currClassPath.length; j++) { buf.append(currClassPath[j]).append('/'); // Always use HTML seperators + } return buf.toString(); } @@ -195,10 +202,11 @@ class StringUtil { * users adhere to Java conventions, of beginning package names with a lowercase * letter. * - * @param className + * @param name * @return Splitting point (Either referring to a dot, or -1) */ - public static int splitPackageClass(String className) { + public static int splitPackageClass(CharSequence name) { + String className = name.toString(); int gen = className.indexOf('<'); // Begin before generics. int end = gen >= 0 ? gen : className.length(); int start = className.lastIndexOf('.', end); diff --git a/src/main/java/org/umlgraph/doclet/SubclassMatcher.java b/src/main/java/org/umlgraph/doclet/SubclassMatcher.java index f9282c6..acbabb4 100644 --- a/src/main/java/org/umlgraph/doclet/SubclassMatcher.java +++ b/src/main/java/org/umlgraph/doclet/SubclassMatcher.java @@ -2,8 +2,11 @@ package org.umlgraph.doclet; import java.util.regex.Pattern; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.RootDoc; +import jdk.javadoc.doclet.DocletEnvironment; + +import javax.lang.model.element.TypeElement; + +import org.umlgraph.doclet.util.ElementUtil; /** * Matches every class that extends (directly or indirectly) a class matched by @@ -11,25 +14,27 @@ import com.sun.javadoc.RootDoc; */ public class SubclassMatcher implements ClassMatcher { - protected RootDoc root; + protected DocletEnvironment root; protected Pattern pattern; - public SubclassMatcher(RootDoc root, Pattern pattern) { + public SubclassMatcher(DocletEnvironment root, Pattern pattern) { this.root = root; this.pattern = pattern; } - public boolean matches(ClassDoc cd) { + public boolean matches(TypeElement cd) { // if it's the class we're looking for return - if (pattern.matcher(cd.toString()).matches()) + if (pattern.matcher(cd.toString()).matches()) { return true; + } - // recurse on supeclass, if available - return cd.superclass() == null ? false : matches(cd.superclass()); + // recurse on superclass, if available + TypeElement scd = ElementUtil.getTypeElement(cd.getSuperclass()); + return scd == null ? false : matches(scd); } - public boolean matches(String name) { - ClassDoc cd = root.classNamed(name); + public boolean matches(CharSequence name) { + TypeElement cd = root.getElementUtils().getTypeElement(name); return cd == null ? false : matches(cd); } diff --git a/src/main/java/org/umlgraph/doclet/TagScanner.java b/src/main/java/org/umlgraph/doclet/TagScanner.java new file mode 100644 index 0000000..420eca8 --- /dev/null +++ b/src/main/java/org/umlgraph/doclet/TagScanner.java @@ -0,0 +1,121 @@ +package org.umlgraph.doclet; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.umlgraph.doclet.util.CommentHelper; + +import com.sun.source.doctree.*; +import com.sun.source.util.SimpleDocTreeVisitor; + +/** + * A visitor to gather the block tags found in a comment. + */ +public class TagScanner extends SimpleDocTreeVisitor { + + private final Map> tags; + + public TagScanner(Map> tags) { + this.tags = tags; + } + + @Override + public Void visitDocComment(DocCommentTree tree, Void p) { + if (tree == null) { + return null; + } + String body = CommentHelper.getText(tree.getFullBody()); + tags.put("comment", new ArrayList<>(List.of(body))); + return visit(tree.getBlockTags(), null); + } + + @Override + public Void visitAuthor(AuthorTree node, Void p) { + String name = node.getTagName(); + String content = node.getName().toString(); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitDeprecated(DeprecatedTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getBody()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitProvides(ProvidesTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getDescription()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitSince(SinceTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getBody()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitVersion(VersionTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getBody()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitParam(ParamTree node, Void p) { + String name = node.getTagName(); + String content = node.getName().toString() + " " + CommentHelper.getText(node.getDescription()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitReturn(ReturnTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getDescription()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitSee(SeeTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getReference()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitThrows(ThrowsTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getDescription()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + @Override + public Void visitUses(UsesTree node, Void p) { + String name = node.getTagName(); + String content = CommentHelper.getText(node.getDescription()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } + + + @Override + public Void visitUnknownBlockTag(UnknownBlockTagTree tree, Void p) { + String name = tree.getTagName(); + String content = CommentHelper.getText(tree.getContent()); + tags.computeIfAbsent(name, n -> new ArrayList<>()).add(content); + return null; + } +} \ No newline at end of file diff --git a/src/main/java/org/umlgraph/doclet/UmlGraph.java b/src/main/java/org/umlgraph/doclet/UmlGraph.java index 5bc08a0..f14d76b 100644 --- a/src/main/java/org/umlgraph/doclet/UmlGraph.java +++ b/src/main/java/org/umlgraph/doclet/UmlGraph.java @@ -20,14 +20,26 @@ package org.umlgraph.doclet; import java.io.IOException; -import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Set; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.Doc; -import com.sun.javadoc.LanguageVersion; -import com.sun.javadoc.RootDoc; +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import jdk.javadoc.doclet.StandardDoclet; + +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import org.umlgraph.doclet.util.ElementUtil; +import org.umlgraph.doclet.util.TagUtil; /** * Doclet API implementation @@ -41,7 +53,7 @@ import com.sun.javadoc.RootDoc; * @version $Revision$ * @author Diomidis Spinellis */ -public class UmlGraph { +public class UmlGraph implements Doclet { private static final String programName = "UmlGraph"; private static final String docletName = "org.umlgraph.doclet.UmlGraph"; @@ -49,25 +61,66 @@ public class UmlGraph { /** Options used for commenting nodes */ private static Options commentOptions; - /** Entry point through javadoc */ - public static boolean start(RootDoc root) throws IOException { - Options opt = buildOptions(root); - root.printNotice("UMLGraph doclet version " + Version.VERSION + " started"); - View[] views = buildViews(opt, root, root); - if (views == null) - return false; - if (views.length == 0) - buildGraph(root, opt, null); - else - for (int i = 0; i < views.length; i++) - buildGraph(root, views[i], null); - return true; + private Locale locale; + private Reporter reporter; + private Options options; + private StandardDoclet standard; + + @Override + public void init(Locale locale, Reporter reporter) { + this.locale = locale; + this.reporter = reporter; + this.options = new Options(); + this.standard = new StandardDoclet(); } - public static void main(String args[]) { - PrintWriter err = new PrintWriter(System.err); - com.sun.tools.javadoc.Main.execute(programName, err, err, err, docletName, args); + @Override + public String getName() { + return docletName; + } + + @Override + public Set getSupportedOptions() { + Set options = new HashSet<>(standard.getSupportedOptions()); + for (Doclet.Option opt : this.options.OPTIONS) { + options.add(opt); + } + return options; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return standard.getSupportedSourceVersion(); + } + + /** + * Standard doclet entry point + * + * @param root + * @return + */ + @Override + /** Entry point through javadoc */ + public boolean run(DocletEnvironment root) { + reporter.print(Diagnostic.Kind.NOTE, "UMLGraph doclet version " + Version.VERSION + " started"); + + List views = buildViews(options, root, root); + if (views == null) { + return false; + } + try { + if (views.isEmpty()) { + buildGraph(reporter, root, options, null, null); + } else { + for (View view : views) { + buildGraph(reporter, root, options, view, null); + } + } + } catch (IOException ioe) { + reporter.print(Diagnostic.Kind.ERROR, "UMLGraph doclet error : " + ioe); + } + return true; } public static Options getCommentOptions() { @@ -79,49 +132,62 @@ public class UmlGraph { * the command line and the ones specified in the UMLOptions class, if * available. Also create the globally accessible commentOptions object. */ - public static Options buildOptions(RootDoc root) { - commentOptions = new Options(); - commentOptions.setOptions(root.options()); - commentOptions.setOptions(findClass(root, "UMLNoteOptions")); + public static Options buildOptions(DocletEnvironment root, Options o) { + commentOptions = o.clone(); + commentOptions.setOptions(root.getDocTrees(), findClass(root, "UMLNoteOptions")); commentOptions.shape = Shape.NOTE; - Options opt = new Options(); - opt.setOptions(root.options()); - opt.setOptions(findClass(root, "UMLOptions")); + Options opt = o.clone(); + opt.setOptions(root.getDocTrees(), findClass(root, "UMLOptions")); return opt; } /** Return the ClassDoc for the specified class; null if not found. */ - private static ClassDoc findClass(RootDoc root, String name) { - ClassDoc[] classes = root.classes(); - for (ClassDoc cd : classes) - if (cd.name().equals(name)) - return cd; + private static TypeElement findClass(DocletEnvironment root, String name) { + Set classes = root.getIncludedElements(); + for (Element element : classes) { + if (element instanceof TypeElement && ((TypeElement) element).getQualifiedName().toString().equals(name)) { + return (TypeElement) element; + } + } return null; } /** * Builds and outputs a single graph according to the view overrides */ - public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException { - if (getCommentOptions() == null) - buildOptions(root); + public static void buildGraph(Reporter reporter, DocletEnvironment root, Options options, OptionProvider op, Element contextDoc) throws IOException { + if (getCommentOptions() == null) { + buildOptions(root, options); + } Options opt = op.getGlobalOptions(); - root.printNotice("Building " + op.getDisplayName()); - ClassDoc[] classes = root.classes(); + reporter.print(Diagnostic.Kind.NOTE, "Building " + op.getDisplayName()); + Set elements = root.getIncludedElements(); + Set classes = new HashSet<>(); + for (Element element : elements) { + if (element instanceof TypeElement) { + classes.add((TypeElement) element); + } + } ClassGraph c = new ClassGraph(root, op, contextDoc); c.prologue(); - for (ClassDoc cd : classes) + for (TypeElement cd : classes) { c.printClass(cd, true); - for (ClassDoc cd : classes) + } + for (TypeElement cd : classes) { c.printRelations(cd); - if (opt.inferRelationships) - for (ClassDoc cd : classes) + } + if (opt.inferRelationships) { + for (TypeElement cd : classes) { c.printInferredRelations(cd); - if (opt.inferDependencies) - for (ClassDoc cd : classes) + } + } + if (opt.inferDependencies) { + for (TypeElement cd : classes) { c.printInferredDependencies(cd); + } + } c.printExtraClasses(root); c.epilogue(); @@ -135,54 +201,51 @@ public class UmlGraph { * @param viewRootDoc The RootDoc for the view classes (may be different, or may * be the same as the srcRootDoc) */ - public static View[] buildViews(Options opt, RootDoc srcRootDoc, RootDoc viewRootDoc) { + public static List buildViews(Options opt, DocletEnvironment srcRootDoc, DocletEnvironment viewRootDoc) { if (opt.viewName != null) { - ClassDoc viewClass = viewRootDoc.classNamed(opt.viewName); + TypeElement viewClass = findClass(viewRootDoc, opt.viewName); if (viewClass == null) { System.out.println("View " + opt.viewName + " not found! Exiting without generating any output."); return null; } - if (viewClass.tags("view").length == 0) { + if (TagUtil.getTag(viewRootDoc, viewClass, "view").isEmpty()) { System.out.println(viewClass + " is not a view!"); return null; } - if (viewClass.isAbstract()) { + if (viewClass.getModifiers().contains(Modifier.ABSTRACT)) { System.out.println(viewClass + " is an abstract view, no output will be generated!"); return null; } - return new View[] { buildView(srcRootDoc, viewClass, opt) }; + return List.of(buildView(srcRootDoc, viewClass, opt)); } else if (opt.findViews) { - List views = new ArrayList(); - ClassDoc[] classes = viewRootDoc.classes(); + List views = new ArrayList<>(); + Set classes = viewRootDoc.getIncludedElements(); // find view classes - for (int i = 0; i < classes.length; i++) - if (classes[i].tags("view").length > 0 && !classes[i].isAbstract()) - views.add(buildView(srcRootDoc, classes[i], opt)); + for (Element elmt : classes) { + if (!(elmt instanceof TypeElement)) { + continue; + } + TypeElement element = (TypeElement) elmt; + if (TagUtil.getTag(viewRootDoc, element, "view").size() > 0 && !element.getModifiers().contains(Modifier.ABSTRACT)) { + views.add(buildView(srcRootDoc, element, opt)); + } + } - return views.toArray(new View[views.size()]); + return views; } else - return new View[0]; + return Collections.emptyList(); } /** * Builds a view along with its parent views, recursively */ - private static View buildView(RootDoc root, ClassDoc viewClass, OptionProvider provider) { - ClassDoc superClass = viewClass.superclass(); - if (superClass == null || superClass.tags("view").length == 0) + private static View buildView(DocletEnvironment root, TypeElement viewClass, OptionProvider provider) { + TypeElement superClass = ElementUtil.getSuperclass(viewClass); + if (superClass == null || TagUtil.getTag(root, superClass, "view").isEmpty()) { return new View(root, viewClass, provider); + } return new View(root, viewClass, buildView(root, superClass, provider)); } - - /** Option checking */ - public static int optionLength(String option) { - return Options.optionLength(option); - } - - /** Indicate the language version we support */ - public static LanguageVersion languageVersion() { - return LanguageVersion.JAVA_1_5; - } } diff --git a/src/main/java/org/umlgraph/doclet/UmlGraphDoc.java b/src/main/java/org/umlgraph/doclet/UmlGraphDoc.java index a22c021..6a8f796 100644 --- a/src/main/java/org/umlgraph/doclet/UmlGraphDoc.java +++ b/src/main/java/org/umlgraph/doclet/UmlGraphDoc.java @@ -3,22 +3,32 @@ package org.umlgraph.doclet; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; -import java.io.IOException; -import java.io.OutputStreamWriter; -import java.io.FileOutputStream; import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; import java.io.InputStreamReader; -import java.util.HashSet; -import java.util.TreeSet; +import java.io.OutputStreamWriter; import java.util.Comparator; +import java.util.HashSet; +import java.util.Locale; import java.util.Set; +import java.util.TreeSet; import java.util.regex.Pattern; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.LanguageVersion; -import com.sun.javadoc.PackageDoc; -import com.sun.javadoc.RootDoc; -import com.sun.tools.doclets.standard.Standard; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.Name; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; + +import org.umlgraph.doclet.util.ElementUtil; + +import jdk.javadoc.doclet.Doclet; +import jdk.javadoc.doclet.DocletEnvironment; +import jdk.javadoc.doclet.Reporter; +import jdk.javadoc.doclet.StandardDoclet; /** * Chaining doclet that runs the standart Javadoc doclet first, and on success, @@ -29,17 +39,42 @@ import com.sun.tools.doclets.standard.Standard; * @depend - - - WrappedClassDoc * @depend - - - WrappedRootDoc */ -public class UmlGraphDoc { - /** - * Option check, forwards options to the standard doclet, if that one refuses - * them, they are sent to UmlGraph - */ - public static int optionLength(String option) { - int result = Standard.optionLength(option); - if (result != 0) - return result; - else - return UmlGraph.optionLength(option); +public class UmlGraphDoc implements Doclet { + + private Locale locale; + private Reporter reporter; + private Options options; + private StandardDoclet standard; + + public UmlGraphDoc() { + this.options = new Options(); + this.standard = new StandardDoclet(); + } + + @Override + public void init(Locale locale, Reporter reporter) { + this.locale = locale; + this.reporter = reporter; + this.standard.init(locale, reporter); + } + + @Override + public String getName() { + return this.getClass().getSimpleName(); + } + + @Override + public Set getSupportedOptions() { + Set options = new HashSet<>(standard.getSupportedOptions()); + for (Doclet.Option opt : this.options.OPTIONS) { + options.add(opt); + } + return options; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return standard.getSupportedSourceVersion(); } /** @@ -48,56 +83,53 @@ public class UmlGraphDoc { * @param root * @return */ - public static boolean start(RootDoc root) { - root.printNotice("UmlGraphDoc version " + Version.VERSION + ", running the standard doclet"); - Standard.start(root); - root.printNotice("UmlGraphDoc version " + Version.VERSION + ", altering javadocs"); + @Override + public boolean run(DocletEnvironment root) { + reporter.print(Diagnostic.Kind.NOTE, "UmlGraphDoc version " + Version.VERSION + ", running the standard doclet"); + standard.run(root); + reporter.print(Diagnostic.Kind.NOTE, "UmlGraphDoc version " + Version.VERSION + ", altering javadocs"); try { - String outputFolder = findOutputPath(root.options()); - - Options opt = UmlGraph.buildOptions(root); - opt.setOptions(root.options()); + Options opt = UmlGraph.buildOptions(root, this.options); // in javadoc enumerations are always printed opt.showEnumerations = true; opt.relativeLinksForSourcePackages = true; // enable strict matching for hide expressions opt.strictMatching = true; -// root.printNotice(opt.toString()); + reporter.print(Diagnostic.Kind.NOTE, opt.toString()); - generatePackageDiagrams(root, opt, outputFolder); - generateContextDiagrams(root, opt, outputFolder); + generatePackageDiagrams(root, reporter, opt, this.options.outputDirectory); + generateContextDiagrams(root, reporter, opt, this.options.outputDirectory); } catch (Throwable t) { - root.printWarning("Error: " + t.toString()); + reporter.print(Diagnostic.Kind.WARNING, "Error: " + t.toString()); t.printStackTrace(); return false; } return true; } - /** - * Standand doclet entry - * - * @return - */ - public static LanguageVersion languageVersion() { - return Standard.languageVersion(); - } - /** * Generates the package diagrams for all of the packages that contain classes * among those returned by RootDoc.class() */ - private static void generatePackageDiagrams(RootDoc root, Options opt, String outputFolder) throws IOException { - Set packages = new HashSet(); - for (ClassDoc classDoc : root.classes()) { - PackageDoc packageDoc = classDoc.containingPackage(); - if (!packages.contains(packageDoc.name())) { - packages.add(packageDoc.name()); + private static void generatePackageDiagrams(DocletEnvironment root, Reporter reporter, Options opt, String outputFolder) throws IOException { + Set packages = new HashSet<>(); + for (Element classDoc : root.getIncludedElements()) { + PackageElement packageDoc = null; + if (classDoc instanceof PackageElement) { + packageDoc = (PackageElement) classDoc; + } else if (classDoc instanceof TypeElement) { + packageDoc = ElementUtil.getPackageOf(root, classDoc); + } else { + continue; + } + if (!packages.contains(packageDoc.getSimpleName())) { + reporter.print(Diagnostic.Kind.WARNING, "Package processed " + packageDoc.getQualifiedName()); + packages.add(packageDoc.getSimpleName()); OptionProvider view = new PackageView(outputFolder, packageDoc, root, opt); - UmlGraph.buildGraph(root, view, packageDoc); - runGraphviz(opt.dotExecutable, outputFolder, packageDoc.name(), packageDoc.name(), root); - alterHtmlDocs(opt, outputFolder, packageDoc.name(), packageDoc.name(), "package-summary.html", - Pattern.compile("()|(

)|(

classDocs = new TreeSet(new Comparator() { - public int compare(ClassDoc cd1, ClassDoc cd2) { - return cd1.name().compareTo(cd2.name()); + private static void generateContextDiagrams(DocletEnvironment root, Reporter reporter, Options opt, String outputFolder) throws IOException { + Set classDocs = new TreeSet<>(new Comparator<>() { + public int compare(TypeElement cd1, TypeElement cd2) { + return cd1.getSimpleName().toString().compareTo(cd2.getSimpleName().toString()); } }); - for (ClassDoc classDoc : root.classes()) - classDocs.add(classDoc); + for (Element classDoc : root.getIncludedElements()) { + if (classDoc instanceof TypeElement) { + classDocs.add((TypeElement) classDoc); + } + } ContextView view = null; - for (ClassDoc classDoc : classDocs) { + for (TypeElement classDoc : classDocs) { + reporter.print(Diagnostic.Kind.WARNING, "Class processed " + classDoc.getQualifiedName()); try { - if (view == null) + if (view == null) { view = new ContextView(outputFolder, classDoc, root, opt); - else + } else { view.setContextCenter(classDoc); - UmlGraph.buildGraph(root, view, classDoc); - runGraphviz(opt.dotExecutable, outputFolder, classDoc.containingPackage().name(), classDoc.name(), - root); - alterHtmlDocs(opt, outputFolder, classDoc.containingPackage().name(), classDoc.name(), - classDoc.name() + ".html", - Pattern.compile(".*(Class|Interface|Enum) " + classDoc.name() + ".*"), root); + } + UmlGraph.buildGraph(reporter, root, opt, view, classDoc); + runGraphviz(opt.dotExecutable, outputFolder, ElementUtil.getModuleOf(root, classDoc), ElementUtil.getPackageOf(root, classDoc).getQualifiedName(), classDoc.getSimpleName(), reporter); + alterHtmlDocs(opt, outputFolder, ElementUtil.getModuleOf(root, classDoc), ElementUtil.getPackageOf(root, classDoc).getQualifiedName(), classDoc.getSimpleName(), + classDoc.getSimpleName() + ".html", + Pattern.compile(".*(Class|Interface|Enum) " + classDoc.getSimpleName() + ".*"), reporter); } catch (Exception e) { - throw new RuntimeException("Error generating " + classDoc.name(), e); + throw new RuntimeException("Error generating " + classDoc.getSimpleName(), e); } } } @@ -137,24 +173,29 @@ public class UmlGraphDoc { * Runs Graphviz dot building both a diagram (in png format) and a client side * map for it. */ - private static void runGraphviz(String dotExecutable, String outputFolder, String packageName, String name, - RootDoc root) { + private static void runGraphviz(String dotExecutable, String outputFolder, ModuleElement module, Name packageName, Name name, Reporter reporter) { if (dotExecutable == null) { dotExecutable = "dot"; } - File dotFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".dot"); - File svgFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".svg"); + String fileName = packageName.toString().replace(".", "/") + "/" + name; + if (Runtime.version().major() > 10 && module != null) { + fileName = module.getQualifiedName().toString() + '/' + fileName; + } + File dotFile = new File(outputFolder, fileName + ".dot"); + File svgFile = new File(outputFolder, fileName + ".svg"); try { Process p = Runtime.getRuntime().exec(new String[] { dotExecutable, "-Tsvg", "-o", svgFile.getAbsolutePath(), dotFile.getAbsolutePath() }); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream())); String line; - while ((line = reader.readLine()) != null) - root.printWarning(line); + while ((line = reader.readLine()) != null) { + reporter.print(Diagnostic.Kind.WARNING, line); + } int result = p.waitFor(); - if (result != 0) - root.printWarning("Errors running Graphviz on " + dotFile); + if (result != 0) { + reporter.print(Diagnostic.Kind.WARNING, "Errors running Graphviz on " + dotFile); + } } catch (Exception e) { e.printStackTrace(); System.err.println("Ensure that dot is in your path and that its path does not contain spaces"); @@ -186,10 +227,11 @@ public class UmlGraphDoc { * point, and inserts the diagram image reference and a client side map in that * point. */ - private static void alterHtmlDocs(Options opt, String outputFolder, String packageName, String className, - String htmlFileName, Pattern insertPointPattern, RootDoc root) throws IOException { + private static void alterHtmlDocs(Options opt, String outputFolder, ModuleElement module, Name packageName, Name className, + String htmlFileName, Pattern insertPointPattern, Reporter reporter) throws IOException { // setup files - File output = new File(outputFolder, packageName.replace(".", "/")); + String prefix = Runtime.version().major() > 10 && module != null ? module.getQualifiedName().toString() + '/' : ""; + File output = new File(outputFolder, prefix + packageName.toString().replace(".", "/")); File htmlFile = new File(output, htmlFileName); File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml"); if (!htmlFile.exists()) { @@ -213,12 +255,14 @@ public class UmlGraphDoc { matched = true; String tag; - if (opt.autoSize) + if (opt.autoSize) { tag = String.format(UML_AUTO_SIZED_DIV_TAG, className); - else + } else { tag = String.format(UML_DIV_TAG, className); - if (opt.collapsibleDiagrams) + } + if (opt.collapsibleDiagrams) { tag = String.format(EXPANDABLE_UML, tag, "Show UML class diagram", "Hide UML class diagram"); + } writer.write(""); writer.newLine(); writer.write(tag); @@ -226,10 +270,12 @@ public class UmlGraphDoc { } } } finally { - if (writer != null) + if (writer != null) { writer.close(); - if (reader != null) + } + if (reader != null) { reader.close(); + } } // if altered, delete old file and rename new one to the old file name @@ -237,20 +283,9 @@ public class UmlGraphDoc { htmlFile.delete(); alteredFile.renameTo(htmlFile); } else { - root.printNotice("Warning, could not find a line that matches the pattern '" - + insertPointPattern.pattern() + "'.\n Class diagram reference not inserted"); + reporter.print(Diagnostic.Kind.NOTE, "Warning, could not find a line that matches the pattern '" + + insertPointPattern.pattern() + "'.\n Class diagram reference not inserted"); alteredFile.delete(); } } - - /** - * Returns the output path specified on the javadoc options - */ - private static String findOutputPath(String[][] options) { - for (int i = 0; i < options.length; i++) { - if (options[i][0].equals("-d")) - return options[i][1]; - } - return "."; - } } diff --git a/src/main/java/org/umlgraph/doclet/View.java b/src/main/java/org/umlgraph/doclet/View.java index f3b2dd4..b58ecbe 100644 --- a/src/main/java/org/umlgraph/doclet/View.java +++ b/src/main/java/org/umlgraph/doclet/View.java @@ -23,9 +23,12 @@ import java.util.Map; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import com.sun.javadoc.ClassDoc; -import com.sun.javadoc.RootDoc; -import com.sun.javadoc.Tag; +import javax.lang.model.element.TypeElement; + +import org.umlgraph.doclet.util.TagUtil; + +import jdk.javadoc.doclet.DocletEnvironment; +import com.sun.source.util.DocTrees; /** * Contains the definition of a View. A View is a set of option overrides that @@ -44,32 +47,35 @@ import com.sun.javadoc.Tag; * */ public class View implements OptionProvider { - Map> optionOverrides = new LinkedHashMap>(); - ClassDoc viewDoc; + Map> optionOverrides = new LinkedHashMap<>(); + TypeElement viewDoc; OptionProvider provider; List globalOptions; - RootDoc root; + DocletEnvironment root; /** * Builds a view given the class that contains its definition */ - public View(RootDoc root, ClassDoc c, OptionProvider provider) { + public View(DocletEnvironment root, TypeElement c, OptionProvider provider) { this.viewDoc = c; this.provider = provider; this.root = root; - Tag[] tags = c.tags(); + Map> tags = TagUtil.getTags(root, viewDoc); ClassMatcher currMatcher = null; // parse options, get the global ones, and build a map of the // pattern matched overrides globalOptions = new ArrayList(); - for (int i = 0; i < tags.length; i++) { - if (tags[i].name().equals("@match")) { - currMatcher = buildMatcher(tags[i].text()); + if (tags.get("match") != null) { + for (String text : tags.get("match")) { + currMatcher = buildMatcher(text); if (currMatcher != null) { - optionOverrides.put(currMatcher, new ArrayList()); + optionOverrides.put(currMatcher, new ArrayList<>()); } - } else if (tags[i].name().equals("@opt")) { - String[] opts = StringUtil.tokenize(tags[i].text()); + } + } + if (tags.get("opt") != null) { + for (String text : tags.get("opt")) { + String[] opts = StringUtil.tokenize(text); opts[0] = "-" + opts[0]; if (currMatcher == null) { globalOptions.add(opts); @@ -118,14 +124,14 @@ public class View implements OptionProvider { // OptionProvider methods // ---------------------------------------------------------------- - public Options getOptionsFor(ClassDoc cd) { + public Options getOptionsFor(DocTrees dt, TypeElement cd) { Options localOpt = getGlobalOptions(); overrideForClass(localOpt, cd); - localOpt.setOptions(cd); + localOpt.setOptions(dt, cd); return localOpt; } - public Options getOptionsFor(String name) { + public Options getOptionsFor(CharSequence name) { Options localOpt = getGlobalOptions(); overrideForClass(localOpt, name); return localOpt; @@ -136,34 +142,42 @@ public class View implements OptionProvider { boolean outputSet = false; for (String[] opts : globalOptions) { - if (Options.matchOption(opts[0], "output")) + if (Options.matchOption(opts[0], "output")) { outputSet = true; + } go.setOption(opts); } - if (!outputSet) - go.setOption(new String[] { "output", viewDoc.name() + ".dot" }); + if (!outputSet) { + go.setOption(new String[] { "output", viewDoc.getSimpleName() + ".dot" }); + } return go; } - public void overrideForClass(Options opt, ClassDoc cd) { + public void overrideForClass(Options opt, TypeElement cd) { provider.overrideForClass(opt, cd); - for (ClassMatcher cm : optionOverrides.keySet()) - if (cm.matches(cd)) - for (String[] override : optionOverrides.get(cm)) + for (ClassMatcher cm : optionOverrides.keySet()) { + if (cm.matches(cd)) { + for (String[] override : optionOverrides.get(cm)) { opt.setOption(override); + } + } + } } - public void overrideForClass(Options opt, String className) { + public void overrideForClass(Options opt, CharSequence className) { provider.overrideForClass(opt, className); - for (ClassMatcher cm : optionOverrides.keySet()) - if (cm.matches(className)) - for (String[] override : optionOverrides.get(cm)) + for (ClassMatcher cm : optionOverrides.keySet()) { + if (cm.matches(className)) { + for (String[] override : optionOverrides.get(cm)) { opt.setOption(override); + } + } + } } public String getDisplayName() { - return "view " + viewDoc.name(); + return "view " + viewDoc.getSimpleName(); } } diff --git a/src/main/java/org/umlgraph/doclet/Visibility.java b/src/main/java/org/umlgraph/doclet/Visibility.java index 831d64b..5804de7 100644 --- a/src/main/java/org/umlgraph/doclet/Visibility.java +++ b/src/main/java/org/umlgraph/doclet/Visibility.java @@ -18,7 +18,10 @@ */ package org.umlgraph.doclet; -import com.sun.javadoc.ProgramElementDoc; +import java.util.Set; + +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; /** * Enumerates the possible visibilities in a Java program. For brevity, package @@ -35,14 +38,16 @@ public enum Visibility { this.symbol = symbol; } - public static Visibility get(ProgramElementDoc doc) { - if (doc.isPrivate()) + public static Visibility get(Element doc) { + Set mods = doc.getModifiers(); + if (mods.contains(Modifier.PRIVATE)) { return PRIVATE; - else if (doc.isPackagePrivate()) - return PACKAGE; - else if (doc.isProtected()) + } else if (mods.contains(Modifier.PROTECTED)) { return PROTECTED; - else + } else if (mods.contains(Modifier.PUBLIC)) { return PUBLIC; + } else { + return PACKAGE; + } } } diff --git a/src/main/java/org/umlgraph/doclet/util/CommentHelper.java b/src/main/java/org/umlgraph/doclet/util/CommentHelper.java new file mode 100644 index 0000000..a3ac18f --- /dev/null +++ b/src/main/java/org/umlgraph/doclet/util/CommentHelper.java @@ -0,0 +1,168 @@ +package org.umlgraph.doclet.util; + +import static com.sun.source.doctree.DocTree.Kind.CODE; + +import java.util.List; + +import com.sun.source.doctree.AttributeTree; +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.EndElementTree; +import com.sun.source.doctree.EntityTree; +import com.sun.source.doctree.LinkTree; +import com.sun.source.doctree.LiteralTree; +import com.sun.source.doctree.ReferenceTree; +import com.sun.source.doctree.SeeTree; +import com.sun.source.doctree.SerialTree; +import com.sun.source.doctree.StartElementTree; +import com.sun.source.doctree.TextTree; +import com.sun.source.doctree.UnknownBlockTagTree; +import com.sun.source.doctree.ValueTree; +import com.sun.source.doctree.AttributeTree.ValueKind; +import com.sun.source.util.SimpleDocTreeVisitor; + +public class CommentHelper { + public static final String SPACER = " "; + + public static String getText(List list) { + StringBuilder sb = new StringBuilder(); + for (DocTree dt : list) { + sb.append(getText0(dt)); + } + return sb.toString(); + } + + public static String getText(DocTree dt) { + return getText0(dt).toString(); + } + + private static StringBuilder getText0(DocTree dt) { + final StringBuilder sb = new StringBuilder(); + new SimpleDocTreeVisitor() { + @Override + public Void visitAttribute(AttributeTree node, Void p) { + sb.append(SPACER).append(node.getName()); + if (node.getValueKind() == ValueKind.EMPTY) { + return null; + } + + sb.append("="); + String quote; + switch (node.getValueKind()) { + case DOUBLE: + quote = "\""; + break; + case SINGLE: + quote = "\'"; + break; + default: + quote = ""; + break; + } + sb.append(quote); + node.getValue().stream().forEach((dt) -> { + dt.accept(this, null); + }); + sb.append(quote); + return null; + } + + @Override + public Void visitEndElement(EndElementTree node, Void p) { + sb.append(""); + return null; + } + + @Override + public Void visitEntity(EntityTree node, Void p) { + sb.append(node.toString()); + return null; + } + + @Override + public Void visitLink(LinkTree node, Void p) { + if (node.getReference() == null) { + return null; + } + + node.getReference().accept(this, null); + node.getLabel().stream().forEach((dt) -> { + dt.accept(this, null); + }); + return null; + } + + @Override + public Void visitLiteral(LiteralTree node, Void p) { + if (node.getKind() == CODE) { + sb.append("<").append(node.getKind().tagName).append(">"); + } + sb.append(node.getBody().toString()); + if (node.getKind() == CODE) { + sb.append(""); + } + return null; + } + + @Override + public Void visitReference(ReferenceTree node, Void p) { + sb.append(node.getSignature()); + return null; + } + + @Override + public Void visitSee(SeeTree node, Void p) { + node.getReference().stream().forEach((dt) -> { + dt.accept(this, null); + }); + return null; + } + + @Override + public Void visitSerial(SerialTree node, Void p) { + node.getDescription().stream().forEach((dt) -> { + dt.accept(this, null); + }); + return null; + } + + @Override + public Void visitStartElement(StartElementTree node, Void p) { + sb.append("<"); + sb.append(node.getName()); + node.getAttributes().stream().forEach((dt) -> { + dt.accept(this, null); + }); + sb.append((node.isSelfClosing() ? "/>" : ">")); + return null; + } + + @Override + public Void visitText(TextTree node, Void p) { + sb.append(node.getBody()); + return null; + } + + @Override + public Void visitUnknownBlockTag(UnknownBlockTagTree node, Void p) { + node.getContent().stream().forEach((dt) -> { + dt.accept(this, null); + }); + return null; + } + + @Override + public Void visitValue(ValueTree node, Void p) { + return node.getReference().accept(this, null); + } + + @Override + protected Void defaultAction(DocTree node, Void p) { + sb.append(node.toString()); + return null; + } + }.visit(dt, null); + return sb; + } +} diff --git a/src/main/java/org/umlgraph/doclet/util/ElementUtil.java b/src/main/java/org/umlgraph/doclet/util/ElementUtil.java new file mode 100644 index 0000000..e00c884 --- /dev/null +++ b/src/main/java/org/umlgraph/doclet/util/ElementUtil.java @@ -0,0 +1,159 @@ +package org.umlgraph.doclet.util; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.ExecutableElement; +import javax.lang.model.element.ModuleElement; +import javax.lang.model.element.PackageElement; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.type.ArrayType; +import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.TypeKind; +import javax.lang.model.type.TypeMirror; +import javax.lang.model.util.ElementFilter; +import javax.lang.model.util.Elements; +import javax.lang.model.util.Types; + +import jdk.javadoc.doclet.DocletEnvironment; + +public class ElementUtil { + + public static CharSequence getSimpleName(Types types, TypeMirror t) { + if (t instanceof ArrayType) { + return getSimpleName(types, ((ArrayType) t).getComponentType()) + "[]"; + } + if (t.getKind().isPrimitive()) { + return t.getKind().name().toLowerCase(); + } + Element element = types.asElement(t); + if (element != null) { + return element.getSimpleName(); + } + return ""; + } + + public static CharSequence getQualifiedName(Types types, TypeMirror t) { + if (t instanceof ArrayType) { + return getQualifiedName(types, ((ArrayType) t).getComponentType()) + "[]"; + } + if (t.getKind().isPrimitive()) { + return t.getKind().name().toLowerCase(); + } + Element element = types.asElement(t); + if (element instanceof TypeElement) { + return ((TypeElement) element).getQualifiedName(); + } + return ""; + } + + public static TypeElement containingTypeElement(Element c) { + if (c instanceof VariableElement) { + Element enclosing = c.getEnclosingElement(); + return enclosing instanceof TypeElement ? (TypeElement) enclosing : null; + } else if (c instanceof ExecutableElement) { + Element enclosing = c.getEnclosingElement(); + return enclosing instanceof TypeElement ? (TypeElement) enclosing : null; + } + if (c == null) { + System.err.println("containingElement will return null for null element"); + return null; + } + System.err.println("containingElement will return null for kind " + c.getKind() + " class " + c.getClass() + " name " + c.getSimpleName()); + return null; + } + + public static TypeElement getTypeElement(TypeMirror clazz) { + if (clazz == null || clazz.getKind() == TypeKind.NONE) { + return null; + } + if (clazz instanceof ArrayType) { + return getTypeElement(((ArrayType) clazz).getComponentType()); + } + if (!(clazz instanceof DeclaredType)) { + return null; + } + Element scd = ((DeclaredType) clazz).asElement(); + return scd instanceof TypeElement ? (TypeElement) scd : null; + } + + public static TypeMirror getSuperclassType(DeclaredType pt) { + return pt.asElement() instanceof TypeElement ? ((TypeElement) pt.asElement()).getSuperclass() : null; + } + + public static TypeElement getSuperclass(TypeElement element) { + return getTypeElement(element.getSuperclass()); + } + + public static List getInterfacesTypes(TypeElement element) { + return element.getInterfaces(); + } + + public static List getInterfaces(TypeElement element) { + List interfaces = getInterfacesTypes(element); + return interfaces.stream().map(i -> getTypeElement(i)).filter(i -> i != null).collect(Collectors.toList()); + } + + public static ModuleElement getModuleOf(DocletEnvironment root, Element element) { + return getModuleOf(root.getElementUtils(), element); + } + + public static ModuleElement getModuleOf(Elements elements, Element element) { + if (element == null) { + return null; + } + if (element instanceof ModuleElement) { + return (ModuleElement) element; + } + return elements.getModuleOf(element); + } + + public static PackageElement getPackageOf(DocletEnvironment root, Element element) { + return getPackageOf(root.getElementUtils(), element); + } + + public static PackageElement getPackageOf(Elements elements, Element element) { + if (element == null) { + return null; + } + if (element instanceof PackageElement) { + return (PackageElement) element; + } + return elements.getPackageOf(element); + } + + public static List getFields(TypeElement element) { + List enclosed = element.getEnclosedElements(); + return ElementFilter.fieldsIn(enclosed).stream().filter(v -> v.getKind() == ElementKind.FIELD).collect(Collectors.toList()); + } + + public static List getEnumConstants(TypeElement element) { + List enclosed = element.getEnclosedElements(); + return ElementFilter.fieldsIn(enclosed).stream().filter(v -> v.getKind() == ElementKind.ENUM_CONSTANT).collect(Collectors.toList()); + } + + public static List getMethods(TypeElement element) { + List enclosed = element.getEnclosedElements(); + return ElementFilter.methodsIn(enclosed); + } + + public static List getConstructors(TypeElement element) { + List enclosed = element.getEnclosedElements(); + return ElementFilter.constructorsIn(enclosed); + } + + public static String dimensions(TypeMirror type) { + StringBuilder sb = new StringBuilder(); + + // First append root component type + TypeMirror t = type; + while (t.getKind() == TypeKind.ARRAY) { + sb.append("[]"); + t = ((ArrayType) t).getComponentType(); + } + return sb.toString(); + } +} diff --git a/src/main/java/org/umlgraph/doclet/util/TagUtil.java b/src/main/java/org/umlgraph/doclet/util/TagUtil.java new file mode 100644 index 0000000..e1c1c02 --- /dev/null +++ b/src/main/java/org/umlgraph/doclet/util/TagUtil.java @@ -0,0 +1,50 @@ +package org.umlgraph.doclet.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.lang.model.element.Element; + +import org.umlgraph.doclet.TagScanner; + +import com.sun.source.doctree.DocCommentTree; +import com.sun.source.util.DocTrees; + +import jdk.javadoc.doclet.DocletEnvironment; + +public class TagUtil { + + public static Map> getTags(DocletEnvironment root, Element c) { + return getTags(root.getDocTrees(), c); + } + + public static Map> getTags(DocTrees docTrees, Element c) { + DocCommentTree tree = docTrees.getDocCommentTree(c); + Map> tagsByName = new HashMap<>(); + new TagScanner(tagsByName).visitDocComment(tree, null); + return tagsByName; + } + + public static String getComment(DocletEnvironment root, Element c) { + return getComment(root.getDocTrees(), c); + } + + public static String getComment(DocTrees docTrees, Element c) { + List comments = getTag(docTrees, c, "comment"); + if (comments == null || comments.isEmpty()) { + return ""; + } + return comments.get(0); + } + + public static List getTag(DocletEnvironment root, Element c, String tagName) { + return getTag(root.getDocTrees(), c, tagName); + } + + public static List getTag(DocTrees docTrees, Element c, String tagName) { + List tags = getTags(docTrees, c).get(tagName); + return tags == null ? Collections.emptyList() : tags; + } +}