Merge pull request #62 from laurentschoelens/java9

jdk9 port of UMLGraph
This commit is contained in:
Diomidis Spinellis 2023-03-24 17:57:20 +02:00 committed by GitHub
commit 6115e12837
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
38 changed files with 3675 additions and 2300 deletions

View File

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src/main/java"/>
<classpathentry kind="src" path="src/main/resources"/>
<classpathentry kind="src" path="src/test/java"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="JDK_LIB/tools.jar"/>
<classpathentry kind="output" path="build"/>
</classpath>

3
.gitignore vendored
View File

@ -3,7 +3,10 @@
*.ipr *.ipr
*.iws *.iws
*.swp *.swp
.idea
.settings .settings
.project
.classpath
_vimrc _vimrc
build build
CHECKSUM.MD5 CHECKSUM.MD5

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>umlgraph_cvs</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

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,16 +40,15 @@
<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> <dependencies>
<dependency> <dependency>
<groupId>sun.jdk</groupId> <groupId>junit</groupId>
<artifactId>tools</artifactId> <artifactId>junit</artifactId>
<version>1.5.0</version> <version>4.13.2</version>
<scope>system</scope> <scope>test</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency> </dependency>
</dependencies> </dependencies>
@ -63,15 +62,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 +84,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 +108,40 @@
<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>
<detectJavaApiLink>false</detectJavaApiLink>
<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,9 +22,12 @@ 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) * Class's dot-compatible alias name (for fully qualified class names) and
* and printed information * printed information
*
* @version $Revision$ * @version $Revision$
* @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a> * @author <a href="http://www.spinellis.gr">Diomidis Spinellis</a>
*/ */
@ -37,36 +40,34 @@ class ClassInfo {
/** True if the class class node is hidden */ /** True if the class class node is hidden */
boolean hidden; boolean hidden;
/** /**
* The list of classes that share a relation with this one. Contains * The list of classes that share a relation with this one. Contains all the
* all the classes linked with a bi-directional relation , and the ones * classes linked with a bi-directional relation , and the ones referred by a
* 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;
name = "c" + classNumber; name = "c" + classNumber;
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);
relatedClasses.put(dest, ri); relatedClasses.put(dest, ri);
} }
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);
} }
/** Start numbering from zero. */ /** Start numbering from zero. */
public static void reset() { public static void reset() {
classNumber = 0; classNumber = 0;
} }
} }

View File

