version running jdk9

This commit is contained in:
Laurent SCHOELENS 2023-03-24 11:13:30 +01:00
parent c8f805bdef
commit cd8c3629a1
26 changed files with 2091 additions and 721 deletions

1
.gitignore vendored
View File

@ -3,6 +3,7 @@
*.ipr *.ipr
*.iws *.iws
*.swp *.swp
.idea
.settings .settings
.project .project
.classpath .classpath

94
pom.xml
View File

@ -6,7 +6,7 @@
<artifactId>umlgraph</artifactId> <artifactId>umlgraph</artifactId>
<packaging>jar</packaging> <packaging>jar</packaging>
<name>UMLGraph</name> <name>UMLGraph</name>
<version>5.7.3-SNAPSHOT</version> <version>6.0.0-SNAPSHOT</version>
<description>Declarative Drawing of UML Diagrams</description> <description>Declarative Drawing of UML Diagrams</description>
<url>http://www.spinellis.gr/umlgraph</url> <url>http://www.spinellis.gr/umlgraph</url>
@ -40,19 +40,9 @@
<parent> <parent>
<groupId>org.sonatype.oss</groupId> <groupId>org.sonatype.oss</groupId>
<artifactId>oss-parent</artifactId> <artifactId>oss-parent</artifactId>
<version>7</version> <version>9</version>
</parent> </parent>
<dependencies>
<dependency>
<groupId>sun.jdk</groupId>
<artifactId>tools</artifactId>
<version>1.5.0</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>
</dependencies>
<build> <build>
<resources> <resources>
<resource> <resource>
@ -63,15 +53,16 @@
<plugins> <plugins>
<plugin> <plugin>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version> <version>3.10.1</version>
<configuration> <configuration>
<source>1.5</source> <source>9</source>
<target>1.5</target> <target>9</target>
</configuration> </configuration>
</plugin> <plugin> </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version> <version>3.3.0</version>
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
@ -84,18 +75,18 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId> <artifactId>maven-release-plugin</artifactId>
<version>2.5</version> <version>3.0.0-M7</version>
<configuration> <configuration>
<autoVersionSubmodules>true</autoVersionSubmodules> <autoVersionSubmodules>true</autoVersionSubmodules>
<useReleaseProfile>false</useReleaseProfile> <useReleaseProfile>false</useReleaseProfile>
<releaseProfiles>release</releaseProfiles> <releaseProfiles>release</releaseProfiles>
<goals>deploy</goals> <goals>deploy</goals>
</configuration> </configuration>
</plugin> </plugin>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId> <artifactId>maven-source-plugin</artifactId>
<version>2.2.1</version> <version>3.2.1</version>
<executions> <executions>
<execution> <execution>
<id>attach-sources</id> <id>attach-sources</id>
@ -108,36 +99,39 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId> <artifactId>maven-javadoc-plugin</artifactId>
<version>2.9</version> <version>3.5.0</version>
<configuration> <configuration>
<tags> <tags>
<tag> <tag>
<name>depend</name> <name>depend</name>
<placement>X</placement> <placement>X</placement>
</tag> </tag>
<tag> <tag>
<name>hidden</name> <name>hidden</name>
<placement>X</placement> <placement>X</placement>
</tag> </tag>
<tag> <tag>
<name>opt</name> <name>opt</name>
<placement>X</placement> <placement>X</placement>
</tag> </tag>
</tags> </tags>
<doclet>org.umlgraph.doclet.UmlGraphDoc</doclet> <doclet>org.umlgraph.doclet.UmlGraphDoc</doclet>
<docletPath>${project.build.directory}${file.separator}${project.build.finalName}.jar</docletPath> <docletPath>${project.build.directory}${file.separator}${project.build.finalName}.jar</docletPath>
<additionalparam>-inferrel</additionalparam> <additionalOptions>
<additionalparam>-inferdep</additionalparam> <option>-inferrel</option>
<additionalparam>-autosize</additionalparam> <option>-inferdep</option>
<additionalparam>-collapsible</additionalparam> <option>-autosize</option>
<additionalparam>-hide java.*</additionalparam> <option>-collapsible</option>
<additionalparam>-collpackages</additionalparam> <option>--hide="java.*"</option>
<additionalparam>-qualify</additionalparam> <option>--collpackages=</option>
<additionalparam>-postfixpackage</additionalparam> <option>-qualify</option>
<additionalparam>-nodefontsize 9</additionalparam> <option>-all</option>
<additionalparam>-nodefontpackagesize 7</additionalparam> <option>-postfixpackage</option>
<additionalparam>-link http://docs.oracle.com/javase/7/docs/jdk/api/javadoc/doclet/</additionalparam> <option>-nodefontsize 9</option>
<additionalparam>-link http://download.oracle.com/javase/7/docs/api/</additionalparam> <option>-nodefontpackagesize 7</option>
<option>--link="https://docs.oracle.com/javase/7/docs/jdk/api/javadoc/doclet/"</option>
<option>--link="https://download.oracle.com/javase/7/docs/api/"</option>
</additionalOptions>
<useStandardDocletOptions>true</useStandardDocletOptions> <useStandardDocletOptions>true</useStandardDocletOptions>
</configuration> </configuration>
<executions> <executions>

View File

