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>

96
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>
*/ */
@ -36,37 +39,35 @@ class ClassInfo {
boolean nodePrinted; boolean nodePrinted;
/** 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.showQualified = false;
this.myGlobalOptions = parent.getGlobalOptions(); this.packageOptions = parent.getGlobalOptions();
this.myGlobalOptions.setOption(new String[] { "output", outputPath }); this.packageOptions.showQualified = false;
this.myGlobalOptions.setOption(HIDE_OPTIONS);
this.hideOptions = parent.getGlobalOptions(); this.myGlobalOptions = parent.getGlobalOptions();
this.hideOptions.setOption(HIDE_OPTIONS); this.myGlobalOptions.setOption(new String[] { "output", outputPath });
this.myGlobalOptions.setOption(HIDE_OPTIONS);
this.centerOptions = parent.getGlobalOptions(); this.hideOptions = parent.getGlobalOptions();
this.centerOptions.nodeFillColor = "lemonChiffon"; this.hideOptions.setOption(HIDE_OPTIONS);
this.centerOptions.showQualified = false;
this.matcher = new ContextMatcher(root, Pattern.compile(Pattern.quote(cd.toString())), this.centerOptions = parent.getGlobalOptions();
myGlobalOptions, true); this.centerOptions.nodeFillColor = "lemonChiffon";
this.centerOptions.showQualified = false;
this.matcher = new ContextMatcher(root, Pattern.compile(Pattern.quote(cd.getQualifiedName().toString())), myGlobalOptions, true);
} }
public void setContextCenter(ClassDoc contextCenter) { public void setContextCenter(TypeElement contextCenter) {
this.cd = contextCenter; this.cd = contextCenter;
String outputPath = cd.containingPackage().name().replace('.', '/') + "/" + cd.name() 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
// are treated the same in the doclet API // for each interface, recurse, since classes and interfaces
for (ClassDoc iface : cd.interfaces()) // are treated the same in the doclet API
if(matches(iface)) for (TypeMirror type : cd.getInterfaces()) {
return true; TypeElement iType = ElementUtil.getTypeElement(type);
if (iType != null && matches(iType)) {
// recurse on supeclass, if available return true;
return cd.superclass() == null ? false : matches(cd.superclass()); }
}
// recurse on superclass, if available
TypeElement scd = ElementUtil.getTypeElement(cd.getSuperclass());
return scd == null ? false : matches(scd);
} }
public boolean matches(String name) { 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,28 +202,29 @@ 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);
}
} }
/** /**
* Format a double to a string. * Format a double to a string.
* <p> * <p>
@ -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
return cd.superclass() == null ? false : matches(cd.superclass()); // recurse on superclass, if available
TypeElement scd = ElementUtil.getTypeElement(cd.getSuperclass());
return scd == null ? false : matches(scd);
} }
public boolean matches(String name) { 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 = "<div align=\"center\">"
private static final String UML_AUTO_SIZED_DIV_TAG = + "<object type=\"image/svg+xml\" data=\"%1$s.svg\" alt=\"Package class diagram package %1$s\" border=0></object>"
"<div align=\"center\">" + + "</div>";
"<object type=\"image/svg+xml\" data=\"%1$s.svg\" alt=\"Package class diagram package %1$s\" border=0></object>" +
"</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
* inserts the diagram image reference and a client side map in that point.
*/
private static void alterHtmlDocs(Options opt, String outputFolder, String packageName, String className,
String htmlFileName, Pattern insertPointPattern, RootDoc root) throws IOException {
// setup files
File output = new File(outputFolder, packageName.replace(".", "/"));
File htmlFile = new File(output, htmlFileName);
File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
if (!htmlFile.exists()) {
System.err.println("Expected file not found: " + htmlFile.getAbsolutePath());
return;
}
// parse & rewrite
BufferedWriter writer = null;
BufferedReader reader = null;
boolean matched = false;
try {
writer = new BufferedWriter(new OutputStreamWriter(new
FileOutputStream(alteredFile), opt.outputEncoding));
reader = new BufferedReader(new InputStreamReader(new
FileInputStream(htmlFile), opt.outputEncoding));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
if (!matched && insertPointPattern.matcher(line).matches()) {
matched = true;
String tag;
if (opt.autoSize)
tag = String.format(UML_AUTO_SIZED_DIV_TAG, className);
else
tag = String.format(UML_DIV_TAG, className);
if (opt.collapsibleDiagrams)
tag = String.format(EXPANDABLE_UML, tag, "Show UML class diagram", "Hide UML class diagram");
writer.write("<!-- UML diagram added by UMLGraph version " +
Version.VERSION +
" (http://www.spinellis.gr/umlgraph/) -->");
writer.newLine();
writer.write(tag);
writer.newLine();
}
}
} finally {
if (writer != null)
writer.close();
if (reader != null)
reader.close();
}
// if altered, delete old file and rename new one to the old file name
if (matched) {
htmlFile.delete();
alteredFile.renameTo(htmlFile);
} else {
root.printNotice("Warning, could not find a line that matches the pattern '" + insertPointPattern.pattern()
+ "'.\n Class diagram reference not inserted");
alteredFile.delete();
}
}
/** /**
* Returns the output path specified on the javadoc options * Takes an HTML file, looks for the first instance of the specified insertion
* point, and inserts the diagram image reference and a client side map in that
* point.
*/ */
private static String findOutputPath(String[][] options) { private static void alterHtmlDocs(Options opt, String outputFolder, ModuleElement module, Name packageName, Name className,
for (int i = 0; i < options.length; i++) { String htmlFileName, Pattern insertPointPattern, Reporter reporter) throws IOException {
if (options[i][0].equals("-d")) // setup files
return options[i][1]; String prefix = Runtime.version().major() > 10 && module != null ? module.getQualifiedName().toString() + '/' : "";
} File output = new File(outputFolder, prefix + packageName.toString().replace(".", "/"));
return "."; File htmlFile = new File(output, htmlFileName);
File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
if (!htmlFile.exists()) {
System.err.println("Expected file not found: " + htmlFile.getAbsolutePath());
return;
}
// parse & rewrite
BufferedWriter writer = null;
BufferedReader reader = null;
boolean matched = false;
try {
writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(alteredFile), opt.outputEncoding));
reader = new BufferedReader(new InputStreamReader(new FileInputStream(htmlFile), opt.outputEncoding));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
if (!matched && insertPointPattern.matcher(line).matches()) {
matched = true;
String tag;
if (opt.autoSize) {
tag = String.format(UML_AUTO_SIZED_DIV_TAG, className);
} else {
tag = String.format(UML_DIV_TAG, className);
}
if (opt.collapsibleDiagrams) {
tag = String.format(EXPANDABLE_UML, tag, "Show UML class diagram", "Hide UML class diagram");
}
writer.write("<!-- UML diagram added by UMLGraph version " + Version.VERSION + " (http://www.spinellis.gr/umlgraph/) -->");
writer.newLine();
writer.write(tag);
writer.newLine();
}
}
} finally {
if (writer != null) {
writer.close();
}
if (reader != null) {
reader.close();
}
}
// if altered, delete old file and rename new one to the old file name
if (matched) {
htmlFile.delete();
alteredFile.renameTo(htmlFile);
} else {
reporter.print(Diagnostic.Kind.NOTE, "Warning, could not find a line that matches the pattern '"
+ insertPointPattern.pattern() + "'.\n Class diagram reference not inserted");
alteredFile.delete();
}
} }
} }

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 {
if (strings[0].equals("class")) {
return new PatternMatcher(Pattern.compile(strings[1]));
} else if (strings[0].equals("context")) {
return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(),
false);
} else if (strings[0].equals("outgoingContext")) {
return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(),
false);
} else if (strings[0].equals("interface")) {
return new InterfaceMatcher(root, Pattern.compile(strings[1]));
} else if (strings[0].equals("subclass")) {
return new SubclassMatcher(root, Pattern.compile(strings[1]));
} 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 (Exception e) {
System.err.println("Skipping @match tag due to an internal error '" + tagText
+ "'" + " in view " + viewDoc);
e.printStackTrace();
}
return null;
}
// ---------------------------------------------------------------- try {
if (strings[0].equals("class")) {
return new PatternMatcher(Pattern.compile(strings[1]));
} else if (strings[0].equals("context")) {
return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(), false);
} else if (strings[0].equals("outgoingContext")) {
return new ContextMatcher(root, Pattern.compile(strings[1]), getGlobalOptions(), false);
} else if (strings[0].equals("interface")) {
return new InterfaceMatcher(root, Pattern.compile(strings[1]));
} else if (strings[0].equals("subclass")) {
return new SubclassMatcher(root, Pattern.compile(strings[1]));
} 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 (Exception e) {
System.err.println("Skipping @match tag due to an internal error '" + tagText + "'" + " 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();
} }
} }