@ -17,22 +17,22 @@
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 * A ClassMatcher is used to check if a class definition matches a specific
* specific condition. The nature of the condition is dependent on * condition. The nature of the condition is dependent on the kind of matcher
* the kind of matcher *
* @author wolf * @author wolf
*/ */
public interface ClassMatcher { 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
@ -38,6 +40,7 @@ import com.sun.javadoc.RootDoc;
* This class needs to perform quite a bit of computations in order to gather * This class needs to perform quite a bit of computations in order to gather
* the network of class releationships, so you are allowed to reuse it should * the network of class releationships, so you are allowed to reuse it should
* you * you
*
* @author wolf * @author wolf
* *
* @depend - - - DevNullWriter * @depend - - - DevNullWriter
@ -45,34 +48,35 @@ 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;
/** /**
* Builds the context matcher * Builds the context matcher
* @param root The root doc returned by JavaDoc *
* @param pattern The pattern that will match the "center" of this * @param root The root doc returned by JavaDoc
* context * @param pattern The pattern that will match the "center" of this
* @param keepParentHide If true, parent option hide patterns will be * context
* preserved, so that classes hidden by the options won't * @param keepParentHide If true, parent option hide patterns will be preserved,
* be shown in the context * so that classes hidden by the options won't be shown in
* 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;
opt = (Options) options.clone(); opt = (Options) options.clone();
opt.setOption(new String[] { "!hide" }); opt.setOption(new String[] { "!hide" });
opt.setOption(new String[] { "!attributes" }); opt.setOption(new String[] { "!attributes" });
opt.setOption(new String[] { "!operations" }); opt.setOption(new String[] { "!operations" });
this.cg = new ClassGraphHack(root, opt); this.cg = new ClassGraphHack(root, opt);
setContextCenter(pattern); setContextCenter(pattern);
} }
/** /**
@ -80,116 +84,126 @@ public class ContextMatcher implements ClassMatcher {
* <p> * <p>
* This can be used to speed up subsequent matching with the same global * This can be used to speed up subsequent matching with the same global
* options, since the class network informations will be reused. * options, since the class network informations will be reused.
*
* @param pattern * @param pattern
*/ */
public void setContextCenter(Pattern pattern) { public void setContextCenter(Pattern pattern) {
// 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);
} }
} }
} }
/** /**
* Adds the specified class to the internal class graph along with its * Adds the specified class to the internal class graph along with its relations
* relations and dependencies, eventually inferring them, according to the * and dependencies, eventually inferring them, according to the Options
* Options specified for this matcher * specified for this matcher
*
* @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) }
cg.printInferredDependencies(cd); if (opt.inferDependencies) {
cg.printInferredDependencies(cd);
}
} }
/** /**
* @see org.umlgraph.doclet.ClassMatcher#matches(com.sun.javadoc.ClassDoc) * @see org.umlgraph.doclet.ClassMatcher#matches(TypeElement)
*/ */
public boolean matches(ClassDoc cd) { 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
addToGraph(cd); addToGraph(cd);
return matches(cd.toString()); return matches(cd.toString());
} }
/** /**
* @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;
} }
/** /**
* A quick hack to compute class dependencies reusing ClassGraph but * A quick hack to compute class dependencies reusing ClassGraph but without
* without generating output. Will be removed once the ClassGraph class * generating output. Will be removed once the ClassGraph class will be split
* will be split into two classes for graph computation and output * into two classes for graph computation and output generation.
* generation. *
* @author wolf * @author wolf
* *
*/ */
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();
} }
@Override @Override
public void prologue() throws IOException { public void prologue() throws IOException {
w = new PrintWriter(new DevNullWriter()); w = new PrintWriter(new DevNullWriter());
} }
} }
/** /**
* Simple dev/null imitation * Simple dev/null imitation
*
* @author wolf * @author wolf
*/ */
private static class DevNullWriter extends Writer { private static class DevNullWriter extends Writer {
@Override @Override
public void write(char[] cbuf, int off, int len) throws IOException { public void write(char[] cbuf, int off, int len) throws IOException {
// nothing to do // nothing to do
} }
@Override @Override
public void flush() throws IOException { public void flush() throws IOException {
// nothing to do // nothing to do
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
// nothing to do // nothing to do
} }
} }

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
@ -14,12 +20,14 @@ import com.sun.javadoc.RootDoc;
* single {@linkplain ContextMatcher}, but provides some extra configuration * single {@linkplain ContextMatcher}, but provides some extra configuration
* such as context highlighting and output path configuration (and it is * such as context highlighting and output path configuration (and it is
* specified in code rather than in javadoc comments). * specified in code rather than in javadoc comments).
*
* @author wolf * @author wolf
* *
*/ */
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;
@ -28,93 +36,98 @@ 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) public ContextView(String outputFolder, TypeElement cd, DocletEnvironment root, Options parent) throws IOException {
throws IOException { this.cd = cd;
this.cd = cd; this.root = root;
String outputPath = cd.containingPackage().name().replace('.', '/') + "/" + cd.name() ModuleElement md = ElementUtil.getModuleOf(root, cd);
+ ".dot"; 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
this.globalOptions = parent.getGlobalOptions(); this.globalOptions = parent.getGlobalOptions();
this.packageOptions = parent.getGlobalOptions(); this.packageOptions = parent.getGlobalOptions();
this.packageOptions.showQualified = false; this.packageOptions.showQualified = false;
this.myGlobalOptions = parent.getGlobalOptions(); this.myGlobalOptions = parent.getGlobalOptions();
this.myGlobalOptions.setOption(new String[] { "output", outputPath }); this.myGlobalOptions.setOption(new String[] { "output", outputPath });
this.myGlobalOptions.setOption(HIDE_OPTIONS); this.myGlobalOptions.setOption(HIDE_OPTIONS);
this.hideOptions = parent.getGlobalOptions(); this.hideOptions = parent.getGlobalOptions();
this.hideOptions.setOption(HIDE_OPTIONS); this.hideOptions.setOption(HIDE_OPTIONS);
this.centerOptions = parent.getGlobalOptions(); this.centerOptions = parent.getGlobalOptions();
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())), this.matcher = new ContextMatcher(root, Pattern.compile(Pattern.quote(cd.getQualifiedName().toString())), myGlobalOptions, true);
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() ModuleElement md = ElementUtil.getModuleOf(root, contextCenter);
+ ".dot"; String pathPrefix = Runtime.version().major() > 10 && md != null ? md.getQualifiedName().toString() + "/" : "";
this.myGlobalOptions.setOption(new String[] { "output", outputPath }); String outputPath = pathPrefix + ElementUtil.getPackageOf(root, cd).getQualifiedName().toString().replace('.', '/') + "/" + cd.getSimpleName() + ".dot";
matcher.setContextCenter(Pattern.compile(Pattern.quote(cd.toString()))); this.myGlobalOptions.setOption(new String[] { "output", outputPath });
matcher.setContextCenter(Pattern.compile(Pattern.quote(cd.toString())));
} }
public String getDisplayName() { public String getDisplayName() {
return "Context view for class " + cd; return "Context view for class " + cd;
} }
public Options getGlobalOptions() { public Options getGlobalOptions() {
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;
} }
Options optionClone = (Options) opt.clone(); Options optionClone = (Options) opt.clone();
overrideForClass(optionClone, cd); overrideForClass(optionClone, cd);
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(); }
overrideForClass(optionClone, name); Options optionClone = (Options) opt.clone();
return optionClone; overrideForClass(optionClone, name);
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)) }
opt.nodeFillColor = "lemonChiffon"; if (cd.equals(this.cd)) {
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.
@ -33,12 +33,12 @@ public enum Font {
// Static initialization of further values. // Static initialization of further values.
static { static {
// use an appropriate font depending on the current operating system // use an appropriate font depending on the current operating system
if (System.getProperty("os.name").toLowerCase().contains("windows")) { if (System.getProperty("os.name").toLowerCase().contains("windows")) {
DEFAULT_FONT = "Arial"; DEFAULT_FONT = "Arial";
} else { } else {
DEFAULT_FONT = "Helvetica"; // TODO: can we use just "sans"? DEFAULT_FONT = "Helvetica"; // TODO: can we use just "sans"?
} }
} }
/** /**
@ -49,53 +49,53 @@ public enum Font {
* @return Wrapped text * @return Wrapped text
*/ */
public String wrap(Options opt, String text) { public String wrap(Options opt, String text) {
if (text.isEmpty() || this == NORMAL) if (text.isEmpty() || this == NORMAL)
return text; return text;
String face = null; String face = null;
double size = -1; double size = -1;
boolean italic = false; boolean italic = false;
switch (this) { switch (this) {
case EDGE: case EDGE:
case NODE: case NODE:
// Not used with the wrap function. // Not used with the wrap function.
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
case ABSTRACT: case ABSTRACT:
italic = opt.nodeFontAbstractItalic; italic = opt.nodeFontAbstractItalic;
case NORMAL: case NORMAL:
break; break;
case CLASS_ABSTRACT: case CLASS_ABSTRACT:
italic = opt.nodeFontAbstractItalic; italic = opt.nodeFontAbstractItalic;
case CLASS: case CLASS:
face = opt.nodeFontClassName; face = opt.nodeFontClassName;
size = opt.nodeFontClassSize; size = opt.nodeFontClassSize;
break; break;
case PACKAGE: case PACKAGE:
face = opt.nodeFontPackageName; face = opt.nodeFontPackageName;
size = opt.nodeFontPackageSize; size = opt.nodeFontPackageSize;
break; break;
case TAG: case TAG:
face = opt.nodeFontTagName; face = opt.nodeFontTagName;
size = opt.nodeFontTagSize; size = opt.nodeFontTagSize;
break; break;
} }
if (face == null && size < 0 && !italic) if (face == null && size < 0 && !italic)
return text; return text;
StringBuilder buf = new StringBuilder(text.length() + 100); StringBuilder buf = new StringBuilder(text.length() + 100);
if (face != null || size > 0) { if (face != null || size > 0) {
buf.append("<font"); buf.append("<font");
if (face != null) if (face != null)
buf.append(" face=\"").append(face).append('"'); buf.append(" face=\"").append(face).append('"');
if (size > 0) if (size > 0)
buf.append(" point-size=\"").append(size).append('"'); buf.append(" point-size=\"").append(size).append('"');
buf.append('>'); buf.append('>');
} }
if (italic) if (italic)
buf.append("<i>"); buf.append("<i>");
buf.append(text); buf.append(text);
if (italic) if (italic)
buf.append("</i>"); buf.append("</i>");
if (face != null || size > 0) if (face != null || size > 0)
buf.append("</font>"); buf.append("</font>");
return buf.toString(); return buf.toString();
} }
} }

View File