@ -0,0 +1,6 @@
module org.umlgraph.doclet {
exports org.umlgraph.doclet;
opens org.umlgraph.doclet;
requires transitive jdk.javadoc;
}

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,8 @@ package org.umlgraph.doclet;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javax.lang.model.element.Name;
/** /**
* Class's dot-compatible alias name (for fully qualified class names) and * Class's dot-compatible alias name (for fully qualified class names) and
* printed information * printed information
@ -42,7 +44,7 @@ class ClassInfo {
* classes linked with a bi-directional relation , and the ones referred by a * classes linked with a bi-directional relation , and the ones referred by a
* directed relation * directed relation
*/ */
Map<String, RelationPattern> relatedClasses = new HashMap<String, RelationPattern>(); Map<Name, RelationPattern> relatedClasses = new HashMap<>();
ClassInfo(boolean h) { ClassInfo(boolean h) {
hidden = h; hidden = h;
@ -50,7 +52,7 @@ class ClassInfo {
classNumber++; classNumber++;
} }
public void addRelation(String dest, RelationType rt, RelationDirection d) { public void addRelation(Name dest, RelationType rt, RelationDirection d) {
RelationPattern ri = relatedClasses.get(dest); RelationPattern ri = relatedClasses.get(dest);
if (ri == null) { if (ri == null) {
ri = new RelationPattern(RelationDirection.NONE); ri = new RelationPattern(RelationDirection.NONE);
@ -59,7 +61,7 @@ class ClassInfo {
ri.addRelation(rt, d); ri.addRelation(rt, d);
} }
public RelationPattern getRelation(String dest) { public RelationPattern getRelation(CharSequence dest) {
return relatedClasses.get(dest); return relatedClasses.get(dest);
} }

View File

@ -17,7 +17,7 @@
package org.umlgraph.doclet; 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 * 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. * Returns the options for the specified class.
*/ */
public boolean matches(ClassDoc cd); public boolean matches(TypeElement cd);
/** /**
* Returns the options for the specified class. * Returns the options for the specified class.
*/ */
public boolean matches(String name); public boolean matches(CharSequence name);
} }

View File

@ -26,8 +26,10 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.RootDoc;
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 * 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 { public class ContextMatcher implements ClassMatcher {
ClassGraphHack cg; ClassGraphHack cg;
Pattern pattern; Pattern pattern;
List<ClassDoc> matched; List<TypeElement> matched;
Set<String> visited = new HashSet<String>(); Set<String> visited = new HashSet<>();
/** The options will be used to decide on inference */ /** The options will be used to decide on inference */
Options opt; Options opt;
RootDoc root; DocletEnvironment root;
boolean keepParentHide; boolean keepParentHide;
/** /**
@ -64,7 +66,7 @@ public class ContextMatcher implements ClassMatcher {
* the context * the context
* @throws IOException * @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.pattern = pattern;
this.root = root; this.root = root;
this.keepParentHide = keepParentHide; this.keepParentHide = keepParentHide;
@ -89,11 +91,11 @@ public class ContextMatcher implements ClassMatcher {
// build up the classgraph printing the relations for all of the // build up the classgraph printing the relations for all of the
// classes that make up the "center" of this context // classes that make up the "center" of this context
this.pattern = pattern; this.pattern = pattern;
matched = new ArrayList<ClassDoc>(); matched = new ArrayList<>();
for (ClassDoc cd : root.classes()) { for (Element cd : root.getIncludedElements()) {
if (pattern.matcher(cd.toString()).matches()) { if (cd instanceof TypeElement && pattern.matcher(cd.toString()).matches()) {
matched.add(cd); matched.add((TypeElement) cd);
addToGraph(cd); addToGraph((TypeElement) cd);
} }
} }
} }
@ -105,31 +107,36 @@ public class ContextMatcher implements ClassMatcher {
* *
* @param cd * @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 // 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 // since there are other ways to add a classInfor than printing the class
if (visited.contains(cd.toString())) if (visited.contains(cd.toString())) {
return; return;
}
visited.add(cd.toString()); visited.add(cd.toString());
cg.printClass(cd, false); cg.printClass(cd, false);
cg.printRelations(cd); cg.printRelations(cd);
if (opt.inferRelationships) if (opt.inferRelationships) {
cg.printInferredRelations(cd); cg.printInferredRelations(cd);
if (opt.inferDependencies) }
if (opt.inferDependencies) {
cg.printInferredDependencies(cd); 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) { public boolean matches(TypeElement cd) {
if (keepParentHide && opt.matchesHideExpression(cd.toString())) if (keepParentHide && opt.matchesHideExpression(cd.getQualifiedName())) {
return false; return false;
}
// if the class is matched, it's in by default. // if the class is matched, it's in by default.
if (matched.contains(cd)) if (matched.contains(cd)) {
return true; return true;
}
// otherwise, add the class to the graph and see if it's associated // otherwise, add the class to the graph and see if it's associated
// with any of the matched classes using the classgraph hack // 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) { public boolean matches(CharSequence name) {
if (pattern.matcher(name).matches()) if (pattern.matcher(name).matches()) {
return true; return true;
}
for (ClassDoc mcd : matched) { for (TypeElement mcd : matched) {
RelationPattern rp = cg.getClassInfo(mcd, true).getRelation(name); 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 true;
}
} }
return false; return false;
} }
@ -162,7 +171,7 @@ public class ContextMatcher implements ClassMatcher {
*/ */
private static class ClassGraphHack extends ClassGraph { 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); super(root, optionProvider, null);
prologue(); prologue();
} }

View File

@ -3,8 +3,14 @@ package org.umlgraph.doclet;
import java.io.IOException; import java.io.IOException;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.RootDoc;
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 * 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 { public class ContextView implements OptionProvider {
private ClassDoc cd; private TypeElement cd;
private DocletEnvironment root;
private ContextMatcher matcher; private ContextMatcher matcher;
private Options globalOptions; private Options globalOptions;
private Options myGlobalOptions; private Options myGlobalOptions;
@ -29,9 +36,12 @@ public class ContextView implements OptionProvider {
private Options packageOptions; private Options packageOptions;
private static final String[] HIDE_OPTIONS = new String[] { "hide" }; 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; 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 // setup options statically, so that we won't need to change them so
// often // often
@ -51,13 +61,15 @@ public class ContextView implements OptionProvider {
this.centerOptions.nodeFillColor = "lemonChiffon"; this.centerOptions.nodeFillColor = "lemonChiffon";
this.centerOptions.showQualified = false; 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; 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 }); this.myGlobalOptions.setOption(new String[] { "output", outputPath });
matcher.setContextCenter(Pattern.compile(Pattern.quote(cd.toString()))); matcher.setContextCenter(Pattern.compile(Pattern.quote(cd.toString())));
} }
@ -70,14 +82,14 @@ public class ContextView implements OptionProvider {
return myGlobalOptions; return myGlobalOptions;
} }
public Options getOptionsFor(ClassDoc cd) { public Options getOptionsFor(DocTrees dt, TypeElement cd) {
Options opt; Options opt;
if (globalOptions.matchesHideExpression(cd.qualifiedName()) if (globalOptions.matchesHideExpression(cd.getQualifiedName())
|| !(matcher.matches(cd) || globalOptions.matchesIncludeExpression(cd.qualifiedName()))) { || !(matcher.matches(cd) || globalOptions.matchesIncludeExpression(cd.getQualifiedName()))) {
opt = hideOptions; opt = hideOptions;
} else if (cd.equals(this.cd)) { } else if (cd.equals(this.cd)) {
opt = centerOptions; opt = centerOptions;
} else if (cd.containingPackage().equals(this.cd.containingPackage())) { } else if (root.getElementUtils().getPackageOf(cd).equals(root.getElementUtils().getPackageOf(this.cd))) {
opt = packageOptions; opt = packageOptions;
} else { } else {
opt = globalOptions; opt = globalOptions;
@ -87,31 +99,35 @@ public class ContextView implements OptionProvider {
return optionClone; return optionClone;
} }
public Options getOptionsFor(String name) { public Options getOptionsFor(CharSequence name) {
Options opt; Options opt;
if (!matcher.matches(name)) if (!matcher.matches(name)) {
opt = hideOptions; opt = hideOptions;
else if (name.equals(cd.name())) } else if (name.equals(cd.getQualifiedName())) {
opt = centerOptions; opt = centerOptions;
else } else {
opt = globalOptions; opt = globalOptions;
}
Options optionClone = (Options) opt.clone(); Options optionClone = (Options) opt.clone();
overrideForClass(optionClone, name); overrideForClass(optionClone, name);
return optionClone; return optionClone;
} }
public void overrideForClass(Options opt, ClassDoc cd) { public void overrideForClass(Options opt, TypeElement cd) {
opt.setOptions(cd); opt.setOptions(root.getDocTrees(), cd);
if (opt.matchesHideExpression(cd.qualifiedName()) if (opt.matchesHideExpression(cd.getQualifiedName())
|| !(matcher.matches(cd) || opt.matchesIncludeExpression(cd.qualifiedName()))) || !(matcher.matches(cd) || opt.matchesIncludeExpression(cd.getQualifiedName()))) {
opt.setOption(HIDE_OPTIONS); opt.setOption(HIDE_OPTIONS);
if (cd.equals(this.cd)) }
if (cd.equals(this.cd)) {
opt.nodeFillColor = "lemonChiffon"; opt.nodeFillColor = "lemonChiffon";
}
} }
public void overrideForClass(Options opt, String className) { public void overrideForClass(Options opt, CharSequence className) {
if (!(matcher.matches(className) || opt.matchesIncludeExpression(className))) if (!(matcher.matches(className) || opt.matchesIncludeExpression(className))) {
opt.setOption(HIDE_OPTIONS); opt.setOption(HIDE_OPTIONS);
}
} }
} }

View File

@ -4,7 +4,7 @@ package org.umlgraph.doclet;
* Class to represent a font for graphviz. * Class to represent a font for graphviz.
* <p> * <p>
* This is a fairly complicated model, because it is rather an API into graphviz * This is a fairly complicated model, because it is rather an API into graphviz
* formatting strings rather than a standalone thing. <p Some fonts (edge, node, * formatting strings rather than a standalone thing. <p> Some fonts (edge, node,
* abstract) are set on the top level elements, whereas others (class name, tag, * abstract) are set on the top level elements, whereas others (class name, tag,
* package) are inserted as {@code <font>} tags, and these can be omitted if not * package) are inserted as {@code <font>} tags, and these can be omitted if not
* set. Inheritance of properties then happens in graphviz. * set. Inheritance of properties then happens in graphviz.

View File

@ -2,8 +2,13 @@ package org.umlgraph.doclet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.RootDoc;
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 * Matches every class that implements (directly or indirectly) an interfaces
@ -11,31 +16,36 @@ import com.sun.javadoc.RootDoc;
*/ */
public class InterfaceMatcher implements ClassMatcher { public class InterfaceMatcher implements ClassMatcher {
protected RootDoc root; protected DocletEnvironment root;
protected Pattern pattern; protected Pattern pattern;
public InterfaceMatcher(RootDoc root, Pattern pattern) { public InterfaceMatcher(DocletEnvironment root, Pattern pattern) {
this.root = root; this.root = root;
this.pattern = pattern; this.pattern = pattern;
} }
public boolean matches(ClassDoc cd) { public boolean matches(TypeElement cd) {
// if it's the interface we're looking for, match // 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; return true;
}
// for each interface, recurse, since classes and interfaces // for each interface, recurse, since classes and interfaces
// are treated the same in the doclet API // are treated the same in the doclet API
for (ClassDoc iface : cd.interfaces()) for (TypeMirror type : cd.getInterfaces()) {
if (matches(iface)) TypeElement iType = ElementUtil.getTypeElement(type);
if (iType != null && matches(iType)) {
return true; return true;
}
}
// recurse on supeclass, if available // recurse on superclass, if available
return cd.superclass() == null ? false : matches(cd.superclass()); TypeElement scd = ElementUtil.getTypeElement(cd.getSuperclass());
return scd == null ? false : matches(scd);
} }
public boolean matches(String name) { public boolean matches(CharSequence name) {
ClassDoc cd = root.classNamed(name); TypeElement cd = root.getElementUtils().getTypeElement(name);
return cd == null ? false : matches(cd); return cd == null ? false : matches(cd);
} }

View File

@ -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<String> getNames() {
return List.of(name);
}
@Override
public String getParameters() {
return argumentCount == 0 ? "" : parameters;
}
}

View File

@ -17,22 +17,23 @@
package org.umlgraph.doclet; 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 * A factory class that builds Options object for general use or for a specific class
* class
*/ */
public interface OptionProvider { public interface OptionProvider {
/** /**
* Returns the options for the specified class. * 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. * 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) * 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 * 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 * 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. * Returns user displayable name for this option provider.

View File

@ -29,18 +29,26 @@ import java.io.InputStreamReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.net.URL; import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
import com.sun.javadoc.ClassDoc; import javax.lang.model.element.Element;
import com.sun.javadoc.Doc; import javax.lang.model.element.Name;
import com.sun.javadoc.Tag; 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 * Represent the program options
@ -49,13 +57,550 @@ import com.sun.javadoc.Tag;
* @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a> * @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a>
*/ */
public class Options implements Cloneable, OptionProvider { public class Options implements Cloneable, OptionProvider {
public final Set<? extends Doclet.Option> OPTIONS = Set.of(
new Option("--d", true, "Specify the output directory (defaults to the current directory).", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
outputDirectory = arguments.get(0);
return true;
}
},
new Option("-qualify", false, "Produce fully-qualified class names.", null) {
@Override
public boolean process(String option, List<String> 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<String> arguments) {
showQualifiedGenerics = true;
return true;
}
},
new Option("-hideGenerics", false, "FIXME Missing doc", null) {
@Override
public boolean process(String option, List<String> arguments) {
hideGenerics = true;
return true;
}
},
new Option("-horizontal", false, "Layout the graph in the horizontal direction.", null) {
@Override
public boolean process(String option, List<String> arguments) {
horizontal = true;
return true;
}
},
new Option("-attributes", false, "Show class attributes (Java fields)", null) {
@Override
public boolean process(String option, List<String> 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<String> arguments) {
showEnumConstants = true;
return true;
}
},
new Option("-operations", false, "Show class operations (Java methods)", null) {
@Override
public boolean process(String option, List<String> arguments) {
showOperations = true;
return true;
}
},
new Option("-enumerations", false, "Show enumarations as separate stereotyped primitive types", null) {
@Override
public boolean process(String option, List<String> arguments) {
showEnumerations = true;
return true;
}
},
new Option("-constructors", false, "Show a class's constructors", null) {
@Override
public boolean process(String option, List<String> 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<String> arguments) {
showVisibility = true;
return true;
}
},
new Option("-types", false, "Add type information to attributes and operations", null) {
@Override
public boolean process(String option, List<String> 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<String> 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<String> 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<String> arguments) {
setAll();
return true;
}
},
new Option("--bgcolor", true, "Specify the graph's background color.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
bgColor = arguments.get(0);
return true;
}
},
new Option("--edgecolor", true, "Specify the color for drawing edges.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
edgeColor = arguments.get(0);
return true;
}
},
new Option("--edgefontcolor", true, "Specify the font color to use for edge labels.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
edgeFontColor = arguments.get(0);
return true;
}
},
new Option("--edgefontname", true, "Specify the font name to use for edge labels.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
edgeFontName = arguments.get(0);
return true;
}
},
new Option("-edgefontsize", true, "Specify the font size to use for edge labels.", "<double>") {
@Override
public boolean process(String option, List<String> arguments) {
edgeFontSize = Double.parseDouble(arguments.get(0));
return true;
}
},
new Option("-nodefontcolor", true, "Specify the font color to use inside nodes", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontColor = arguments.get(0);
return true;
}
},
new Option("-nodefontname", true, "Specify the font name to use inside nodes", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontName = arguments.get(0);
return true;
}
},
new Option("-nodefontabstractitalic", false, "FIXME no documentation", null) {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontAbstractItalic = true;
return true;
}
},
new Option("-nodefontsize", true, "Specify the font size to use inside nodes", "<double>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontSize = Double.parseDouble(arguments.get(0));
return true;
}
},
new Option("-nodefontclassname", true, "Specify the font name to use for the class names", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontClassName = arguments.get(0);
return true;
}
},
new Option("-nodefontclasssize", true, "Specify the font size to use for the class names.", "<double>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontClassSize = Double.parseDouble(arguments.get(0));
return true;
}
},
new Option("-nodefonttagname", true, "Specify the font name to use for the tag names.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontTagName = arguments.get(0);
return true;
}
},
new Option("-nodefonttagsize", true, "Specify the font size to use for the tag names", "<double>") {
@Override
public boolean process(String option, List<String> 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).", "<string>") {
@Override
public boolean process(String option, List<String> 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).", "<double>") {
@Override
public boolean process(String option, List<String> arguments) {
nodeFontPackageSize = Double.parseDouble(arguments.get(0));
return true;
}
},
new Option("-nodefillcolor", true, "Specify the color to use to fill the shapes", "<string>") {
@Override
public boolean process(String option, List<String> 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", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
shape = Shape.of(arguments.get(0));
return true;
}
},
new Option("-output", true, "Specify the output file (default <code>graph.dot</code>).\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 <em>dot</em>.\n"
+ "Note that, in order to avoid <em>javadoc</em> messages to contaminate\n"
+ "UMLGraph's output, you must execute UMLGraph directly as a jar,\n"
+ "not through <em>javadoc</em>.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
outputFileName = arguments.get(0);
return true;
}
},
new Option("-outputencoding", true, "Specify the output encoding character set (default <code>UTF-8</code>).\n"
+ "When using <em>dot</em> to generate SVG diagrams you should specify\n"
+ "<code>UTF-8</code> as the output encoding, to have guillemots correctly\n"
+ "appearing in the resulting SVG.", "<string>") {
@Override
public boolean process(String option, List<String> 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, \"<code>-hide (Big|\\.)Widget</code>\" would hide \"<code>com.foo.widgets.Widget</code>\" and "
+ " \"<code>com.foo.widgets.BigWidget</code>\". 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).", "<pattern>") {
@Override
public boolean process(String option, List<String> 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 <code>-hide</code> 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).", "<pattern>") {
@Override
public boolean process(String option, List<String> 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 <code>http://www.acme.org/apidocs</code> is\n"
+ "provided, the class <code>org.acme.util.MyClass</code> will be mapped to the URL\n"
+ "<code>http://www.acme.org/apidocs/org/acme/util/MyClass.html</code>.\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.", "<string>") {
@Override
public boolean process(String option, List<String> 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.", "<string>") {
@Override
public boolean process(String option, List<String> 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<String> arguments) {
guilOpen = "&lt;&lt;";
guilClose = "&gt;&gt;";
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"
+ " <br/>See the views chapter for more details.", "<string>") {
@Override
public boolean process(String option, List<String> 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<String> 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<String> 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).", "<RelationType>") {
@Override
public boolean process(String option, List<String> 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", "<Visibility>") {
@Override
public boolean process(String option, List<String> 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<String> 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<String> 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<String> arguments) {
inferDepInPackage = true;
return true;
}
},
new Option("-hideprivateinner", false, "FIXME NO DOC", null) {
@Override
public boolean process(String option, List<String> 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<String> 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.", "<string>") {
@Override
public boolean process(String option, List<String> 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<String> 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<String> arguments) {
postfixPackage = true;
return true;
}
},
new Option("-hideGenerics", false, "?", null) {
@Override
public boolean process(String option, List<String> arguments) {
hideGenerics = true;
return true;
}
},
new Option("--link", true, "A clone of the standard doclet\n"
+ "<a href=\"http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/javadoc.html#link\">-link</a>\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).", "<string>") {
@Override
public boolean process(String option, List<String> 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 <code>-linkoffline</code> option takes two arguments:\n"
+ "the first for the string to be embedded in the <code>href</code>\n"
+ "links, the second telling it where to find the <code>package-list</code>.\n"
+ "Example:\n"
+ "<pre>\n"
+ "-linkoffline http://developer.android.com/reference file:/home/doc/android/\n"
+ "</pre>\n"
+ "See the <em>javadoc</em> documentation for more details.", "<string> <string>") {
@Override
public boolean process(String option, List<String> arguments) {
addApiDocRootsOffline(arguments.get(0), arguments.get(1));
return true;
}
},
new Option("-contextPattern", 2, "FIXME MISSING DOC.", "<string> <string>") {
@Override
public boolean process(String option, List<String> 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.", "<double>") {
@Override
public boolean process(String option, List<String> 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.", "<double>") {
@Override
public boolean process(String option, List<String> 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 <em>dot</em> executable.", "<string>") {
@Override
public boolean process(String option, List<String> arguments) {
dotExecutable = arguments.get(0);
return true;
}
}
);
// reused often, especially in UmlGraphDoc, worth creating just once and reusing // reused often, especially in UmlGraphDoc, worth creating just once and reusing
private static final Pattern allPattern = Pattern.compile(".*"); private static final Pattern allPattern = Pattern.compile(".*");
protected static final String DEFAULT_EXTERNAL_APIDOC = "http://docs.oracle.com/javase/7/docs/api/"; protected static final String DEFAULT_EXTERNAL_APIDOC = "http://docs.oracle.com/javase/7/docs/api/";
// instance fields // instance fields
List<Pattern> hidePatterns = new ArrayList<Pattern>(); List<Pattern> hidePatterns = new ArrayList<>();
List<Pattern> includePatterns = new ArrayList<Pattern>(); List<Pattern> includePatterns = new ArrayList<>();
boolean showQualified = false; boolean showQualified = false;
boolean showQualifiedGenerics = false; boolean showQualifiedGenerics = false;
boolean hideGenerics = false; boolean hideGenerics = false;
@ -87,8 +632,8 @@ public class Options implements Cloneable, OptionProvider {
Shape shape = Shape.CLASS; Shape shape = Shape.CLASS;
String bgColor = null; String bgColor = null;
public String outputFileName = "graph.dot"; public String outputFileName = "graph.dot";
String outputEncoding = "ISO-8859-1"; // TODO: default to UTF-8 now? String outputEncoding = StandardCharsets.UTF_8.name();
Map<Pattern, String> apiDocMap = new HashMap<Pattern, String>(); Map<Pattern, String> apiDocMap = new HashMap<>();
String apiDocRoot = null; String apiDocRoot = null;
boolean postfixPackage = false; boolean postfixPackage = false;
boolean useGuillemot = true; boolean useGuillemot = true;
@ -96,7 +641,7 @@ public class Options implements Cloneable, OptionProvider {
String viewName = null; String viewName = null;
double nodeSep = 0.25; double nodeSep = 0.25;
double rankSep = 0.5; double rankSep = 0.5;
public String outputDirectory = null; public String outputDirectory = ".";
/* /*
* Numeric values are preferable to symbolic here. Symbolic reportedly fail on * Numeric values are preferable to symbolic here. Symbolic reportedly fail on
* MacOSX, and also are more difficult to verify with XML tools. * 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; Visibility inferDependencyVisibility = Visibility.PRIVATE;
boolean inferDepInPackage = false; boolean inferDepInPackage = false;
RelationType inferRelationshipType = RelationType.NAVASSOC; RelationType inferRelationshipType = RelationType.NAVASSOC;
private List<Pattern> collPackages = new ArrayList<Pattern>(); private List<Pattern> collPackages = new ArrayList<>();
boolean compact = false; boolean compact = false;
boolean hidePrivateInner = false; boolean hidePrivateInner = false;
// internal option, used by UMLDoc to generate relative links between classes // internal option, used by UMLDoc to generate relative links between classes
boolean relativeLinksForSourcePackages = false; boolean relativeLinksForSourcePackages = false;
// internal option, used by UMLDoc to force strict matching on the class names // internal option, used by UMLDoc to force strict matching on the class names
// and avoid problems with packages in the template declaration making UmlGraph // and avoid problems with packages in the template declaration making UmlGraph hide
// hide // classes outside of them (for example, class gr.spinellis.Foo<T extends java.io.Serializable>
// classes outside of them (for example, class gr.spinellis.Foo<T extends
// java.io.Serializable>
// would have been hidden by the hide pattern "java.*" // would have been hidden by the hide pattern "java.*"
// TODO: consider making this standard behaviour boolean strictMatching = true;
boolean strictMatching = false;
String dotExecutable = "dot"; String dotExecutable = "dot";
Options() { Options() {
} }
@Override @Override
public Object clone() { public Options clone() {
Options clone = null; Options clone = null;
try { try {
clone = (Options) super.clone(); clone = (Options) super.clone();
@ -140,10 +682,10 @@ public class Options implements Cloneable, OptionProvider {
throw new RuntimeException("Cannot clone?!?", e); // Should not happen throw new RuntimeException("Cannot clone?!?", e); // Should not happen
} }
// deep clone the hide and collection patterns // deep clone the hide and collection patterns
clone.hidePatterns = new ArrayList<Pattern>(hidePatterns); clone.hidePatterns = new ArrayList<>(hidePatterns);
clone.includePatterns = new ArrayList<Pattern>(includePatterns); clone.includePatterns = new ArrayList<>(includePatterns);
clone.collPackages = new ArrayList<Pattern>(collPackages); clone.collPackages = new ArrayList<>(collPackages);
clone.apiDocMap = new HashMap<Pattern, String>(apiDocMap); clone.apiDocMap = new HashMap<>(apiDocMap);
return clone; return clone;
} }
@ -159,7 +701,7 @@ public class Options implements Cloneable, OptionProvider {
} }
/** /**
* Match strings, ignoring leading <tt>-</tt>, <tt>-!</tt>, and <tt>!</tt>. * Match strings, ignoring leading <code>-</code>, <code>--</code>, <code>-!</code>, and <code>!</code>.
* *
* @param given Given string * @param given Given string
* @param expect Expected string * @param expect Expected string
@ -170,7 +712,7 @@ public class Options implements Cloneable, OptionProvider {
} }
/** /**
* Match strings, ignoring leading <tt>-</tt>, <tt>-!</tt>, and <tt>!</tt>. * Match strings, ignoring leading <code>-</code>, <code>--</code>, <code>-!</code>, and <code>!</code>.
* *
* @param given Given string * @param given Given string
* @param expect Expected 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) { protected static boolean matchOption(String given, String expect, boolean negative) {
int begin = 0, end = given.length(); int begin = 0, end = given.length();
if (begin < end && given.charAt(begin) == '-') if (begin < end && given.charAt(begin) == '-') {
++begin; ++begin;
if (negative && begin < end && given.charAt(begin) == '!') }
// double dashed option
if (begin < end && given.charAt(begin) == '-') {
++begin; ++begin;
}
if (negative && begin < end && given.charAt(begin) == '!') {
++begin;
}
return expect.length() == end - begin && expect.regionMatches(0, given, begin, end - 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, "inferrel", true) || matchOption(option, "useimports", true)
|| matchOption(option, "collapsible", true) || matchOption(option, "inferdep", true) || matchOption(option, "collapsible", true) || matchOption(option, "inferdep", true)
|| matchOption(option, "inferdepinpackage", true) || matchOption(option, "hideprivateinner", true) || matchOption(option, "inferdepinpackage", true) || matchOption(option, "hideprivateinner", true)
|| matchOption(option, "compact", true)) || matchOption(option, "compact", true)) {
return 1; 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, "nodefontsize") || matchOption(option, "nodefontname")
|| matchOption(option, "nodefontclasssize") || matchOption(option, "nodefontclassname") || matchOption(option, "nodefontclasssize") || matchOption(option, "nodefontclassname")
|| matchOption(option, "nodefonttagsize") || matchOption(option, "nodefonttagname") || matchOption(option, "nodefonttagsize") || matchOption(option, "nodefonttagname")
@ -221,12 +769,13 @@ public class Options implements Cloneable, OptionProvider {
|| matchOption(option, "inferreltype") || matchOption(option, "inferdepvis") || matchOption(option, "inferreltype") || matchOption(option, "inferdepvis")
|| matchOption(option, "collpackages") || matchOption(option, "nodesep") || matchOption(option, "collpackages") || matchOption(option, "nodesep")
|| matchOption(option, "ranksep") || matchOption(option, "dotexecutable") || matchOption(option, "ranksep") || matchOption(option, "dotexecutable")
|| matchOption(option, "link")) || matchOption(option, "link")) {
return 2; return 2;
else if (matchOption(option, "contextPattern") || matchOption(option, "linkoffline")) } else if (matchOption(option, "contextPattern") || matchOption(option, "linkoffline")) {
return 3; return 3;
else } else {
return 0; return 0;
}
} }
/** Set the options based on a single option and its arguments */ /** Set the options based on a single option and its arguments */
@ -436,7 +985,7 @@ public class Options implements Cloneable, OptionProvider {
BufferedReader br = null; BufferedReader br = null;
packageListUrl = fixApiDocRoot(packageListUrl); packageListUrl = fixApiDocRoot(packageListUrl);
try { try {
URL url = new URL(packageListUrl + "/package-list"); URL url = new URL(packageListUrl + "package-list");
br = new BufferedReader(new InputStreamReader(url.openStream())); br = new BufferedReader(new InputStreamReader(url.openStream()));
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
@ -447,11 +996,12 @@ public class Options implements Cloneable, OptionProvider {
} catch (IOException e) { } catch (IOException e) {
System.err.println("Errors happened while accessing the package-list file at " + packageListUrl); System.err.println("Errors happened while accessing the package-list file at " + packageListUrl);
} finally { } finally {
if (br != null) if (br != null) {
try { try {
br.close(); br.close();
} catch (IOException e) { } catch (IOException e) {
} }
}
} }
} }
@ -468,7 +1018,7 @@ public class Options implements Cloneable, OptionProvider {
BufferedReader br = null; BufferedReader br = null;
packageListUrl = fixApiDocRoot(packageListUrl); packageListUrl = fixApiDocRoot(packageListUrl);
try { try {
URL url = new URL(packageListUrl + "/package-list"); URL url = new URL(packageListUrl + "package-list");
br = new BufferedReader(new InputStreamReader(url.openStream())); br = new BufferedReader(new InputStreamReader(url.openStream()));
String line; String line;
while ((line = br.readLine()) != null) { while ((line = br.readLine()) != null) {
@ -479,11 +1029,12 @@ public class Options implements Cloneable, OptionProvider {
} catch (IOException e) { } catch (IOException e) {
System.err.println("Unable to access the package-list file at " + packageListUrl); System.err.println("Unable to access the package-list file at " + packageListUrl);
} finally { } finally {
if (br != null) if (br != null) {
try { try {
br.close(); br.close();
} catch (IOException e) { } 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" * the constructor of the api doc root, so it depends on the order of "-link"
* and "-apiDocMap" parameters. * and "-apiDocMap" parameters.
*/ */
public String getApiDocRoot(String className) { public String getApiDocRoot(Name className) {
if (apiDocMap.isEmpty()) if (apiDocMap.isEmpty()) {
apiDocMap.put(Pattern.compile(".*"), DEFAULT_EXTERNAL_APIDOC); apiDocMap.put(Pattern.compile(".*"), DEFAULT_EXTERNAL_APIDOC);
}
for (Map.Entry<Pattern, String> mapEntry : apiDocMap.entrySet()) { for (Map.Entry<Pattern, String> mapEntry : apiDocMap.entrySet()) {
if (mapEntry.getKey().matcher(className).matches()) if (mapEntry.getKey().matcher(className).matches()) {
return mapEntry.getValue(); return mapEntry.getValue();
}
} }
return null; return null;
} }
/** Trim and append a file separator to the string */ /** Trim and append a file separator to the string */
private String fixApiDocRoot(String str) { private String fixApiDocRoot(String str) {
if (str == null) if (str == null) {
return null; return null;
}
String fixed = str.trim(); String fixed = str.trim();
if (fixed.isEmpty()) if (fixed.isEmpty()) {
return ""; return "";
if (File.separatorChar != '/') }
if (File.separatorChar != '/') {
fixed = fixed.replace(File.separatorChar, '/'); fixed = fixed.replace(File.separatorChar, '/');
if (!fixed.endsWith("/")) }
if (!fixed.endsWith("/")) {
fixed = fixed + "/"; fixed = fixed + "/";
}
return 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 */ /** Set the options based on the tag elements of the ClassDoc parameter */
public void setOptions(Doc p) { public void setOptions(DocTrees docTrees, Element p) {
if (p == null) if (p == null) {
return; return;
}
for (Tag tag : p.tags("opt")) List<String> tags = TagUtil.getTag(docTrees, p, "opt");
setOption(StringUtil.tokenize(tag.text())); for (String tag : tags) {
setOption(StringUtil.tokenize(tag));
}
} }
/** /**
@ -574,15 +1128,17 @@ public class Options implements Cloneable, OptionProvider {
* *
* @return true if the string matches. * @return true if the string matches.
*/ */
public boolean matchesHideExpression(String s) { public boolean matchesHideExpression(CharSequence s) {
for (Pattern hidePattern : hidePatterns) { for (Pattern hidePattern : hidePatterns) {
// micro-optimization because the "all pattern" is heavily used in UmlGraphDoc // micro-optimization because the "all pattern" is heavily used in UmlGraphDoc
if (hidePattern == allPattern) if (hidePattern == allPattern) {
return true; return true;
}
Matcher m = hidePattern.matcher(s); Matcher m = hidePattern.matcher(s);
if (strictMatching ? m.matches() : m.find()) if (strictMatching ? m.matches() : m.find()) {
return true; return true;
}
} }
return false; return false;
} }
@ -593,11 +1149,12 @@ public class Options implements Cloneable, OptionProvider {
* *
* @return true if the string matches. * @return true if the string matches.
*/ */
public boolean matchesIncludeExpression(String s) { public boolean matchesIncludeExpression(CharSequence s) {
for (Pattern includePattern : includePatterns) { for (Pattern includePattern : includePatterns) {
Matcher m = includePattern.matcher(s); Matcher m = includePattern.matcher(s);
if (strictMatching ? m.matches() : m.find()) if (strictMatching ? m.matches() : m.find()) {
return true; return true;
}
} }
return false; return false;
} }
@ -608,7 +1165,7 @@ public class Options implements Cloneable, OptionProvider {
* *
* @return true if the string matches. * @return true if the string matches.
*/ */
public boolean matchesCollPackageExpression(String s) { public boolean matchesCollPackageExpression(CharSequence s) {
for (Pattern collPattern : collPackages) { for (Pattern collPattern : collPackages) {
Matcher m = collPattern.matcher(s); Matcher m = collPattern.matcher(s);
if (strictMatching ? m.matches() : m.find()) if (strictMatching ? m.matches() : m.find())
@ -621,13 +1178,13 @@ public class Options implements Cloneable, OptionProvider {
// OptionProvider methods // OptionProvider methods
// ---------------------------------------------------------------- // ----------------------------------------------------------------
public Options getOptionsFor(ClassDoc cd) { public Options getOptionsFor(DocTrees docTrees, TypeElement cd) {
Options localOpt = getGlobalOptions(); Options localOpt = getGlobalOptions();
localOpt.setOptions(cd); localOpt.setOptions(docTrees, cd);
return localOpt; return localOpt;
} }
public Options getOptionsFor(String name) { public Options getOptionsFor(CharSequence name) {
return getGlobalOptions(); return getGlobalOptions();
} }
@ -635,11 +1192,11 @@ public class Options implements Cloneable, OptionProvider {
return (Options) clone(); return (Options) clone();
} }
public void overrideForClass(Options opt, ClassDoc cd) { public void overrideForClass(Options opt, TypeElement cd) {
// nothing to do // nothing to do
} }
public void overrideForClass(Options opt, String className) { public void overrideForClass(Options opt, CharSequence className) {
// nothing to do // nothing to do
} }

View File

@ -1,24 +1,32 @@
package org.umlgraph.doclet; package org.umlgraph.doclet;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.PackageDoc;
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 { public class PackageMatcher implements ClassMatcher {
protected PackageDoc packageDoc; protected DocletEnvironment root;
protected PackageElement packageDoc;
public PackageMatcher(PackageDoc packageDoc) { public PackageMatcher(DocletEnvironment root, PackageElement packageDoc) {
super(); this.root = root;
this.packageDoc = packageDoc; this.packageDoc = packageDoc;
} }
public boolean matches(ClassDoc cd) { public boolean matches(TypeElement cd) {
return cd.containingPackage().equals(packageDoc); return packageDoc.equals(ElementUtil.getPackageOf(root, cd));
} }
public boolean matches(String name) { public boolean matches(CharSequence name) {
for (ClassDoc cd : packageDoc.allClasses()) for (Element cd : packageDoc.getEnclosedElements()) {
if (cd.qualifiedName().equals(name)) if (cd instanceof TypeElement && ((TypeElement) cd).getQualifiedName().equals(name)) {
return true; return true;
}
}
return false; return false;
} }

View File

@ -1,8 +1,14 @@
package org.umlgraph.doclet; package org.umlgraph.doclet;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.RootDoc; 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 * 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 { public class PackageView implements OptionProvider {
private static final String[] HIDE = new String[] { "hide" }; private static final String[] HIDE = new String[] { "hide" };
private PackageDoc pd; private PackageElement pd;
private OptionProvider parent; private OptionProvider parent;
private ClassMatcher matcher; private ClassMatcher matcher;
private String outputPath; private String outputPath;
private Options opt; 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.parent = parent;
this.pd = pd; this.pd = pd;
this.matcher = new PackageMatcher(pd); this.docTrees = root.getDocTrees();
this.matcher = new PackageMatcher(root, pd);
this.opt = parent.getGlobalOptions(); this.opt = parent.getGlobalOptions();
this.opt.setOptions(pd); this.opt.setOptions(docTrees, pd);
this.outputPath = pd.name().replace('.', '/') + "/" + pd.name() + ".dot"; 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() { public String getDisplayName() {
@ -47,36 +57,39 @@ public class PackageView implements OptionProvider {
return go; return go;
} }
public Options getOptionsFor(ClassDoc cd) { public Options getOptionsFor(DocTrees dt, TypeElement cd) {
Options go = parent.getGlobalOptions(); Options go = parent.getGlobalOptions();
overrideForClass(go, cd); overrideForClass(go, cd);
return go; return go;
} }
public Options getOptionsFor(String name) { public Options getOptionsFor(CharSequence name) {
Options go = parent.getGlobalOptions(); Options go = parent.getGlobalOptions();
overrideForClass(go, name); overrideForClass(go, name);
return go; return go;
} }
public void overrideForClass(Options opt, ClassDoc cd) { public void overrideForClass(Options opt, TypeElement cd) {
opt.setOptions(cd); opt.setOptions(docTrees, cd);
boolean inPackage = matcher.matches(cd); boolean inPackage = matcher.matches(cd);
if (inPackage) if (inPackage) {
opt.showQualified = false; 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); opt.setOption(HIDE);
}
} }
public void overrideForClass(Options opt, String className) { public void overrideForClass(Options opt, CharSequence className) {
opt.showQualified = false; opt.showQualified = false;
boolean inPackage = matcher.matches(className); boolean inPackage = matcher.matches(className);
if (inPackage) if (inPackage)
opt.showQualified = false; opt.showQualified = false;
boolean included = inPackage || this.opt.matchesIncludeExpression(className); boolean included = inPackage || this.opt.matchesIncludeExpression(className);
if (!included || this.opt.matchesHideExpression(className)) if (!included || this.opt.matchesHideExpression(className)) {
opt.setOption(HIDE); opt.setOption(HIDE);
}
} }
} }

View File

@ -18,7 +18,7 @@ package org.umlgraph.doclet;
import java.util.regex.Pattern; 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 * Matches classes performing a regular expression match on the qualified class
@ -33,11 +33,11 @@ public class PatternMatcher implements ClassMatcher {
this.pattern = pattern; this.pattern = pattern;
} }
public boolean matches(ClassDoc cd) { public boolean matches(TypeElement cd) {
return matches(cd.toString()); return matches(cd.getQualifiedName());
} }
public boolean matches(String name) { public boolean matches(CharSequence name) {
return pattern.matcher(name).matches(); return pattern.matcher(name).matches();
} }

View File

@ -19,6 +19,9 @@
package org.umlgraph.doclet; package org.umlgraph.doclet;
import javax.lang.model.element.Name;
import javax.lang.model.util.Elements;
import java.util.*; import java.util.*;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -148,10 +151,11 @@ class StringUtil {
} }
/** Removes the template specs from a class name. */ /** Removes the template specs from a class name. */
public static String removeTemplate(String name) { public static Name removeTemplate(Elements elements, Name name) {
int openIdx = name.indexOf('<'); int openIdx = name.toString().indexOf('<');
if (openIdx == -1) if (openIdx == -1) {
return name; return name;
}
StringBuilder buf = new StringBuilder(name.length()); StringBuilder buf = new StringBuilder(name.length());
for (int i = 0, depth = 0; i < name.length(); i++) { for (int i = 0, depth = 0; i < name.length(); i++) {
char c = name.charAt(i); char c = name.charAt(i);
@ -162,26 +166,29 @@ class StringUtil {
else if (depth == 0) else if (depth == 0)
buf.append(c); 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 // path, relative to the root, of the destination class
String[] contextClassPath = contextPackageName.split("\\."); String[] contextClassPath = contextPackageName.toString().split("\\.");
String[] currClassPath = classPackageName.split("\\."); String[] currClassPath = classPackageName.split("\\.");
// compute relative path between the context and the destination // compute relative path between the context and the destination
// ... first, compute common part // ... first, compute common part
int i = 0, e = Math.min(contextClassPath.length, currClassPath.length); 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++; i++;
}
// ... go up with ".." to reach the common root // ... go up with ".." to reach the common root
StringBuilder buf = new StringBuilder(classPackageName.length()); StringBuilder buf = new StringBuilder(classPackageName.length());
for (int j = i; j < contextClassPath.length; j++) for (int j = i; j < contextClassPath.length; j++) {
buf.append("../"); buf.append("../");
}
// ... go down from the common root to the destination // ... 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 buf.append(currClassPath[j]).append('/'); // Always use HTML seperators
}
return buf.toString(); return buf.toString();
} }
@ -195,10 +202,11 @@ class StringUtil {
* users adhere to Java conventions, of beginning package names with a lowercase * users adhere to Java conventions, of beginning package names with a lowercase
* letter. * letter.
* *
* @param className * @param name
* @return Splitting point (Either referring to a dot, or -1) * @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 gen = className.indexOf('<'); // Begin before generics.
int end = gen >= 0 ? gen : className.length(); int end = gen >= 0 ? gen : className.length();
int start = className.lastIndexOf('.', end); int start = className.lastIndexOf('.', end);

View File

@ -2,8 +2,11 @@ package org.umlgraph.doclet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.RootDoc;
import javax.lang.model.element.TypeElement;
import org.umlgraph.doclet.util.ElementUtil;
/** /**
* Matches every class that extends (directly or indirectly) a class matched by * 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 { public class SubclassMatcher implements ClassMatcher {
protected RootDoc root; protected DocletEnvironment root;
protected Pattern pattern; protected Pattern pattern;
public SubclassMatcher(RootDoc root, Pattern pattern) { public SubclassMatcher(DocletEnvironment root, Pattern pattern) {
this.root = root; this.root = root;
this.pattern = pattern; this.pattern = pattern;
} }
public boolean matches(ClassDoc cd) { public boolean matches(TypeElement cd) {
// if it's the class we're looking for return // if it's the class we're looking for return
if (pattern.matcher(cd.toString()).matches()) if (pattern.matcher(cd.toString()).matches()) {
return true; return true;
}
// recurse on supeclass, if available // recurse on superclass, if available
return cd.superclass() == null ? false : matches(cd.superclass()); TypeElement scd = ElementUtil.getTypeElement(cd.getSuperclass());
return scd == null ? false : matches(scd);
} }
public boolean matches(String name) { public boolean matches(CharSequence name) {
ClassDoc cd = root.classNamed(name); TypeElement cd = root.getElementUtils().getTypeElement(name);
return cd == null ? false : matches(cd); return cd == null ? false : matches(cd);
} }

View File

@ -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<Void, Void> {
private final Map<String, List<String>> tags;
public TagScanner(Map<String, List<String>> 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;
}
}

View File

@ -20,14 +20,26 @@
package org.umlgraph.doclet; package org.umlgraph.doclet;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set;
import com.sun.javadoc.ClassDoc; import jdk.javadoc.doclet.Doclet;
import com.sun.javadoc.Doc; import jdk.javadoc.doclet.DocletEnvironment;
import com.sun.javadoc.LanguageVersion; import jdk.javadoc.doclet.Reporter;
import com.sun.javadoc.RootDoc; 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 * Doclet API implementation
@ -41,7 +53,7 @@ import com.sun.javadoc.RootDoc;
* @version $Revision$ * @version $Revision$
* @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a> * @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a>
*/ */
public class UmlGraph { public class UmlGraph implements Doclet {
private static final String programName = "UmlGraph"; private static final String programName = "UmlGraph";
private static final String docletName = "org.umlgraph.doclet.UmlGraph"; private static final String docletName = "org.umlgraph.doclet.UmlGraph";
@ -49,25 +61,66 @@ public class UmlGraph {
/** Options used for commenting nodes */ /** Options used for commenting nodes */
private static Options commentOptions; 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); private Locale locale;
if (views == null) private Reporter reporter;
return false; private Options options;
if (views.length == 0) private StandardDoclet standard;
buildGraph(root, opt, null);
else @Override
for (int i = 0; i < views.length; i++) public void init(Locale locale, Reporter reporter) {
buildGraph(root, views[i], null); this.locale = locale;
return true; this.reporter = reporter;
this.options = new Options();
this.standard = new StandardDoclet();
} }
public static void main(String args[]) { @Override
PrintWriter err = new PrintWriter(System.err); public String getName() {
com.sun.tools.javadoc.Main.execute(programName, err, err, err, docletName, args); return docletName;
}
@Override
public Set<? extends Doclet.Option> getSupportedOptions() {
Set<Doclet.Option> 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<View> 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() { public static Options getCommentOptions() {
@ -79,49 +132,62 @@ public class UmlGraph {
* the command line and the ones specified in the UMLOptions class, if * the command line and the ones specified in the UMLOptions class, if
* available. Also create the globally accessible commentOptions object. * available. Also create the globally accessible commentOptions object.
*/ */
public static Options buildOptions(RootDoc root) { public static Options buildOptions(DocletEnvironment root, Options o) {
commentOptions = new Options(); commentOptions = o.clone();
commentOptions.setOptions(root.options()); commentOptions.setOptions(root.getDocTrees(), findClass(root, "UMLNoteOptions"));
commentOptions.setOptions(findClass(root, "UMLNoteOptions"));
commentOptions.shape = Shape.NOTE; commentOptions.shape = Shape.NOTE;
Options opt = new Options(); Options opt = o.clone();
opt.setOptions(root.options()); opt.setOptions(root.getDocTrees(), findClass(root, "UMLOptions"));
opt.setOptions(findClass(root, "UMLOptions"));
return opt; return opt;
} }
/** Return the ClassDoc for the specified class; null if not found. */ /** Return the ClassDoc for the specified class; null if not found. */
private static ClassDoc findClass(RootDoc root, String name) { private static TypeElement findClass(DocletEnvironment root, String name) {
ClassDoc[] classes = root.classes(); Set<? extends Element> classes = root.getIncludedElements();
for (ClassDoc cd : classes) for (Element element : classes) {
if (cd.name().equals(name)) if (element instanceof TypeElement && ((TypeElement) element).getQualifiedName().toString().equals(name)) {
return cd; return (TypeElement) element;
}
}
return null; return null;
} }
/** /**
* Builds and outputs a single graph according to the view overrides * Builds and outputs a single graph according to the view overrides
*/ */
public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException { public static void buildGraph(Reporter reporter, DocletEnvironment root, Options options, OptionProvider op, Element contextDoc) throws IOException {
if (getCommentOptions() == null) if (getCommentOptions() == null) {
buildOptions(root); buildOptions(root, options);
}
Options opt = op.getGlobalOptions(); Options opt = op.getGlobalOptions();
root.printNotice("Building " + op.getDisplayName()); reporter.print(Diagnostic.Kind.NOTE, "Building " + op.getDisplayName());
ClassDoc[] classes = root.classes(); Set<? extends Element> elements = root.getIncludedElements();
Set<TypeElement> classes = new HashSet<>();
for (Element element : elements) {
if (element instanceof TypeElement) {
classes.add((TypeElement) element);
}
}
ClassGraph c = new ClassGraph(root, op, contextDoc); ClassGraph c = new ClassGraph(root, op, contextDoc);
c.prologue(); c.prologue();
for (ClassDoc cd : classes) for (TypeElement cd : classes) {
c.printClass(cd, true); c.printClass(cd, true);
for (ClassDoc cd : classes) }
for (TypeElement cd : classes) {
c.printRelations(cd); c.printRelations(cd);
if (opt.inferRelationships) }
for (ClassDoc cd : classes) if (opt.inferRelationships) {
for (TypeElement cd : classes) {
c.printInferredRelations(cd); c.printInferredRelations(cd);
if (opt.inferDependencies) }
for (ClassDoc cd : classes) }
if (opt.inferDependencies) {
for (TypeElement cd : classes) {
c.printInferredDependencies(cd); c.printInferredDependencies(cd);
}
}
c.printExtraClasses(root); c.printExtraClasses(root);
c.epilogue(); c.epilogue();
@ -135,54 +201,51 @@ public class UmlGraph {
* @param viewRootDoc The RootDoc for the view classes (may be different, or may * @param viewRootDoc The RootDoc for the view classes (may be different, or may
* be the same as the srcRootDoc) * be the same as the srcRootDoc)
*/ */
public static View[] buildViews(Options opt, RootDoc srcRootDoc, RootDoc viewRootDoc) { public static List<View> buildViews(Options opt, DocletEnvironment srcRootDoc, DocletEnvironment viewRootDoc) {
if (opt.viewName != null) { if (opt.viewName != null) {
ClassDoc viewClass = viewRootDoc.classNamed(opt.viewName); TypeElement viewClass = findClass(viewRootDoc, opt.viewName);
if (viewClass == null) { if (viewClass == null) {
System.out.println("View " + opt.viewName + " not found! Exiting without generating any output."); System.out.println("View " + opt.viewName + " not found! Exiting without generating any output.");
return null; return null;
} }
if (viewClass.tags("view").length == 0) { if (TagUtil.getTag(viewRootDoc, viewClass, "view").isEmpty()) {
System.out.println(viewClass + " is not a view!"); System.out.println(viewClass + " is not a view!");
return null; return null;
} }
if (viewClass.isAbstract()) { if (viewClass.getModifiers().contains(Modifier.ABSTRACT)) {
System.out.println(viewClass + " is an abstract view, no output will be generated!"); System.out.println(viewClass + " is an abstract view, no output will be generated!");
return null; return null;
} }
return new View[] { buildView(srcRootDoc, viewClass, opt) }; return List.of(buildView(srcRootDoc, viewClass, opt));
} else if (opt.findViews) { } else if (opt.findViews) {
List<View> views = new ArrayList<View>(); List<View> views = new ArrayList<>();
ClassDoc[] classes = viewRootDoc.classes(); Set<? extends Element> classes = viewRootDoc.getIncludedElements();
// find view classes // find view classes
for (int i = 0; i < classes.length; i++) for (Element elmt : classes) {
if (classes[i].tags("view").length > 0 && !classes[i].isAbstract()) if (!(elmt instanceof TypeElement)) {
views.add(buildView(srcRootDoc, classes[i], opt)); 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 } else
return new View[0]; return Collections.emptyList();
} }
/** /**
* Builds a view along with its parent views, recursively * Builds a view along with its parent views, recursively
*/ */
private static View buildView(RootDoc root, ClassDoc viewClass, OptionProvider provider) { private static View buildView(DocletEnvironment root, TypeElement viewClass, OptionProvider provider) {
ClassDoc superClass = viewClass.superclass(); TypeElement superClass = ElementUtil.getSuperclass(viewClass);
if (superClass == null || superClass.tags("view").length == 0) if (superClass == null || TagUtil.getTag(root, superClass, "view").isEmpty()) {
return new View(root, viewClass, provider); return new View(root, viewClass, provider);
}
return new View(root, viewClass, buildView(root, superClass, 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;
}
} }

View File

@ -3,22 +3,32 @@ package org.umlgraph.doclet;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.FileOutputStream;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.HashSet; import java.io.OutputStreamWriter;
import java.util.TreeSet;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet;
import java.util.Locale;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc; import javax.lang.model.SourceVersion;
import com.sun.javadoc.LanguageVersion; import javax.lang.model.element.Element;
import com.sun.javadoc.PackageDoc; import javax.lang.model.element.ModuleElement;
import com.sun.javadoc.RootDoc; import javax.lang.model.element.Name;
import com.sun.tools.doclets.standard.Standard; 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, * 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 - - - WrappedClassDoc
* @depend - - - WrappedRootDoc * @depend - - - WrappedRootDoc
*/ */
public class UmlGraphDoc { public class UmlGraphDoc implements Doclet {
/**
* Option check, forwards options to the standard doclet, if that one refuses private Locale locale;
* them, they are sent to UmlGraph private Reporter reporter;
*/ private Options options;
public static int optionLength(String option) { private StandardDoclet standard;
int result = Standard.optionLength(option);
if (result != 0) public UmlGraphDoc() {
return result; this.options = new Options();
else this.standard = new StandardDoclet();
return UmlGraph.optionLength(option); }
@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<? extends Doclet.Option> getSupportedOptions() {
Set<Doclet.Option> 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 * @param root
* @return * @return
*/ */
public static boolean start(RootDoc root) { @Override
root.printNotice("UmlGraphDoc version " + Version.VERSION + ", running the standard doclet"); public boolean run(DocletEnvironment root) {
Standard.start(root); reporter.print(Diagnostic.Kind.NOTE, "UmlGraphDoc version " + Version.VERSION + ", running the standard doclet");
root.printNotice("UmlGraphDoc version " + Version.VERSION + ", altering javadocs"); standard.run(root);
reporter.print(Diagnostic.Kind.NOTE, "UmlGraphDoc version " + Version.VERSION + ", altering javadocs");
try { try {
String outputFolder = findOutputPath(root.options()); Options opt = UmlGraph.buildOptions(root, this.options);
Options opt = UmlGraph.buildOptions(root);
opt.setOptions(root.options());
// in javadoc enumerations are always printed // in javadoc enumerations are always printed
opt.showEnumerations = true; opt.showEnumerations = true;
opt.relativeLinksForSourcePackages = true; opt.relativeLinksForSourcePackages = true;
// enable strict matching for hide expressions // enable strict matching for hide expressions
opt.strictMatching = true; opt.strictMatching = true;
// root.printNotice(opt.toString()); reporter.print(Diagnostic.Kind.NOTE, opt.toString());
generatePackageDiagrams(root, opt, outputFolder); generatePackageDiagrams(root, reporter, opt, this.options.outputDirectory);
generateContextDiagrams(root, opt, outputFolder); generateContextDiagrams(root, reporter, opt, this.options.outputDirectory);
} catch (Throwable t) { } catch (Throwable t) {
root.printWarning("Error: " + t.toString()); reporter.print(Diagnostic.Kind.WARNING, "Error: " + t.toString());
t.printStackTrace(); t.printStackTrace();
return false; return false;
} }
return true; 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 * Generates the package diagrams for all of the packages that contain classes
* among those returned by RootDoc.class() * among those returned by RootDoc.class()
*/ */
private static void generatePackageDiagrams(RootDoc root, Options opt, String outputFolder) throws IOException { private static void generatePackageDiagrams(DocletEnvironment root, Reporter reporter, Options opt, String outputFolder) throws IOException {
Set<String> packages = new HashSet<String>(); Set<Name> packages = new HashSet<>();
for (ClassDoc classDoc : root.classes()) { for (Element classDoc : root.getIncludedElements()) {
PackageDoc packageDoc = classDoc.containingPackage(); PackageElement packageDoc = null;
if (!packages.contains(packageDoc.name())) { if (classDoc instanceof PackageElement) {
packages.add(packageDoc.name()); 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); OptionProvider view = new PackageView(outputFolder, packageDoc, root, opt);
UmlGraph.buildGraph(root, view, packageDoc); UmlGraph.buildGraph(reporter, root, opt, view, packageDoc);
runGraphviz(opt.dotExecutable, outputFolder, packageDoc.name(), packageDoc.name(), root); runGraphviz(opt.dotExecutable, outputFolder, ElementUtil.getModuleOf(root, packageDoc), packageDoc.getQualifiedName(), packageDoc.getSimpleName(), reporter);
alterHtmlDocs(opt, outputFolder, packageDoc.name(), packageDoc.name(), "package-summary.html", alterHtmlDocs(opt, outputFolder, ElementUtil.getModuleOf(root, packageDoc), packageDoc.getQualifiedName(), packageDoc.getSimpleName(), "package-summary.html",
Pattern.compile("(</[Hh]2>)|(<h1 title=\"Package\").*"), root); Pattern.compile("(</[Hh]2>)|(<h1 title=\"Package\").*"), reporter);
} }
} }
} }
@ -105,30 +137,34 @@ public class UmlGraphDoc {
/** /**
* Generates the context diagram for a single class * Generates the context diagram for a single class
*/ */
private static void generateContextDiagrams(RootDoc root, Options opt, String outputFolder) throws IOException { private static void generateContextDiagrams(DocletEnvironment root, Reporter reporter, Options opt, String outputFolder) throws IOException {
Set<ClassDoc> classDocs = new TreeSet<ClassDoc>(new Comparator<ClassDoc>() { Set<TypeElement> classDocs = new TreeSet<>(new Comparator<>() {
public int compare(ClassDoc cd1, ClassDoc cd2) { public int compare(TypeElement cd1, TypeElement cd2) {
return cd1.name().compareTo(cd2.name()); return cd1.getSimpleName().toString().compareTo(cd2.getSimpleName().toString());
} }
}); });
for (ClassDoc classDoc : root.classes()) for (Element classDoc : root.getIncludedElements()) {
classDocs.add(classDoc); if (classDoc instanceof TypeElement) {
classDocs.add((TypeElement) classDoc);
}
}
ContextView view = null; ContextView view = null;
for (ClassDoc classDoc : classDocs) { for (TypeElement classDoc : classDocs) {
reporter.print(Diagnostic.Kind.WARNING, "Class processed " + classDoc.getQualifiedName());
try { try {
if (view == null) if (view == null) {
view = new ContextView(outputFolder, classDoc, root, opt); view = new ContextView(outputFolder, classDoc, root, opt);
else } else {
view.setContextCenter(classDoc); view.setContextCenter(classDoc);
UmlGraph.buildGraph(root, view, classDoc); }
runGraphviz(opt.dotExecutable, outputFolder, classDoc.containingPackage().name(), classDoc.name(), UmlGraph.buildGraph(reporter, root, opt, view, classDoc);
root); runGraphviz(opt.dotExecutable, outputFolder, ElementUtil.getModuleOf(root, classDoc), ElementUtil.getPackageOf(root, classDoc).getQualifiedName(), classDoc.getSimpleName(), reporter);
alterHtmlDocs(opt, outputFolder, classDoc.containingPackage().name(), classDoc.name(), alterHtmlDocs(opt, outputFolder, ElementUtil.getModuleOf(root, classDoc), ElementUtil.getPackageOf(root, classDoc).getQualifiedName(), classDoc.getSimpleName(),
classDoc.name() + ".html", classDoc.getSimpleName() + ".html",
Pattern.compile(".*(Class|Interface|Enum) " + classDoc.name() + ".*"), root); Pattern.compile(".*(Class|Interface|Enum) " + classDoc.getSimpleName() + ".*"), reporter);
} catch (Exception e) { } 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 * Runs Graphviz dot building both a diagram (in png format) and a client side
* map for it. * map for it.
*/ */
private static void runGraphviz(String dotExecutable, String outputFolder, String packageName, String name, private static void runGraphviz(String dotExecutable, String outputFolder, ModuleElement module, Name packageName, Name name, Reporter reporter) {
RootDoc root) {
if (dotExecutable == null) { if (dotExecutable == null) {
dotExecutable = "dot"; dotExecutable = "dot";
} }
File dotFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".dot"); String fileName = packageName.toString().replace(".", "/") + "/" + name;
File svgFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".svg"); 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 { try {
Process p = Runtime.getRuntime().exec(new String[] { dotExecutable, "-Tsvg", "-o", Process p = Runtime.getRuntime().exec(new String[] { dotExecutable, "-Tsvg", "-o",
svgFile.getAbsolutePath(), dotFile.getAbsolutePath() }); svgFile.getAbsolutePath(), dotFile.getAbsolutePath() });
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream())); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
String line; String line;
while ((line = reader.readLine()) != null) while ((line = reader.readLine()) != null) {
root.printWarning(line); reporter.print(Diagnostic.Kind.WARNING, line);
}
int result = p.waitFor(); int result = p.waitFor();
if (result != 0) if (result != 0) {
root.printWarning("Errors running Graphviz on " + dotFile); reporter.print(Diagnostic.Kind.WARNING, "Errors running Graphviz on " + dotFile);
}
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
System.err.println("Ensure that dot is in your path and that its path does not contain spaces"); 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, and inserts the diagram image reference and a client side map in that
* point. * point.
*/ */
private static void alterHtmlDocs(Options opt, String outputFolder, String packageName, String className, private static void alterHtmlDocs(Options opt, String outputFolder, ModuleElement module, Name packageName, Name className,
String htmlFileName, Pattern insertPointPattern, RootDoc root) throws IOException { String htmlFileName, Pattern insertPointPattern, Reporter reporter) throws IOException {
// setup files // 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 htmlFile = new File(output, htmlFileName);
File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml"); File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
if (!htmlFile.exists()) { if (!htmlFile.exists()) {
@ -213,12 +255,14 @@ public class UmlGraphDoc {
matched = true; matched = true;
String tag; String tag;
if (opt.autoSize) if (opt.autoSize) {
tag = String.format(UML_AUTO_SIZED_DIV_TAG, className); tag = String.format(UML_AUTO_SIZED_DIV_TAG, className);
else } else {
tag = String.format(UML_DIV_TAG, className); 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"); tag = String.format(EXPANDABLE_UML, tag, "Show UML class diagram", "Hide UML class diagram");
}
writer.write("<!-- UML diagram added by UMLGraph version " + Version.VERSION + " (http://www.spinellis.gr/umlgraph/) -->"); writer.write("<!-- UML diagram added by UMLGraph version " + Version.VERSION + " (http://www.spinellis.gr/umlgraph/) -->");
writer.newLine(); writer.newLine();
writer.write(tag); writer.write(tag);
@ -226,10 +270,12 @@ public class UmlGraphDoc {
} }
} }
} finally { } finally {
if (writer != null) if (writer != null) {
writer.close(); writer.close();
if (reader != null) }
if (reader != null) {
reader.close(); reader.close();
}
} }
// if altered, delete old file and rename new one to the old file name // if altered, delete old file and rename new one to the old file name
@ -237,20 +283,9 @@ public class UmlGraphDoc {
htmlFile.delete(); htmlFile.delete();
alteredFile.renameTo(htmlFile); alteredFile.renameTo(htmlFile);
} else { } else {
root.printNotice("Warning, could not find a line that matches the pattern '" reporter.print(Diagnostic.Kind.NOTE, "Warning, could not find a line that matches the pattern '"
+ insertPointPattern.pattern() + "'.\n Class diagram reference not inserted"); + insertPointPattern.pattern() + "'.\n Class diagram reference not inserted");
alteredFile.delete(); 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 ".";
}
} }

View File

@ -23,9 +23,12 @@ import java.util.Map;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException; import java.util.regex.PatternSyntaxException;
import com.sun.javadoc.ClassDoc; import javax.lang.model.element.TypeElement;
import com.sun.javadoc.RootDoc;
import com.sun.javadoc.Tag; 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 * 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 { public class View implements OptionProvider {
Map<ClassMatcher, List<String[]>> optionOverrides = new LinkedHashMap<ClassMatcher, List<String[]>>(); Map<ClassMatcher, List<String[]>> optionOverrides = new LinkedHashMap<>();
ClassDoc viewDoc; TypeElement viewDoc;
OptionProvider provider; OptionProvider provider;
List<String[]> globalOptions; List<String[]> globalOptions;
RootDoc root; DocletEnvironment root;
/** /**
* Builds a view given the class that contains its definition * 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.viewDoc = c;
this.provider = provider; this.provider = provider;
this.root = root; this.root = root;
Tag[] tags = c.tags(); Map<String, List<String>> tags = TagUtil.getTags(root, viewDoc);
ClassMatcher currMatcher = null; ClassMatcher currMatcher = null;
// parse options, get the global ones, and build a map of the // parse options, get the global ones, and build a map of the
// pattern matched overrides // pattern matched overrides
globalOptions = new ArrayList<String[]>(); globalOptions = new ArrayList<String[]>();
for (int i = 0; i < tags.length; i++) { if (tags.get("match") != null) {
if (tags[i].name().equals("@match")) { for (String text : tags.get("match")) {
currMatcher = buildMatcher(tags[i].text()); currMatcher = buildMatcher(text);
if (currMatcher != null) { if (currMatcher != null) {
optionOverrides.put(currMatcher, new ArrayList<String[]>()); 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]; opts[0] = "-" + opts[0];
if (currMatcher == null) { if (currMatcher == null) {
globalOptions.add(opts); globalOptions.add(opts);
@ -118,14 +124,14 @@ public class View implements OptionProvider {
// OptionProvider methods // OptionProvider methods
// ---------------------------------------------------------------- // ----------------------------------------------------------------
public Options getOptionsFor(ClassDoc cd) { public Options getOptionsFor(DocTrees dt, TypeElement cd) {
Options localOpt = getGlobalOptions(); Options localOpt = getGlobalOptions();
overrideForClass(localOpt, cd); overrideForClass(localOpt, cd);
localOpt.setOptions(cd); localOpt.setOptions(dt, cd);
return localOpt; return localOpt;
} }
public Options getOptionsFor(String name) { public Options getOptionsFor(CharSequence name) {
Options localOpt = getGlobalOptions(); Options localOpt = getGlobalOptions();
overrideForClass(localOpt, name); overrideForClass(localOpt, name);
return localOpt; return localOpt;
@ -136,34 +142,42 @@ public class View implements OptionProvider {
boolean outputSet = false; boolean outputSet = false;
for (String[] opts : globalOptions) { for (String[] opts : globalOptions) {
if (Options.matchOption(opts[0], "output")) if (Options.matchOption(opts[0], "output")) {
outputSet = true; outputSet = true;
}
go.setOption(opts); go.setOption(opts);
} }
if (!outputSet) if (!outputSet) {
go.setOption(new String[] { "output", viewDoc.name() + ".dot" }); go.setOption(new String[] { "output", viewDoc.getSimpleName() + ".dot" });
}
return go; return go;
} }
public void overrideForClass(Options opt, ClassDoc cd) { public void overrideForClass(Options opt, TypeElement cd) {
provider.overrideForClass(opt, cd); provider.overrideForClass(opt, cd);
for (ClassMatcher cm : optionOverrides.keySet()) for (ClassMatcher cm : optionOverrides.keySet()) {
if (cm.matches(cd)) if (cm.matches(cd)) {
for (String[] override : optionOverrides.get(cm)) for (String[] override : optionOverrides.get(cm)) {
opt.setOption(override); opt.setOption(override);
}
}
}
} }
public void overrideForClass(Options opt, String className) { public void overrideForClass(Options opt, CharSequence className) {
provider.overrideForClass(opt, className); provider.overrideForClass(opt, className);
for (ClassMatcher cm : optionOverrides.keySet()) for (ClassMatcher cm : optionOverrides.keySet()) {
if (cm.matches(className)) if (cm.matches(className)) {
for (String[] override : optionOverrides.get(cm)) for (String[] override : optionOverrides.get(cm)) {
opt.setOption(override); opt.setOption(override);
}
}
}
} }
public String getDisplayName() { public String getDisplayName() {
return "view " + viewDoc.name(); return "view " + viewDoc.getSimpleName();
} }
} }

View File

@ -18,7 +18,10 @@
*/ */
package org.umlgraph.doclet; 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 * Enumerates the possible visibilities in a Java program. For brevity, package
@ -35,14 +38,16 @@ public enum Visibility {
this.symbol = symbol; this.symbol = symbol;
} }
public static Visibility get(ProgramElementDoc doc) { public static Visibility get(Element doc) {
if (doc.isPrivate()) Set<Modifier> mods = doc.getModifiers();
if (mods.contains(Modifier.PRIVATE)) {
return PRIVATE; return PRIVATE;
else if (doc.isPackagePrivate()) } else if (mods.contains(Modifier.PROTECTED)) {
return PACKAGE;
else if (doc.isProtected())
return PROTECTED; return PROTECTED;
else } else if (mods.contains(Modifier.PUBLIC)) {
return PUBLIC; return PUBLIC;
} else {
return PACKAGE;
}
} }
} }

View File

@ -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<? extends DocTree> 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<Void, Void>() {
@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("</")
.append(node.getName())
.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("</").append(node.getKind().tagName).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;
}
}

View File

@ -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<? extends TypeMirror> getInterfacesTypes(TypeElement element) {
return element.getInterfaces();
}
public static List<TypeElement> getInterfaces(TypeElement element) {
List<? extends TypeMirror> 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<VariableElement> getFields(TypeElement element) {
List<? extends Element> enclosed = element.getEnclosedElements();
return ElementFilter.fieldsIn(enclosed).stream().filter(v -> v.getKind() == ElementKind.FIELD).collect(Collectors.toList());
}
public static List<VariableElement> getEnumConstants(TypeElement element) {
List<? extends Element> enclosed = element.getEnclosedElements();
return ElementFilter.fieldsIn(enclosed).stream().filter(v -> v.getKind() == ElementKind.ENUM_CONSTANT).collect(Collectors.toList());
}
public static List<ExecutableElement> getMethods(TypeElement element) {
List<? extends Element> enclosed = element.getEnclosedElements();
return ElementFilter.methodsIn(enclosed);
}
public static List<ExecutableElement> getConstructors(TypeElement element) {
List<? extends Element> 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();
}
}

View File

@ -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<String, List<String>> getTags(DocletEnvironment root, Element c) {
return getTags(root.getDocTrees(), c);
}
public static Map<String, List<String>> getTags(DocTrees docTrees, Element c) {
DocCommentTree tree = docTrees.getDocCommentTree(c);
Map<String, List<String>> 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<String> comments = getTag(docTrees, c, "comment");
if (comments == null || comments.isEmpty()) {
return "";
}
return comments.get(0);
}
public static List<String> getTag(DocletEnvironment root, Element c, String tagName) {
return getTag(root.getDocTrees(), c, tagName);
}
public static List<String> getTag(DocTrees docTrees, Element c, String tagName) {
List<String> tags = getTags(docTrees, c).get(tagName);
return tags == null ? Collections.emptyList() : tags;
}
}