@ -2,41 +2,51 @@ 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 * Matches every class that implements (directly or indirectly) an interfaces
* interfaces matched by regular expression provided. * matched by regular expression provided.
*/ */
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);
return true; if (iType != null && matches(iType)) {
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 * A factory class that builds Options object for general use or for a specific class
* specific 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,16 +43,17 @@ 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.
* <p>Will be used to provide progress feedback on the console * <p>
* Will be used to provide progress feedback on the console
*/ */
public String getDisplayName(); public String getDisplayName();
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +1,33 @@
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
@ -12,70 +18,78 @@ import com.sun.javadoc.RootDoc;
* single {@linkplain ClassMatcher}, and provides some extra configuration such * single {@linkplain ClassMatcher}, and provides some extra configuration such
* as output path configuration (and it is specified in code rather than in * as output path configuration (and it is specified in code rather than in
* javadoc comments). * javadoc comments).
*
* @author wolf * @author wolf
* *
*/ */
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.opt = parent.getGlobalOptions(); this.matcher = new PackageMatcher(root, pd);
this.opt.setOptions(pd); this.opt = parent.getGlobalOptions();
this.outputPath = pd.name().replace('.', '/') + "/" + pd.name() + ".dot"; this.opt.setOptions(docTrees, pd);
ModuleElement md = ElementUtil.getModuleOf(root, pd);
String pathPrefix = Runtime.version().major() > 10 && md != null ? md.getQualifiedName().toString() + "/" : "";
this.outputPath = pathPrefix + pd.getQualifiedName().toString().replace('.', '/') + "/" + pd.getSimpleName().toString() + ".dot";
} }
public String getDisplayName() { public String getDisplayName() {
return "Package view for package " + pd; return "Package view for package " + pd;
} }
public Options getGlobalOptions() { public Options getGlobalOptions() {
Options go = parent.getGlobalOptions(); Options go = parent.getGlobalOptions();
go.setOption(new String[] { "output", outputPath }); go.setOption(new String[] { "output", outputPath });
go.setOption(HIDE); go.setOption(HIDE);
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());
opt.setOption(HIDE); if (!included || this.opt.matchesHideExpression(cd.getQualifiedName())) {
opt.setOption(HIDE);
}
} }
public void overrideForClass(Options opt, String className) { public void overrideForClass(Options opt, CharSequence className) {
opt.showQualified = false; 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,26 +18,27 @@ 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
* name * name
*
* @author wolf * @author wolf
*/ */
public class PatternMatcher implements ClassMatcher { public class PatternMatcher implements ClassMatcher {
Pattern pattern; Pattern pattern;
public PatternMatcher(Pattern pattern) { public PatternMatcher(Pattern pattern) {
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

@ -9,32 +9,35 @@ public enum RelationDirection {
/** /**
* Adds the current direction * Adds the current direction
*
* @param d * @param d
* @return * @return
*/ */
public RelationDirection sum(RelationDirection d) { public RelationDirection sum(RelationDirection d) {
// Handle same and nones first: // Handle same and nones first:
return (this == d || d == NONE) ? this : // return (this == d || d == NONE) ? this : //
this == NONE ? d : BOTH; // They are different and not none. this == NONE ? d : BOTH; // They are different and not none.
} }
/** /**
* Returns true if this direction "contains" the specified one, that is, * Returns true if this direction "contains" the specified one, that is, either
* either it's equal to it, or this direction is {@link #BOTH} * it's equal to it, or this direction is {@link #BOTH}
*
* @param d * @param d
* @return * @return
*/ */
public boolean contains(RelationDirection d) { public boolean contains(RelationDirection d) {
return this == BOTH ? true : (d == this); return this == BOTH ? true : (d == this);
} }
/** /**
* Inverts the direction of the relation. Turns IN into OUT and vice-versa, NONE and BOTH * Inverts the direction of the relation. Turns IN into OUT and vice-versa, NONE
* are not changed * and BOTH are not changed
*
* @return * @return
*/ */
public RelationDirection inverse() { public RelationDirection inverse() {
return this == IN ? OUT : this == OUT ? IN : this; return this == IN ? OUT : this == OUT ? IN : this;
} }
}; };

View File

@ -2,6 +2,7 @@ package org.umlgraph.doclet;
/** /**
* A map from relation types to directions * A map from relation types to directions
*
* @author wolf * @author wolf
* *
*/ */
@ -13,38 +14,41 @@ public class RelationPattern {
/** /**
* Creates a new pattern using the same direction for every relation kind * Creates a new pattern using the same direction for every relation kind
*
* @param defaultDirection The direction used to initialize this pattern * @param defaultDirection The direction used to initialize this pattern
*/ */
public RelationPattern(RelationDirection defaultDirection) { public RelationPattern(RelationDirection defaultDirection) {
directions = new RelationDirection[RelationType.values().length]; directions = new RelationDirection[RelationType.values().length];
for (int i = 0; i < directions.length; i++) { for (int i = 0; i < directions.length; i++) {
directions[i] = defaultDirection; directions[i] = defaultDirection;
} }
} }
/** /**
* Adds, eventually merging, a direction for the specified relation type * Adds, eventually merging, a direction for the specified relation type
*
* @param relationType * @param relationType
* @param direction * @param direction
*/ */
public void addRelation(RelationType relationType, RelationDirection direction) { public void addRelation(RelationType relationType, RelationDirection direction) {
int idx = relationType.ordinal(); int idx = relationType.ordinal();
directions[idx] = directions[idx].sum(direction); directions[idx] = directions[idx].sum(direction);
} }
/** /**
* Returns true if this patterns matches at least the direction of one * Returns true if this patterns matches at least the direction of one of the
* of the relations in the other relation patterns. Matching is defined * relations in the other relation patterns. Matching is defined by
* by {@linkplain RelationDirection#contains(RelationDirection)} * {@linkplain RelationDirection#contains(RelationDirection)}
*
* @param relationPattern * @param relationPattern
* @return * @return
*/ */
public boolean matchesOne(RelationPattern relationPattern) { public boolean matchesOne(RelationPattern relationPattern) {
for (int i = 0; i < directions.length; i++) { for (int i = 0; i < directions.length; i++) {
if (directions[i].contains(relationPattern.directions[i])) if (directions[i].contains(relationPattern.directions[i]))
return true; return true;
} }
return false; return false;
} }
} }

View File

@ -27,8 +27,8 @@ public enum RelationType {
/** Enum constructors must be private */ /** Enum constructors must be private */
private RelationType(String style, boolean backorder) { private RelationType(String style, boolean backorder) {
this.lower = toString().toLowerCase(); this.lower = toString().toLowerCase();
this.style = style; this.style = style;
this.backorder = backorder; this.backorder = backorder;
} }
} }

View File

@ -45,10 +45,10 @@ public enum Shape {
/** Initialize the lookup index */ /** Initialize the lookup index */
static { static {
for (Shape s : Shape.values()) { for (Shape s : Shape.values()) {
index.put(s.name(), s); index.put(s.name(), s);
index.put(s.name().toLowerCase(Locale.ROOT), s); index.put(s.name().toLowerCase(Locale.ROOT), s);
} }
} }
/** /**
@ -59,25 +59,25 @@ public enum Shape {
* @return Shape * @return Shape
*/ */
public static Shape of(String s) { public static Shape of(String s) {
Shape shp = index.get(s); Shape shp = index.get(s);
if (shp != null) if (shp != null)
return shp; return shp;
System.err.println("Ignoring invalid shape: " + s); System.err.println("Ignoring invalid shape: " + s);
return CLASS; return CLASS;
} }
/** Enum constructor, must be private! */ /** Enum constructor, must be private! */
private Shape(String style) { private Shape(String style) {
this.style = style; this.style = style;
} }
/** Return the table border required for the shape */ /** Return the table border required for the shape */
public String extraColumn() { public String extraColumn() {
return this == Shape.ACTIVECLASS ? ("<td rowspan=\"10\"></td>") : ""; return this == Shape.ACTIVECLASS ? ("<td rowspan=\"10\"></td>") : "";
} }
/** Return the cell border required for the shape */ /** Return the cell border required for the shape */
public String cellBorder() { public String cellBorder() {
return this == CLASS || this == ACTIVECLASS ? "1" : "0"; return this == CLASS || this == ACTIVECLASS ? "1" : "0";
} }
} }

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;
@ -31,37 +34,37 @@ import java.util.regex.Pattern;
class StringUtil { class StringUtil {
/** Tokenize string s into an array */ /** Tokenize string s into an array */
public static String[] tokenize(String s) { public static String[] tokenize(String s) {
ArrayList<String> r = new ArrayList<String>(); ArrayList<String> r = new ArrayList<String>();
String remain = s; String remain = s;
int n = 0, pos; int n = 0, pos;
remain = remain.trim(); remain = remain.trim();
while (remain.length() > 0) { while (remain.length() > 0) {
if (remain.startsWith("\"")) { if (remain.startsWith("\"")) {
// Field in quotes // Field in quotes
pos = remain.indexOf('"', 1); pos = remain.indexOf('"', 1);
if (pos == -1) if (pos == -1)
break; break;
r.add(remain.substring(1, pos)); r.add(remain.substring(1, pos));
if (pos + 1 < remain.length()) if (pos + 1 < remain.length())
pos++; pos++;
} else { } else {
// Space-separated field // Space-separated field
pos = remain.indexOf(' ', 0); pos = remain.indexOf(' ', 0);
if (pos == -1) { if (pos == -1) {
r.add(remain); r.add(remain);
remain = ""; remain = "";
} else } else
r.add(remain.substring(0, pos)); r.add(remain.substring(0, pos));
} }
remain = remain.substring(pos + 1); remain = remain.substring(pos + 1);
remain = remain.trim(); remain = remain.trim();
// - is used as a placeholder for empy fields // - is used as a placeholder for empy fields
if (r.get(n).equals("-")) if (r.get(n).equals("-"))
r.set(n, ""); r.set(n, "");
n++; n++;
} }
return r.toArray(new String[0]); return r.toArray(new String[0]);
} }
private final static Pattern ESCAPE_BASIC_XML = Pattern.compile("[&<>]"); private final static Pattern ESCAPE_BASIC_XML = Pattern.compile("[&<>]");
@ -71,47 +74,47 @@ class StringUtil {
* HTML entity code. * HTML entity code.
*/ */
public static String escape(String s) { public static String escape(String s) {
if (ESCAPE_BASIC_XML.matcher(s).find()) { if (ESCAPE_BASIC_XML.matcher(s).find()) {
StringBuilder sb = new StringBuilder(s); StringBuilder sb = new StringBuilder(s);
for (int i = 0; i < sb.length();) { for (int i = 0; i < sb.length();) {
switch (sb.charAt(i)) { switch (sb.charAt(i)) {
case '&': case '&':
sb.replace(i, i + 1, "&amp;"); sb.replace(i, i + 1, "&amp;");
i += "&amp;".length(); i += "&amp;".length();
break; break;
case '<': case '<':
sb.replace(i, i + 1, "&lt;"); sb.replace(i, i + 1, "&lt;");
i += "&lt;".length(); i += "&lt;".length();
break; break;
case '>': case '>':
sb.replace(i, i + 1, "&gt;"); sb.replace(i, i + 1, "&gt;");
i += "&gt;".length(); i += "&gt;".length();
break; break;
default: default:
i++; i++;
} }
} }
return sb.toString(); return sb.toString();
} else } else
return s; return s;
} }
/** /**
* Convert embedded newlines into HTML line breaks * Convert embedded newlines into HTML line breaks
*/ */
public static String htmlNewline(String s) { public static String htmlNewline(String s) {
if (s.indexOf('\n') == -1) if (s.indexOf('\n') == -1)
return s; return s;
StringBuilder sb = new StringBuilder(s); StringBuilder sb = new StringBuilder(s);
for (int i = 0; i < sb.length();) { for (int i = 0; i < sb.length();) {
if (sb.charAt(i) == '\n') { if (sb.charAt(i) == '\n') {
sb.replace(i, i + 1, "<br/>"); sb.replace(i, i + 1, "<br/>");
i += "<br/>".length(); i += "<br/>".length();
} else } else
i++; i++;
} }
return sb.toString(); return sb.toString();
} }
/** /**
@ -119,22 +122,22 @@ class StringUtil {
* characters. * characters.
*/ */
public static String guillemize(Options opt, String s) { public static String guillemize(Options opt, String s) {
StringBuilder r = new StringBuilder(s); StringBuilder r = new StringBuilder(s);
for (int i = 0; i < r.length();) for (int i = 0; i < r.length();)
switch (r.charAt(i)) { switch (r.charAt(i)) {
case '<': case '<':
r.replace(i, i + 1, opt.guilOpen); r.replace(i, i + 1, opt.guilOpen);
i += opt.guilOpen.length(); i += opt.guilOpen.length();
break; break;
case '>': case '>':
r.replace(i, i + 1, opt.guilClose); r.replace(i, i + 1, opt.guilClose);
i += opt.guilClose.length(); i += opt.guilClose.length();
break; break;
default: default:
i++; i++;
break; break;
} }
return r.toString(); return r.toString();
} }
/** /**
@ -144,45 +147,49 @@ class StringUtil {
* @return the wrapped <code>String</code>. * @return the wrapped <code>String</code>.
*/ */
public static String guilWrap(Options opt, String str) { public static String guilWrap(Options opt, String str) {
return opt.guilOpen + str + opt.guilClose; return opt.guilOpen + str + opt.guilClose;
} }
/** 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()); }
for (int i = 0, depth = 0; i < name.length(); i++) { StringBuilder buf = new StringBuilder(name.length());
char c = name.charAt(i); for (int i = 0, depth = 0; i < name.length(); i++) {
if (c == '<') char c = name.charAt(i);
depth++; if (c == '<')
else if (c == '>') depth++;
depth--; else if (c == '>')
else if (depth == 0) depth--;
buf.append(c); else if (depth == 0)
} buf.append(c);
return buf.toString(); }
return elements.getName(buf.toString());
} }
public static String buildRelativePathFromClassNames(String contextPackageName, String classPackageName) { public static String buildRelativePathFromClassNames(Name contextPackageName, String classPackageName) {
// path, relative to the root, of the destination class // 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 }
StringBuilder buf = new StringBuilder(classPackageName.length()); // ... go up with ".." to reach the common root
for (int j = i; j < contextClassPath.length; j++) StringBuilder buf = new StringBuilder(classPackageName.length());
buf.append("../"); for (int j = i; j < contextClassPath.length; j++) {
// ... go down from the common root to the destination buf.append("../");
for (int j = i; j < currClassPath.length; j++) }
buf.append(currClassPath[j]).append('/'); // Always use HTML seperators // ... go down from the common root to the destination
return buf.toString(); for (int j = i; j < currClassPath.length; j++) {
buf.append(currClassPath[j]).append('/'); // Always use HTML seperators
}
return buf.toString();
} }
/** /**
@ -195,26 +202,27 @@ 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) {
int gen = className.indexOf('<'); // Begin before generics. String className = name.toString();
int end = gen >= 0 ? gen : className.length(); int gen = className.indexOf('<'); // Begin before generics.
int start = className.lastIndexOf('.', end); int end = gen >= 0 ? gen : className.length();
// No package name special cases: int start = className.lastIndexOf('.', end);
if (start < 0) // No package name special cases:
return gen >= 0 || className.isEmpty() ? -1 // if (start < 0)
: Character.isLowerCase(className.charAt(0)) ? end : -1; return gen >= 0 || className.isEmpty() ? -1 //
int split = end; : Character.isLowerCase(className.charAt(0)) ? end : -1;
while (true) { int split = end;
if (Character.isLowerCase(className.charAt(start + 1))) while (true) {
return split; if (Character.isLowerCase(className.charAt(start + 1)))
split = start; // Continue, this looks like a class name. return split;
if (start < 0) split = start; // Continue, this looks like a class name.
return -1; if (start < 0)
start = className.lastIndexOf('.', start - 1); return -1;
} start = className.lastIndexOf('.', start - 1);
}
} }
/** /**
@ -226,6 +234,6 @@ class StringUtil {
* @return Formatted value * @return Formatted value
*/ */
public static String fmt(double val) { public static String fmt(double val) {
return val == Math.round(val) ? Long.toString((long) val) : Double.toString(val); return val == Math.round(val) ? Long.toString((long) val) : Double.toString(val);
} }
} }

View File

@ -2,35 +2,40 @@ 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 * Matches every class that extends (directly or indirectly) a class matched by
* matched by the regular expression provided. * the regular expression provided.
*/ */
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

@ -2,8 +2,10 @@ package org.umlgraph.doclet;
/** /**
* Options for UMLGraph class diagram generation * Options for UMLGraph class diagram generation
*
* @opt operations * @opt operations
* @opt visibility * @opt visibility
* @hidden * @hidden
*/ */
public class UMLOptions {} public class UMLOptions {
}

View File

@ -20,17 +20,30 @@
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
*
* @depend - - - OptionProvider * @depend - - - OptionProvider
* @depend - - - Options * @depend - - - Options
* @depend - - - View * @depend - - - View
@ -40,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";
@ -48,141 +61,195 @@ 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 public UmlGraph() {
for (int i = 0; i < views.length; i++) this.options = new Options();
buildGraph(root, views[i], null); this.standard = new StandardDoclet();
return true;
} }
public static void main(String args[]) { @Override
PrintWriter err = new PrintWriter(System.err); public void init(Locale locale, Reporter reporter) {
com.sun.tools.javadoc.Main.execute(programName, this.locale = locale;
err, err, err, docletName, args); this.reporter = reporter;
} }
public static Options getCommentOptions() { @Override
return commentOptions; public String getName() {
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();
} }
/** /**
* Creates the base Options object. * Standard doclet entry point
* This contains both the options specified on the command *
* line and the ones specified in the UMLOptions class, if available. * @param root
* Also create the globally accessible commentOptions object. * @return
*/ */
public static Options buildOptions(RootDoc root) { @Override
commentOptions = new Options(); /** Entry point through javadoc */
commentOptions.setOptions(root.options()); public boolean run(DocletEnvironment root) {
commentOptions.setOptions(findClass(root, "UMLNoteOptions")); reporter.print(Diagnostic.Kind.NOTE, "UMLGraph doclet version " + Version.VERSION + " started");
commentOptions.shape = Shape.NOTE;
Options opt = new Options(); List<View> views = buildViews(options, root, root);
opt.setOptions(root.options()); if (views == null) {
opt.setOptions(findClass(root, "UMLOptions")); return false;
return opt; }
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() {
return commentOptions;
}
/**
* Creates the base Options object. This contains both the options specified on
* the command line and the ones specified in the UMLOptions class, if
* available. Also create the globally accessible commentOptions object.
*/
public static Options buildOptions(DocletEnvironment root, Options o) {
commentOptions = o.clone();
commentOptions.setOptions(root.getDocTrees(), findClass(root, "UMLNoteOptions"));
commentOptions.shape = Shape.NOTE;
Options opt = o.clone();
opt.setOptions(root.getDocTrees(), findClass(root, "UMLOptions"));
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(); }
root.printNotice("Building " + op.getDisplayName()); Options opt = op.getGlobalOptions();
ClassDoc[] classes = root.classes(); reporter.print(Diagnostic.Kind.NOTE, "Building " + op.getDisplayName());
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) }
c.printRelations(cd); for (TypeElement cd : classes) {
if(opt.inferRelationships) c.printRelations(cd);
for (ClassDoc cd : classes) }
c.printInferredRelations(cd); if (opt.inferRelationships) {
if(opt.inferDependencies) for (TypeElement cd : classes) {
for (ClassDoc cd : classes) c.printInferredRelations(cd);
c.printInferredDependencies(cd); }
}
if (opt.inferDependencies) {
for (TypeElement cd : classes) {
c.printInferredDependencies(cd);
}
}
c.printExtraClasses(root); c.printExtraClasses(root);
c.epilogue(); c.epilogue();
} }
/** /**
* Builds the views according to the parameters on the command line * Builds the views according to the parameters on the command line
* @param opt The options *
* @param srcRootDoc The RootDoc for the source classes * @param opt The options
* @param viewRootDoc The RootDoc for the view classes (may be * @param srcRootDoc The RootDoc for the source classes
* different, or may be the same as the srcRootDoc) * @param viewRootDoc The RootDoc for the view classes (may be different, or may
* be the same as the srcRootDoc)
*/ */
public static View[] buildViews(Options opt, RootDoc srcRootDoc, RootDoc viewRootDoc) { public static List<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,268 +3,289 @@ 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,
* runs the generation of dot files by UMLGraph * runs the generation of dot files by UMLGraph
*
* @author wolf * @author wolf
* *
* @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 them, private Locale locale;
* 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();
} }
/** /**
* Standard doclet entry point * Standard doclet entry point
*
* @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);
try { reporter.print(Diagnostic.Kind.NOTE, "UmlGraphDoc version " + Version.VERSION + ", altering javadocs");
String outputFolder = findOutputPath(root.options()); try {
Options opt = UmlGraph.buildOptions(root, this.options);
// in javadoc enumerations are always printed
opt.showEnumerations = true;
opt.relativeLinksForSourcePackages = true;
// enable strict matching for hide expressions
opt.strictMatching = true;
reporter.print(Diagnostic.Kind.NOTE, opt.toString());
Options opt = UmlGraph.buildOptions(root); generatePackageDiagrams(root, reporter, opt, this.options.outputDirectory);
opt.setOptions(root.options()); generateContextDiagrams(root, reporter, opt, this.options.outputDirectory);
// in javadoc enumerations are always printed } catch (Throwable t) {
opt.showEnumerations = true; reporter.print(Diagnostic.Kind.WARNING, "Error: " + t.toString());
opt.relativeLinksForSourcePackages = true; t.printStackTrace();
// enable strict matching for hide expressions return false;
opt.strictMatching = true; }
// root.printNotice(opt.toString()); return true;
generatePackageDiagrams(root, opt, outputFolder);
generateContextDiagrams(root, opt, outputFolder);
} catch(Throwable t) {
root.printWarning("Error: " + t.toString());
t.printStackTrace();
return false;
}
return true;
} }
/** /**
* Standand doclet entry * Generates the package diagrams for all of the packages that contain classes
* @return * among those returned by RootDoc.class()
*/ */
public static LanguageVersion languageVersion() { private static void generatePackageDiagrams(DocletEnvironment root, Reporter reporter, Options opt, String outputFolder) throws IOException {
return Standard.languageVersion(); Set<Name> packages = new HashSet<>();
} for (Element classDoc : root.getIncludedElements()) {
PackageElement packageDoc = null;
/** if (classDoc instanceof PackageElement) {
* Generates the package diagrams for all of the packages that contain classes among those packageDoc = (PackageElement) classDoc;
* returned by RootDoc.class() } else if (classDoc instanceof TypeElement) {
*/ packageDoc = ElementUtil.getPackageOf(root, classDoc);
private static void generatePackageDiagrams(RootDoc root, Options opt, String outputFolder) } else {
throws IOException { continue;
Set<String> packages = new HashSet<String>(); }
for (ClassDoc classDoc : root.classes()) { if (!packages.contains(packageDoc.getSimpleName())) {
PackageDoc packageDoc = classDoc.containingPackage(); reporter.print(Diagnostic.Kind.WARNING, "Package processed " + packageDoc.getQualifiedName());
if(!packages.contains(packageDoc.name())) { packages.add(packageDoc.getSimpleName());
packages.add(packageDoc.name()); OptionProvider view = new PackageView(outputFolder, packageDoc, root, opt);
OptionProvider view = new PackageView(outputFolder, packageDoc, root, opt); UmlGraph.buildGraph(reporter, root, opt, view, packageDoc);
UmlGraph.buildGraph(root, view, packageDoc); runGraphviz(opt.dotExecutable, outputFolder, ElementUtil.getModuleOf(root, packageDoc), packageDoc.getQualifiedName(), packageDoc.getSimpleName(), reporter);
runGraphviz(opt.dotExecutable, outputFolder, packageDoc.name(), packageDoc.name(), root); alterHtmlDocs(opt, outputFolder, ElementUtil.getModuleOf(root, packageDoc), packageDoc.getQualifiedName(), packageDoc.getSimpleName(), "package-summary.html",
alterHtmlDocs(opt, outputFolder, packageDoc.name(), packageDoc.name(), Pattern.compile("(</[Hh]2>)|(<h1 title=\"Package\").*"), reporter);
"package-summary.html", Pattern.compile("(</[Hh]2>)|(<h1 title=\"Package\").*"), root); }
} }
}
} }
/** /**
* 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) private static void generateContextDiagrams(DocletEnvironment root, Reporter reporter, Options opt, String outputFolder) throws IOException {
throws IOException { Set<TypeElement> classDocs = new TreeSet<>(new Comparator<>() {
Set<ClassDoc> classDocs = new TreeSet<ClassDoc>(new Comparator<ClassDoc>() { public int compare(TypeElement cd1, TypeElement cd2) {
public int compare(ClassDoc cd1, ClassDoc cd2) { return cd1.getSimpleName().toString().compareTo(cd2.getSimpleName().toString());
return cd1.name().compareTo(cd2.name());
} }
}); });
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) {
try { reporter.print(Diagnostic.Kind.WARNING, "Class processed " + classDoc.getQualifiedName());
if(view == null) try {
view = new ContextView(outputFolder, classDoc, root, opt); if (view == null) {
else view = new ContextView(outputFolder, classDoc, root, opt);
view.setContextCenter(classDoc); } else {
UmlGraph.buildGraph(root, view, classDoc); view.setContextCenter(classDoc);
runGraphviz(opt.dotExecutable, outputFolder, classDoc.containingPackage().name(), classDoc.name(), root); }
alterHtmlDocs(opt, outputFolder, classDoc.containingPackage().name(), classDoc.name(), UmlGraph.buildGraph(reporter, root, opt, view, classDoc);
classDoc.name() + ".html", Pattern.compile(".*(Class|Interface|Enum) " + classDoc.name() + ".*") , root); runGraphviz(opt.dotExecutable, outputFolder, ElementUtil.getModuleOf(root, classDoc), ElementUtil.getPackageOf(root, classDoc).getQualifiedName(), classDoc.getSimpleName(), reporter);
} catch (Exception e) { alterHtmlDocs(opt, outputFolder, ElementUtil.getModuleOf(root, classDoc), ElementUtil.getPackageOf(root, classDoc).getQualifiedName(), classDoc.getSimpleName(),
throw new RuntimeException("Error generating " + classDoc.name(), e); classDoc.getSimpleName() + ".html",
} Pattern.compile(".*(Class|Interface|Enum) " + classDoc.getSimpleName() + ".*"), reporter);
} } catch (Exception e) {
throw new RuntimeException("Error generating " + classDoc.getSimpleName(), e);
}
}
} }
/** /**
* Runs Graphviz dot building both a diagram (in png format) and a client side map for it. * Runs Graphviz dot building both a diagram (in png format) and a client side
* map for it.
*/ */
private static void runGraphviz(String dotExecutable, String outputFolder, String packageName, String name, RootDoc root) { private static void runGraphviz(String dotExecutable, String outputFolder, ModuleElement module, Name packageName, Name name, Reporter reporter) {
if (dotExecutable == null) { 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 [] { Process p = Runtime.getRuntime().exec(new String[] { dotExecutable, "-Tsvg", "-o",
dotExecutable, svgFile.getAbsolutePath(), dotFile.getAbsolutePath() });
"-Tsvg", BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
"-o", String line;
svgFile.getAbsolutePath(), while ((line = reader.readLine()) != null) {
dotFile.getAbsolutePath() reporter.print(Diagnostic.Kind.WARNING, line);
}); }
BufferedReader reader = new BufferedReader(new InputStreamReader(p.getErrorStream())); int result = p.waitFor();
String line; if (result != 0) {
while((line = reader.readLine()) != null) reporter.print(Diagnostic.Kind.WARNING, "Errors running Graphviz on " + dotFile);
root.printWarning(line); }
int result = p.waitFor(); } catch (Exception e) {
if (result != 0) e.printStackTrace();
root.printWarning("Errors running Graphviz on " + dotFile); System.err.println("Ensure that dot is in your path and that its path does not contain spaces");
} catch (Exception e) { }
e.printStackTrace();
System.err.println("Ensure that dot is in your path and that its path does not contain spaces");
}
} }
//Format string for the uml image div tag. // Format string for the uml image div tag.
private static final String UML_DIV_TAG = private static final String UML_DIV_TAG = "<div align=\"center\">"
"<div align=\"center\">" + + "<object width=\"100%%\" height=\"100%%\" type=\"image/svg+xml\" data=\"%1$s.svg\" alt=\"Package class diagram package %1$s\" border=0></object>"
"<object width=\"100%%\" height=\"100%%\" type=\"image/svg+xml\" data=\"%1$s.svg\" alt=\"Package class diagram package %1$s\" border=0></object>" + + "</div>";
"</div>";
private static final String UML_AUTO_SIZED_DIV_TAG = private static final String UML_AUTO_SIZED_DIV_TAG = "<div align=\"center\">"
"<div align=\"center\">" + + "<object type=\"image/svg+xml\" data=\"%1$s.svg\" alt=\"Package class diagram package %1$s\" border=0></object>"
"<object type=\"image/svg+xml\" data=\"%1$s.svg\" alt=\"Package class diagram package %1$s\" border=0></object>" + + "</div>";
"</div>";
private static final String EXPANDABLE_UML_STYLE = "font-family: Arial,Helvetica,sans-serif;font-size: 1.5em; display: block; width: 250px; height: 20px; background: #009933; padding: 5px; text-align: center; border-radius: 8px; color: white; font-weight: bold;"; private static final String EXPANDABLE_UML_STYLE = "font-family: Arial,Helvetica,sans-serif;font-size: 1.5em; display: block; width: 250px; height: 20px; background: #009933; padding: 5px; text-align: center; border-radius: 8px; color: white; font-weight: bold;";
//Format string for the java script tag. // Format string for the java script tag.
private static final String EXPANDABLE_UML = private static final String EXPANDABLE_UML = "<script type=\"text/javascript\">\n" + "function show() {\n"
"<script type=\"text/javascript\">\n" + + " document.getElementById(\"uml\").innerHTML = \n" + " \'<a style=\"" + EXPANDABLE_UML_STYLE
"function show() {\n" + + "\" href=\"javascript:hide()\">%3$s</a>\' +\n" + " \'%1$s\';\n" + "}\n" + "function hide() {\n"
" document.getElementById(\"uml\").innerHTML = \n" + + " document.getElementById(\"uml\").innerHTML = \n" + " \'<a style=\"" + EXPANDABLE_UML_STYLE
" \'<a style=\"" + EXPANDABLE_UML_STYLE + "\" href=\"javascript:hide()\">%3$s</a>\' +\n" + + "\" href=\"javascript:show()\">%2$s</a>\' ;\n" + "}\n" + "</script>\n" + "<div id=\"uml\" >\n"
" \'%1$s\';\n" + + " <a href=\"javascript:show()\">\n" + " <a style=\"" + EXPANDABLE_UML_STYLE
"}\n" + + "\" href=\"javascript:show()\">%2$s</a> \n" + "</div>";
"function hide() {\n" +
" document.getElementById(\"uml\").innerHTML = \n" +
" \'<a style=\"" + EXPANDABLE_UML_STYLE + "\" href=\"javascript:show()\">%2$s</a>\' ;\n" +
"}\n" +
"</script>\n" +
"<div id=\"uml\" >\n" +
" <a href=\"javascript:show()\">\n" +
" <a style=\"" + EXPANDABLE_UML_STYLE + "\" href=\"javascript:show()\">%2$s</a> \n" +
"</div>";
/** /**
* Takes an HTML file, looks for the first instance of the specified insertion point, and * Takes an HTML file, looks for the first instance of the specified insertion
* inserts the diagram image reference and a client side map in that point. * point, and inserts the diagram image reference and a client side map in that
* point.
*/ */
private static void alterHtmlDocs(Options opt, String outputFolder, String packageName, String className, 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 htmlFile = new File(output, htmlFileName); File output = new File(outputFolder, prefix + packageName.toString().replace(".", "/"));
File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml"); File htmlFile = new File(output, htmlFileName);
if (!htmlFile.exists()) { File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
System.err.println("Expected file not found: " + htmlFile.getAbsolutePath()); if (!htmlFile.exists()) {
return; System.err.println("Expected file not found: " + htmlFile.getAbsolutePath());
} return;
}
// parse & rewrite // parse & rewrite
BufferedWriter writer = null; BufferedWriter writer = null;
BufferedReader reader = null; BufferedReader reader = null;
boolean matched = false; boolean matched = false;
try { try {
writer = new BufferedWriter(new OutputStreamWriter(new writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(alteredFile), opt.outputEncoding));
FileOutputStream(alteredFile), opt.outputEncoding)); reader = new BufferedReader(new InputStreamReader(new FileInputStream(htmlFile), opt.outputEncoding));
reader = new BufferedReader(new InputStreamReader(new
FileInputStream(htmlFile), opt.outputEncoding));
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
writer.write(line); writer.write(line);
writer.newLine(); writer.newLine();
if (!matched && insertPointPattern.matcher(line).matches()) { if (!matched && insertPointPattern.matcher(line).matches()) {
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) }
tag = String.format(EXPANDABLE_UML, tag, "Show UML class diagram", "Hide UML class diagram"); if (opt.collapsibleDiagrams) {
writer.write("<!-- UML diagram added by UMLGraph version " + tag = String.format(EXPANDABLE_UML, tag, "Show UML class diagram", "Hide UML class diagram");
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);
writer.newLine(); writer.newLine();
} }
} }
} finally { } finally {
if (writer != null) if (writer != null) {
writer.close(); writer.close();
if (reader != null) }
reader.close(); if (reader != null) {
} 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
if (matched) { if (matched) {
htmlFile.delete(); htmlFile.delete();
alteredFile.renameTo(htmlFile); alteredFile.renameTo(htmlFile);
} else { } else {
root.printNotice("Warning, could not find a line that matches the pattern '" + insertPointPattern.pattern() reporter.print(Diagnostic.Kind.NOTE, "Warning, could not find a line that matches the 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

@ -1,4 +1,6 @@
/* Automatically generated file */ /* Automatically generated file */
package org.umlgraph.doclet; package org.umlgraph.doclet;
class Version { public static String VERSION = "R5_7_2-60-g0e99a6";}
class Version {
public static String VERSION = "R5_7_2-60-g0e99a6";
}

View File

@ -23,15 +23,19 @@ 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
* will lead to the creation of a UML class diagram. Multiple views can be * will lead to the creation of a UML class diagram. Multiple views can be
* defined on the same source tree, effectively allowing to create multiple * defined on the same source tree, effectively allowing to create multiple
* class diagram out of it. * class diagram out of it.
*
* @author wolf * @author wolf
* *
* @depend - - - Options * @depend - - - Options
@ -43,131 +47,137 @@ 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()); }
opts[0] = "-" + opts[0]; if (tags.get("opt") != null) {
if (currMatcher == null) { for (String text : tags.get("opt")) {
globalOptions.add(opts); String[] opts = StringUtil.tokenize(text);
} else { opts[0] = "-" + opts[0];
optionOverrides.get(currMatcher).add(opts); if (currMatcher == null) {
} globalOptions.add(opts);
} } else {
} optionOverrides.get(currMatcher).add(opts);
}
}
}
} }
/** /**
* Factory method that builds the appropriate matcher for @match tags * Factory method that builds the appropriate matcher for @match tags
*/ */
private ClassMatcher buildMatcher(String tagText) { private ClassMatcher buildMatcher(String tagText) {
// check there are at least @match <type> and a parameter // check there are at least @match <type> and a parameter
String[] strings = StringUtil.tokenize(tagText); String[] strings = StringUtil.tokenize(tagText);
if (strings.length < 2) { if (strings.length < 2) {
System.err.println("Skipping uncomplete @match tag, type missing: " + tagText + " in view " + viewDoc); System.err.println("Skipping uncomplete @match tag, type missing: " + tagText + " in view " + viewDoc);
return null; return null;
} }
try { try {
if (strings[0].equals("class")) { if (strings[0].equals("class")) {
return new PatternMatcher(Pattern.compile(strings[1])); return new PatternMatcher(Pattern.compile(strings[1]));
} else if (strings[0].equals("context")) { } else if (strings[0].equals("context")) {
return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(), return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(), false);
false); } else if (strings[0].equals("outgoingContext")) {
} else if (strings[0].equals("outgoingContext")) { return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(), false);
return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(), } else if (strings[0].equals("interface")) {
false); return new InterfaceMatcher(root, Pattern.compile(strings[1]));
} else if (strings[0].equals("interface")) { } else if (strings[0].equals("subclass")) {
return new InterfaceMatcher(root, Pattern.compile(strings[1])); return new SubclassMatcher(root, Pattern.compile(strings[1]));
} else if (strings[0].equals("subclass")) { } else {
return new SubclassMatcher(root, Pattern.compile(strings[1])); System.err.println("Skipping @match tag, unknown match type, in view " + viewDoc);
} else { }
System.err.println("Skipping @match tag, unknown match type, in view " + viewDoc); } catch (PatternSyntaxException pse) {
} System.err.println("Skipping @match tag due to invalid regular expression '" + tagText + "'" + " in view " + viewDoc);
} catch (PatternSyntaxException pse) { } catch (Exception e) {
System.err.println("Skipping @match tag due to invalid regular expression '" + tagText System.err.println("Skipping @match tag due to an internal error '" + tagText + "'" + " in view " + viewDoc);
+ "'" + " in view " + viewDoc); e.printStackTrace();
} catch (Exception e) { }
System.err.println("Skipping @match tag due to an internal error '" + tagText return null;
+ "'" + " in view " + viewDoc);
e.printStackTrace();
}
return null;
} }
// ---------------------------------------------------------------- // ----------------------------------------------------------------
// 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;
} }
public Options getGlobalOptions() { public Options getGlobalOptions() {
Options go = provider.getGlobalOptions(); Options go = provider.getGlobalOptions();
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) }
go.setOption(new String[] { "output", viewDoc.name() + ".dot" }); if (!outputSet) {
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,11 +18,15 @@
*/ */
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
* private visibility is referred as PACKAGE. * private visibility is referred as PACKAGE.
*
* @author wolf * @author wolf
*/ */
public enum Visibility { public enum Visibility {
@ -31,17 +35,19 @@ public enum Visibility {
final public String symbol; final public String symbol;
private Visibility(String symbol) { private Visibility(String symbol) {
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();
return PRIVATE; if (mods.contains(Modifier.PRIVATE)) {
else if (doc.isPackagePrivate()) return PRIVATE;
return PACKAGE; } else if (mods.contains(Modifier.PROTECTED)) {
else if (doc.isProtected()) return PROTECTED;
return PROTECTED; } else if (mods.contains(Modifier.PUBLIC)) {
else 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;
}
}

View File

@ -26,6 +26,14 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.tools.DocumentationTool;
import javax.tools.ToolProvider;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.umlgraph.doclet.UmlGraph;
/** /**
* UmlGraph regression tests * UmlGraph regression tests
* @author wolf * @author wolf
@ -41,12 +49,15 @@ public class BasicTest {
static PrintWriter pw = new PrintWriter(System.out); static PrintWriter pw = new PrintWriter(System.out);
public static void main(String[] args) throws IOException { @Test
@Ignore
public void test() throws IOException {
List<String> differences = new ArrayList<String>(); List<String> differences = new ArrayList<String>();
File outFolder = new File(testDestFolder); File outFolder = new File(testDestFolder);
if (!outFolder.exists()) if (!outFolder.exists()) {
outFolder.mkdirs(); outFolder.mkdirs();
}
TestUtils.cleanFolder(outFolder, true); TestUtils.cleanFolder(outFolder, true);
@ -64,14 +75,14 @@ public class BasicTest {
pw.println(); pw.println();
pw.println(); pw.println();
pw.flush(); pw.flush();
System.exit(differences.size() > 0 ? 1 : 0); Assert.assertEquals("Some differences found " + differences.size(), 0, differences.size());
} }
private static void performViewTests(List<String> differences, File outFolder) private static void performViewTests(List<String> differences, File outFolder)
throws IOException { throws IOException {
String[] options = new String[] { "-docletpath", "build", "-private", "-d", String[] options = new String[] { "--docletpath=build", "-private",
outFolder.getAbsolutePath(), "-sourcepath", "testdata/java", "-compact", "--d=\"" + outFolder.getAbsolutePath() + "\"", "--sourcepath=\"testdata/java\"", "-compact",
"-subpackages", "gr.spinellis", "-views" }; "--subpackages=\"gr.spinellis\"", "-views" };
runDoclet(options); runDoclet(options);
List<String> viewFiles = new ArrayList<String>(); List<String> viewFiles = new ArrayList<String>();
@ -118,8 +129,8 @@ public class BasicTest {
dotFile.delete(); dotFile.delete();
File refFile = new File(testRefFolder, outFileName); File refFile = new File(testRefFolder, outFileName);
String javaPath = new File(testSourceFolder, javaFiles[i]).getAbsolutePath(); String javaPath = new File(testSourceFolder, javaFiles[i]).getAbsolutePath();
String[] options = new String[] { "-docletpath", "build", "-hide", "Hidden", String[] options = new String[] { "--docletpath=build", "--hide=\"Hidden\"",
"-compact", "-private", "-d", testDestFolder, "-output", outFileName, javaPath }; "-compact", "-private", "--d=\"" + testDestFolder + "\"", "--output=\"" + outFileName + "\"", javaPath };
runDoclet(options); runDoclet(options);
compare(differences, dotFile, refFile); compare(differences, dotFile, refFile);
@ -128,8 +139,9 @@ public class BasicTest {
} }
private static void runDoclet(String[] options) { private static void runDoclet(String[] options) {
com.sun.tools.javadoc.Main.execute("UMLGraph test", pw, pw, pw, DocumentationTool systemDocumentationTool = ToolProvider.getSystemDocumentationTool();
"org.umlgraph.doclet.UmlGraph", options); DocumentationTool.DocumentationTask task = systemDocumentationTool.getTask(pw, null, null, UmlGraph.class, Arrays.asList(options), null);
task.call();
} }
private static void compare(List<String> differences, File dotFile, File refFile) private static void compare(List<String> differences, File dotFile, File refFile)

View File

@ -21,6 +21,12 @@ package org.umlgraph.test;
import java.io.File; import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays;
import javax.tools.DocumentationTool;
import javax.tools.ToolProvider;
import org.umlgraph.doclet.UmlGraphDoc;
public class RunDoc { public class RunDoc {
@ -34,14 +40,15 @@ public class RunDoc {
File outFolder = new File(docFolder); File outFolder = new File(docFolder);
if (!outFolder.exists()) if (!outFolder.exists())
outFolder.mkdirs(); outFolder.mkdirs();
String[] options = new String[] { "-docletpath", "build", "-private", "-d", docFolder, String[] options = new String[] { "-docletpath", "build", "-private", "--d", docFolder,
"-sourcepath", sourcesFolder, "-subpackages", "gr.spinellis" }; "-sourcepath", sourcesFolder, "-subpackages", "gr.spinellis" };
runDoclet(options); runDoclet(options);
} }
private static void runDoclet(String[] options) { private static void runDoclet(String[] options) {
com.sun.tools.javadoc.Main.execute("UMLGraph test", pw, pw, pw, DocumentationTool systemDocumentationTool = ToolProvider.getSystemDocumentationTool();
"org.umlgraph.doclet.UmlGraphDoc", options); DocumentationTool.DocumentationTask task = systemDocumentationTool.getTask(pw, null, null, UmlGraphDoc.class, Arrays.asList(options), null);
task.call();
} }
} }

View File

@ -21,6 +21,12 @@ package org.umlgraph.test;
import java.io.File; import java.io.File;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.Arrays;
import javax.tools.DocumentationTool;
import javax.tools.ToolProvider;
import org.umlgraph.doclet.UmlGraph;
public class RunOne { public class RunOne {
@ -53,8 +59,8 @@ public class RunOne {
} }
private static void runDoclet(String[] options) { private static void runDoclet(String[] options) {
com.sun.tools.javadoc.Main.execute("UMLGraph test", pw, pw, pw, "org.umlgraph.doclet.UmlGraph", options); DocumentationTool systemDocumentationTool = ToolProvider.getSystemDocumentationTool();
DocumentationTool.DocumentationTask task = systemDocumentationTool.getTask(pw, null, null, UmlGraph.class, Arrays.asList(options), null);
task.call();
} }
} }

View File

@ -25,6 +25,13 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import javax.tools.DocumentationTool;
import javax.tools.ToolProvider;
import org.junit.Ignore;
import org.junit.Test;
import org.umlgraph.doclet.UmlGraphDoc;
/** /**
* UmlGraphDoc doclet regression tests * UmlGraphDoc doclet regression tests
* @author wolf * @author wolf
@ -41,7 +48,9 @@ public class UmlDocTest {
static PrintWriter pw = new PrintWriter(System.out); static PrintWriter pw = new PrintWriter(System.out);
public static void main(String[] args) throws IOException { @Test
@Ignore
public void test() throws IOException {
List<String> differences = new ArrayList<String>(); List<String> differences = new ArrayList<String>();
File outFolder = new File(testDestFolder); File outFolder = new File(testDestFolder);
@ -68,10 +77,11 @@ public class UmlDocTest {
private static void runTest(List<String> differences) throws IOException { private static void runTest(List<String> differences) throws IOException {
File outFolder = new File(testDestFolder); File outFolder = new File(testDestFolder);
String[] options = new String[] { "-docletpath", "build", "-private", "-d", File srcFolder = new File(testSourceFolder);
outFolder.getAbsolutePath(), "-sourcepath", testSourceFolder, "-compact", String[] options = new String[] { "-private", "--d=\"" + outFolder.getAbsolutePath() + "\"",
"--source-path=\"" + srcFolder.getAbsolutePath() + "\"", "-compact",
"-subpackages", "gr.spinellis", "-inferrel", "-inferdep", "-qualify", "-subpackages", "gr.spinellis", "-inferrel", "-inferdep", "-qualify",
"-postfixpackage", "-collpackages", "java.util.*" }; "-postfixpackage", "--collpackages=\"java.util.*\"" };
runDoclet(options); runDoclet(options);
compareDocletOutputs(differences, new File(testRefFolder), new File(testDestFolder)); compareDocletOutputs(differences, new File(testRefFolder), new File(testDestFolder));
@ -119,8 +129,9 @@ public class UmlDocTest {
differences.add(out.getName() + " is different from the reference"); differences.add(out.getName() + " is different from the reference");
} else { } else {
if (!TestUtils.textFilesEquals(pw, ref, out)) if (!TestUtils.textFilesEquals(pw, ref, out)) {
differences.add(out.getName() + " is different from the reference"); differences.add(out.getName() + " is different from the reference");
}
} }
i++; i++;
j++; j++;
@ -146,13 +157,14 @@ public class UmlDocTest {
* @param options * @param options
*/ */
private static void runDoclet(String[] options) { private static void runDoclet(String[] options) {
pw.print("Run javadoc -doclet " + doclet); pw.print("Run javadoc -doclet " + doclet);
for (String o : options) for (String o : options) {
pw.print(" " + o); pw.print(" " + o);
pw.println(); }
com.sun.tools.javadoc.Main.execute("UMLDoc test", pw, pw, pw, pw.println();
doclet, options); DocumentationTool systemDocumentationTool = ToolProvider.getSystemDocumentationTool();
System.exit(0); DocumentationTool.DocumentationTask task = systemDocumentationTool.getTask(pw, null, null, UmlGraphDoc.class, Arrays.asList(options), null);
task.call();
} }
} }