First stab at UmlDoc implementation. It's an "improved" javadoc that outputs docs along with class diagrams.

Diomidis, can you have a look at the output (UmlGraph javadoc are built with UmlDoc now) and review the code?
I know I still have to write the docs, and I will have to try out UmlDoc on something other than UmlGraph itself (was thinking of the java sources themselves, or Hibernate, Spring, ... whatever is big enough).
This commit is contained in:
Andrea Aime 2006-05-01 19:38:12 +00:00
parent 23fbf7ff19
commit 72f918b68c
23 changed files with 1013 additions and 53 deletions

View File

@ -67,7 +67,11 @@ class Version { public static String VERSION = "${VERSION}";}
</target>
<target name="javadocs" depends="compile">
<javadoc sourcepath="${src}" packagenames="gr.spinellis.umlgraph.doclet.*" destdir="${javadoc}" package="true"/>
<javadoc sourcepath="${src}" packagenames="gr.spinellis.umlgraph.doclet.*" destdir="${javadoc}" package="true">
<doclet name="gr.spinellis.umlgraph.doclet.UmlDoc" path="${lib}/UMLGraph.jar">
</doclet>
</javadoc>
<property name="outFolder" location="${javadoc}/gr/spinellis/umlgraph/doclet"/>
<property name="dotName" value="umlgraph.dot"/>
<property name="dotFile" location="${outFolder}/${dotName}"/>

View File

@ -56,6 +56,7 @@ import com.sun.javadoc.WildcardType;
/**
* Class graph generation engine
* @depend - - - StringUtil
* @depend - - - Options
* @composed - - * ClassInfo
* @has - - - OptionProvider
*
@ -89,16 +90,26 @@ class ClassGraph {
protected ClassDoc mapClassDoc;
protected String linePostfix;
protected String linePrefix;
// used only when generating context class diagrams in UMLDoc, to generate the proper
// relative links to other classes in the image map
protected Doc contextDoc;
/**
* Create a new ClassGraph. The packages passed as an
* argument are the ones specified on the command line.
* Local URLs will be generated for these packages.
* Create a new ClassGraph. <p>The packages passed as an
* argument are the ones specified on the command line.</p>
* <p>Local URLs will be generated for these packages.</p>
* @param root The root of docs as provided by the javadoc API
* @param optionProvider The main option provider
* @param contextDoc The current context for generating relative links, may be a ClassDoc
* or a PackageDoc (used by UMLDoc)
*/
public ClassGraph(RootDoc root, OptionProvider optionProvider) throws IOException {
public ClassGraph(RootDoc root, OptionProvider optionProvider, Doc contextDoc) throws IOException {
this.optionProvider = optionProvider;
this.collectionClassDoc = root.classNamed("java.util.Collection");
this.mapClassDoc = root.classNamed("java.util.Map");
this.contextDoc = contextDoc;
specifiedPackages = new HashSet<String>();
for (PackageDoc p : root.specifiedPackages())
@ -447,7 +458,7 @@ class ClassGraph {
w.println("\t// " + r);
// Create label
w.print("\t" + ci.name + " [label=");
externalTableStart(opt, c.qualifiedName());
externalTableStart(opt, c.qualifiedName(), classToUrl(c));
innerTableStart();
if (c.isInterface())
tableLine(Align.CENTER, guilWrap(opt, "interface"));
@ -656,7 +667,7 @@ class ClassGraph {
continue;
w.println("\t// " + className);
w.print("\t" + info.name + "[label=");
externalTableStart(opt, className);
externalTableStart(opt, className, classToUrl(className));
innerTableStart();
int idx = className.lastIndexOf(".");
if(opt.postfixPackage && idx > 0 && idx < (className.length() - 1)) {
@ -847,24 +858,76 @@ class ClassGraph {
return name.substring(0, openIdx);
}
/** Return true if the class name has been specified in the command line */
/**
* Return true if the class name has been specified in the command line.
* Use this only if the ClassDoc is not available, since this implementation won't handle
* properly inner classes (confuses the containing class as a package)
*/
public boolean isSpecifiedPackage(String className) {
int idx = className.lastIndexOf(".");
String packageName = idx > 0 ? className.substring(0, idx) : className;
return specifiedPackages.contains(packageName);
}
/** Return true if the class name has been specified in the command line */
public boolean isSpecifiedPackage(ClassDoc cd) {
return specifiedPackages.contains(cd.containingPackage().name());
}
/** Convert the class name into a corresponding URL */
public String classToUrl(ClassDoc cd) {
// building relative path for context and package diagrams
if(contextDoc != null && isSpecifiedPackage(cd)) {
// determine the context path, relative to the root
String[] contextClassPath = null;
if(contextDoc instanceof ClassDoc) {
contextClassPath = ((ClassDoc) contextDoc).containingPackage().name().split("\\.");
} else if(contextDoc instanceof PackageDoc) {
contextClassPath = ((PackageDoc) contextDoc).name().split("\\.");
} else {
return classToUrl(cd.qualifiedName());
}
// path, relative to the root, of the destination class
String[] currClassPath = cd.containingPackage().name().split("\\.");
// compute relative path between the context and the destination
// ... first, compute common part
int i = 0;
while(i < contextClassPath.length && i < currClassPath.length
&& contextClassPath[i].equals(currClassPath[i]))
i++;
// ... go up with ".." to reach the common root
StringBuffer buf = new StringBuffer();
if(i == contextClassPath.length) {
buf.append(".").append(FILE_SEPARATOR);
} else {
for (int j = i; j < currClassPath.length; j++) {
buf.append("..").append(FILE_SEPARATOR);
}
}
// ... go down from the common root to the destination
for (int j = i; j < currClassPath.length; j++) {
buf.append(currClassPath[j]).append(FILE_SEPARATOR);
}
buf.append(cd.name()).append(".html");
return buf.toString();
} else {
return classToUrl(cd.qualifiedName());
}
}
/** Convert the class name into a corresponding URL */
public String classToUrl(String className) {
String result = null;
String docRoot = mapApiDocRoot(className);
if (docRoot != null) {
StringBuffer buf = new StringBuffer(docRoot);
buf.append(className.replace('.', FILE_SEPARATOR));
buf.append(".html");
result = buf.toString();
}
return result;
String docRoot = mapApiDocRoot(className);
if (docRoot != null) {
StringBuffer buf = new StringBuffer(docRoot);
buf.append(className.replace('.', FILE_SEPARATOR));
buf.append(".html");
return buf.toString();
} else {
return null;
}
}
/**
@ -944,12 +1007,11 @@ class ClassGraph {
w.close();
}
private void externalTableStart(Options opt, String name) {
private void externalTableStart(Options opt, String name, String url) {
String bgcolor = "";
if (opt.nodeFillColor != null)
bgcolor = " bgcolor=\""+ opt.nodeFillColor + "\"";
String href = "";
String url = classToUrl(name);
if (url != null)
href = " href=\"" + url + "\"";
w.print("<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\" "

View File

@ -128,7 +128,7 @@ public class ContextMatcher implements ClassMatcher {
private static class ClassGraphHack extends ClassGraph {
public ClassGraphHack(RootDoc root, OptionProvider optionProvider) throws IOException {
super(root, optionProvider);
super(root, optionProvider, null);
prologue();
}

View File

@ -0,0 +1,74 @@
package gr.spinellis.umlgraph.doclet;
import java.io.IOException;
import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.RootDoc;
/**
* A view designed for UMLDoc, filters out everything that it's not directly
* connected to the center class of the context.
* <p>
* As such, can be viewed as a simplified version of a {@linkplain View} using a
* single {@linkplain ContextMatcher}, but provides some extra configuration
* such as context highlighting and output path configuration (and it is
* specified in code rather than in javadoc comments).
* @author wolf
*
*/
public class ContextView implements OptionProvider {
private ClassDoc cd;
private OptionProvider parent;
private ContextMatcher matcher;
private String outputPath;
public ContextView(String outputFolder, ClassDoc cd, RootDoc root, OptionProvider parent)
throws IOException {
this.parent = parent;
this.cd = cd;
this.matcher = new ContextMatcher(root, Pattern.compile(cd.qualifiedName()),
getGlobalOptions());
this.outputPath = cd.containingPackage().name().replace('.', '/') + "/" + cd.name()
+ ".dot";
}
public String getDisplayName() {
return "Context view for class " + cd;
}
public Options getGlobalOptions() {
Options go = parent.getGlobalOptions();
go.setOption(new String[] { "-output", outputPath });
go.setOption(new String[] { "-hide" });
return go;
}
public Options getOptionsFor(ClassDoc cd) {
Options go = parent.getGlobalOptions();
overrideForClass(go, cd);
return go;
}
public Options getOptionsFor(String name) {
Options go = parent.getGlobalOptions();
overrideForClass(go, name);
return go;
}
public void overrideForClass(Options opt, ClassDoc cd) {
if (parent.getGlobalOptions().matchesHideExpression(cd.name()) || !matcher.matches(cd))
opt.setOption(new String[] { "-hide" });
if (cd.equals(this.cd))
opt.nodeFillColor = "lemonChiffon";
}
public void overrideForClass(Options opt, String className) {
if (!matcher.matches(className))
opt.setOption(new String[] { "-hide" });
}
}

View File

@ -97,6 +97,8 @@ class Options implements Cloneable, OptionProvider {
String inferRelationshipType;
private Vector<Pattern> collPackages;
public boolean compact;
// internal option, used by UMLDoc to generate relative links between classes
boolean relativeLinksForSourcePackages;
Options() {
showQualified = false;
@ -141,6 +143,7 @@ class Options implements Cloneable, OptionProvider {
inferRelationshipType = "navassoc";
collPackages = new Vector<Pattern>();
compact = false;
relativeLinksForSourcePackages = false;
}
public Object clone() {

View File

@ -0,0 +1,26 @@
package gr.spinellis.umlgraph.doclet;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.PackageDoc;
public class PackageMatcher implements ClassMatcher {
protected PackageDoc packageDoc;
public PackageMatcher(PackageDoc packageDoc) {
super();
this.packageDoc = packageDoc;
}
public boolean matches(ClassDoc cd) {
return cd.containingPackage().equals(packageDoc);
}
public boolean matches(String name) {
for (ClassDoc cd : packageDoc.allClasses()) {
if (cd.qualifiedName().equals(name))
return true;
}
return false;
}
}

View File

@ -0,0 +1,67 @@
package gr.spinellis.umlgraph.doclet;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.RootDoc;
/**
* A view designed for UMLDoc, filters out everything that it's not contained in
* the specified package.
* <p>
* As such, can be viewed as a simplified version of a {@linkplain View} using a
* single {@linkplain ClassMatcher}, and provides some extra configuration such
* as output path configuration (and it is specified in code rather than in
* javadoc comments).
* @author wolf
*
*/
public class PackageView implements OptionProvider {
private PackageDoc pd;
private OptionProvider parent;
private ClassMatcher matcher;
private String outputPath;
public PackageView(String outputFolder, PackageDoc pd, RootDoc root, OptionProvider parent) {
this.parent = parent;
this.pd = pd;
this.matcher = new PackageMatcher(pd);
this.outputPath = pd.name().replace('.', '/') + "/" + pd.name() + ".dot";
}
public String getDisplayName() {
return "Package view for packages" + pd;
}
public Options getGlobalOptions() {
Options go = parent.getGlobalOptions();
go.setOption(new String[] { "-output", outputPath });
go.setOption(new String[] { "-hide" });
return go;
}
public Options getOptionsFor(ClassDoc cd) {
Options go = parent.getGlobalOptions();
overrideForClass(go, cd);
return go;
}
public Options getOptionsFor(String name) {
Options go = parent.getGlobalOptions();
overrideForClass(go, name);
return go;
}
public void overrideForClass(Options opt, ClassDoc cd) {
if (!matcher.matches(cd) || parent.getGlobalOptions().matchesHideExpression(cd.name()))
opt.setOption(new String[] { "-hide" });
}
public void overrideForClass(Options opt, String className) {
if (!matcher.matches(className))
opt.setOption(new String[] { "-hide" });
}
}

View File

@ -0,0 +1,196 @@
package gr.spinellis.umlgraph.doclet;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.RootDoc;
import com.sun.tools.doclets.standard.Standard;
/**
* Chaining doclet that runs the standart Javadoc doclet first, and on success,
* runs the generation of dot files by UMLGraph
* @author wolf
*
*/
public class UmlDoc {
public static int optionLength(String option) {
int result = Standard.optionLength(option);
if (result != 0)
return result;
else
return UmlGraph.optionLength(option);
}
public static boolean start(RootDoc root) throws IOException {
if (Standard.start(root)) {
String outputFolder = findOutputPath(root.options());
Options opt = new Options();
opt.setOptions(root.options());
opt.relativeLinksForSourcePackages = true;
generatePackageDiagrams(root, opt, outputFolder);
generateContextDiagrams(root, opt, outputFolder);
return true;
} else {
return false;
}
}
public static LanguageVersion languageVersion() {
return Standard.languageVersion();
}
private static void generatePackageDiagrams(RootDoc root, Options opt, String outputFolder)
throws IOException {
if (opt.verbose2)
System.out.println("Examining packages");
for (PackageDoc packageDoc : root.specifiedPackages()) {
if (opt.verbose2)
System.out.println("Altering javadocs for pacakge " + packageDoc);
OptionProvider view = new PackageView(outputFolder, packageDoc, root, opt);
UmlGraph.buildGraph(root, view, packageDoc);
runGraphviz(outputFolder, packageDoc.name(), packageDoc.name());
alterHtmlDocs(outputFolder, packageDoc.name(), packageDoc.name(),
"package-summary.html", "</H2>");
}
}
private static void generateContextDiagrams(RootDoc root, Options opt, String outputFolder)
throws IOException {
if (opt.verbose2)
System.out.println("Examining classes");
for (ClassDoc classDoc : root.classes()) {
if (!isIncluded(classDoc, root.specifiedPackages()))
continue;
if (opt.verbose2)
System.out.println("Altering class doc for " + classDoc);
ContextView view = new ContextView(outputFolder, classDoc, root, opt);
UmlGraph.buildGraph(root, view, classDoc);
runGraphviz(outputFolder, classDoc.containingPackage().name(), classDoc.name());
alterHtmlDocs(outputFolder, classDoc.containingPackage().name(), classDoc.name(),
classDoc.name() + ".html", classDoc.name() + "</H2>");
}
}
private static void runGraphviz(String outputFolder, String packageName, String name) {
File dotFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".dot");
File pngFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".png");
File mapFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".map");
try {
String command = "dot -Tcmapx -o" + mapFile.getAbsolutePath() + " -Tpng -o"
+ pngFile.getAbsolutePath() + " " + dotFile.getAbsolutePath();
Process p = Runtime.getRuntime().exec(command);
int result = p.waitFor();
if (result != 0)
System.out.println("Errors running Graphviz on " + dotFile);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void alterHtmlDocs(String outputFolder, String packageName, String name,
String htmlFileName, String matchLineEnd) throws IOException {
// setup files
File output = new File(outputFolder, packageName.replace(".", "/"));
File htmlFile = new File(output, htmlFileName);
File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
File mapFile = new File(output, name + ".map");
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 FileWriter(alteredFile));
reader = new BufferedReader(new FileReader(htmlFile));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
if (!matched && line.endsWith(matchLineEnd)) {
matched = true;
if (mapFile.exists())
insertClientSideMap(mapFile, writer);
else
System.out.println("Could not find map file " + mapFile);
writer.write("<div align=\"center\"><img src=\"" + name
+ ".png\" alt=\"Package class diagram package " + name
+ "\" usemap=\"#G\" border=0/></a></div>");
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 {
System.out.println("Error, could not find a line that ends with '" + matchLineEnd
+ "'.\n Class diagram reference not inserted");
alteredFile.delete();
}
}
/**
* Reads the map file and outputs in to the specified writer
* @throws IOException
*/
private static void insertClientSideMap(File mapFile, BufferedWriter writer) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(mapFile));
String line = null;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} finally {
if (reader != null)
reader.close();
}
}
/**
* Return true if the class name has been specified in the command line
*/
private static boolean isIncluded(ClassDoc cd, PackageDoc[] docs) {
for (PackageDoc pd : docs) {
for (ClassDoc pcd : pd.allClasses())
if (pcd.equals(cd))
return true;
}
return false;
}
/**
* Returns the output path specified on the javadoc options
*/
private static String findOutputPath(String[][] options) {
for (int i = 0; i < options.length; i++) {
if (options[i][0].equals("-d"))
return options[i][1];
}
return ".";
}
}

View File

@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.List;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.RootDoc;
@ -52,10 +53,10 @@ public class UmlGraph {
if(views == null)
return false;
if (views.length == 0) {
buildGraph(root, opt);
buildGraph(root, opt, null);
} else {
for (int i = 0; i < views.length; i++) {
buildGraph(root, views[i]);
buildGraph(root, views[i], null);
}
}
@ -76,13 +77,13 @@ public class UmlGraph {
/**
* Builds and outputs a single graph according to the view overrides
*/
private static void buildGraph(RootDoc root, OptionProvider op) throws IOException {
public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException {
Options opt = op.getGlobalOptions();
if (opt.verbose2)
System.out.println("Building " + op.getDisplayName());
ClassDoc[] classes = root.classes();
ClassGraph c = new ClassGraph(root, op);
ClassGraph c = new ClassGraph(root, op, contextDoc);
c.prologue();
for (int i = 0; i < classes.length; i++) {
c.printClass(classes[i]);

View File

@ -40,6 +40,7 @@ import com.sun.javadoc.Tag;
* @depend - - - InterfaceMatcher
* @depend - - - PatternMatcher
* @depend - - - SubclassMatcher
* @depend - - - ContextMatcher
*
*/
class View implements OptionProvider {

View File

@ -0,0 +1,48 @@
/*
* UmlGraph class diagram testing framework
*
* Contibuted by Andrea Aime
* (C) Copyright 2005 Diomidis Spinellis
*
* Permission to use, copy, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id$
*
*/
package gr.spinellis.umlgraph.test;
import java.io.File;
import java.io.PrintWriter;
public class RunDoc {
static String sourcesFolder = "src";
static String docFolder = "javadoc";
static PrintWriter pw = new PrintWriter(System.out);
public static void main(String[] args) {
File outFolder = new File(docFolder);
if (!outFolder.exists())
outFolder.mkdirs();
String[] options = new String[] { "-docletpath", "build", "-private", "-d", docFolder,
"-sourcepath", sourcesFolder, "-subpackages", "gr.spinellis" };
runDoclet(options);
}
private static void runDoclet(String[] options) {
com.sun.tools.javadoc.Main.execute("UMLGraph test", pw, pw, pw,
"gr.spinellis.umlgraph.doclet.UmlDoc", options);
}
}

View File

@ -36,8 +36,8 @@ public class RunOne {
if (!outFolder.exists())
outFolder.mkdirs();
runView("gr.spinellis.views.ViewChildEmpty");
runSingleClass("foo");
// runView("gr.spinellis.views.ViewChildEmpty");
runSingleClass("TestHiddenOp");
}
public static void runView(String viewClass) {

View File

@ -56,6 +56,7 @@ import com.sun.javadoc.WildcardType;
/**
* Class graph generation engine
* @depend - - - StringUtil
* @depend - - - Options
* @composed - - * ClassInfo
* @has - - - OptionProvider
*
@ -89,16 +90,26 @@ class ClassGraph {
protected ClassDoc mapClassDoc;
protected String linePostfix;
protected String linePrefix;
// used only when generating context class diagrams in UMLDoc, to generate the proper
// relative links to other classes in the image map
protected Doc contextDoc;
/**
* Create a new ClassGraph. The packages passed as an
* argument are the ones specified on the command line.
* Local URLs will be generated for these packages.
* Create a new ClassGraph. <p>The packages passed as an
* argument are the ones specified on the command line.</p>
* <p>Local URLs will be generated for these packages.</p>
* @param root The root of docs as provided by the javadoc API
* @param optionProvider The main option provider
* @param contextDoc The current context for generating relative links, may be a ClassDoc
* or a PackageDoc (used by UMLDoc)
*/
public ClassGraph(RootDoc root, OptionProvider optionProvider) throws IOException {
public ClassGraph(RootDoc root, OptionProvider optionProvider, Doc contextDoc) throws IOException {
this.optionProvider = optionProvider;
this.collectionClassDoc = root.classNamed("java.util.Collection");
this.mapClassDoc = root.classNamed("java.util.Map");
this.contextDoc = contextDoc;
specifiedPackages = new HashSet<String>();
for (PackageDoc p : root.specifiedPackages())
@ -447,7 +458,7 @@ class ClassGraph {
w.println("\t// " + r);
// Create label
w.print("\t" + ci.name + " [label=");
externalTableStart(opt, c.qualifiedName());
externalTableStart(opt, c.qualifiedName(), classToUrl(c));
innerTableStart();
if (c.isInterface())
tableLine(Align.CENTER, guilWrap(opt, "interface"));
@ -656,7 +667,7 @@ class ClassGraph {
continue;
w.println("\t// " + className);
w.print("\t" + info.name + "[label=");
externalTableStart(opt, className);
externalTableStart(opt, className, classToUrl(className));
innerTableStart();
int idx = className.lastIndexOf(".");
if(opt.postfixPackage && idx > 0 && idx < (className.length() - 1)) {
@ -847,24 +858,76 @@ class ClassGraph {
return name.substring(0, openIdx);
}
/** Return true if the class name has been specified in the command line */
/**
* Return true if the class name has been specified in the command line.
* Use this only if the ClassDoc is not available, since this implementation won't handle
* properly inner classes (confuses the containing class as a package)
*/
public boolean isSpecifiedPackage(String className) {
int idx = className.lastIndexOf(".");
String packageName = idx > 0 ? className.substring(0, idx) : className;
return specifiedPackages.contains(packageName);
}
/** Return true if the class name has been specified in the command line */
public boolean isSpecifiedPackage(ClassDoc cd) {
return specifiedPackages.contains(cd.containingPackage().name());
}
/** Convert the class name into a corresponding URL */
public String classToUrl(ClassDoc cd) {
// building relative path for context and package diagrams
if(contextDoc != null && isSpecifiedPackage(cd)) {
// determine the context path, relative to the root
String[] contextClassPath = null;
if(contextDoc instanceof ClassDoc) {
contextClassPath = ((ClassDoc) contextDoc).containingPackage().name().split("\\.");
} else if(contextDoc instanceof PackageDoc) {
contextClassPath = ((PackageDoc) contextDoc).name().split("\\.");
} else {
return classToUrl(cd.qualifiedName());
}
// path, relative to the root, of the destination class
String[] currClassPath = cd.containingPackage().name().split("\\.");
// compute relative path between the context and the destination
// ... first, compute common part
int i = 0;
while(i < contextClassPath.length && i < currClassPath.length
&& contextClassPath[i].equals(currClassPath[i]))
i++;
// ... go up with ".." to reach the common root
StringBuffer buf = new StringBuffer();
if(i == contextClassPath.length) {
buf.append(".").append(FILE_SEPARATOR);
} else {
for (int j = i; j < currClassPath.length; j++) {
buf.append("..").append(FILE_SEPARATOR);
}
}
// ... go down from the common root to the destination
for (int j = i; j < currClassPath.length; j++) {
buf.append(currClassPath[j]).append(FILE_SEPARATOR);
}
buf.append(cd.name()).append(".html");
return buf.toString();
} else {
return classToUrl(cd.qualifiedName());
}
}
/** Convert the class name into a corresponding URL */
public String classToUrl(String className) {
String result = null;
String docRoot = mapApiDocRoot(className);
if (docRoot != null) {
StringBuffer buf = new StringBuffer(docRoot);
buf.append(className.replace('.', FILE_SEPARATOR));
buf.append(".html");
result = buf.toString();
}
return result;
String docRoot = mapApiDocRoot(className);
if (docRoot != null) {
StringBuffer buf = new StringBuffer(docRoot);
buf.append(className.replace('.', FILE_SEPARATOR));
buf.append(".html");
return buf.toString();
} else {
return null;
}
}
/**
@ -944,12 +1007,11 @@ class ClassGraph {
w.close();
}
private void externalTableStart(Options opt, String name) {
private void externalTableStart(Options opt, String name, String url) {
String bgcolor = "";
if (opt.nodeFillColor != null)
bgcolor = " bgcolor=\""+ opt.nodeFillColor + "\"";
String href = "";
String url = classToUrl(name);
if (url != null)
href = " href=\"" + url + "\"";
w.print("<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\" "

View File

@ -128,7 +128,7 @@ public class ContextMatcher implements ClassMatcher {
private static class ClassGraphHack extends ClassGraph {
public ClassGraphHack(RootDoc root, OptionProvider optionProvider) throws IOException {
super(root, optionProvider);
super(root, optionProvider, null);
prologue();
}

View File

@ -0,0 +1,74 @@
package gr.spinellis.umlgraph.doclet;
import java.io.IOException;
import java.util.regex.Pattern;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.RootDoc;
/**
* A view designed for UMLDoc, filters out everything that it's not directly
* connected to the center class of the context.
* <p>
* As such, can be viewed as a simplified version of a {@linkplain View} using a
* single {@linkplain ContextMatcher}, but provides some extra configuration
* such as context highlighting and output path configuration (and it is
* specified in code rather than in javadoc comments).
* @author wolf
*
*/
public class ContextView implements OptionProvider {
private ClassDoc cd;
private OptionProvider parent;
private ContextMatcher matcher;
private String outputPath;
public ContextView(String outputFolder, ClassDoc cd, RootDoc root, OptionProvider parent)
throws IOException {
this.parent = parent;
this.cd = cd;
this.matcher = new ContextMatcher(root, Pattern.compile(cd.qualifiedName()),
getGlobalOptions());
this.outputPath = cd.containingPackage().name().replace('.', '/') + "/" + cd.name()
+ ".dot";
}
public String getDisplayName() {
return "Context view for class " + cd;
}
public Options getGlobalOptions() {
Options go = parent.getGlobalOptions();
go.setOption(new String[] { "-output", outputPath });
go.setOption(new String[] { "-hide" });
return go;
}
public Options getOptionsFor(ClassDoc cd) {
Options go = parent.getGlobalOptions();
overrideForClass(go, cd);
return go;
}
public Options getOptionsFor(String name) {
Options go = parent.getGlobalOptions();
overrideForClass(go, name);
return go;
}
public void overrideForClass(Options opt, ClassDoc cd) {
if (parent.getGlobalOptions().matchesHideExpression(cd.name()) || !matcher.matches(cd))
opt.setOption(new String[] { "-hide" });
if (cd.equals(this.cd))
opt.nodeFillColor = "lemonChiffon";
}
public void overrideForClass(Options opt, String className) {
if (!matcher.matches(className))
opt.setOption(new String[] { "-hide" });
}
}

View File

@ -97,6 +97,8 @@ class Options implements Cloneable, OptionProvider {
String inferRelationshipType;
private Vector<Pattern> collPackages;
public boolean compact;
// internal option, used by UMLDoc to generate relative links between classes
boolean relativeLinksForSourcePackages;
Options() {
showQualified = false;
@ -141,6 +143,7 @@ class Options implements Cloneable, OptionProvider {
inferRelationshipType = "navassoc";
collPackages = new Vector<Pattern>();
compact = false;
relativeLinksForSourcePackages = false;
}
public Object clone() {

View File

@ -0,0 +1,26 @@
package gr.spinellis.umlgraph.doclet;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.PackageDoc;
public class PackageMatcher implements ClassMatcher {
protected PackageDoc packageDoc;
public PackageMatcher(PackageDoc packageDoc) {
super();
this.packageDoc = packageDoc;
}
public boolean matches(ClassDoc cd) {
return cd.containingPackage().equals(packageDoc);
}
public boolean matches(String name) {
for (ClassDoc cd : packageDoc.allClasses()) {
if (cd.qualifiedName().equals(name))
return true;
}
return false;
}
}

View File

@ -0,0 +1,67 @@
package gr.spinellis.umlgraph.doclet;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.RootDoc;
/**
* A view designed for UMLDoc, filters out everything that it's not contained in
* the specified package.
* <p>
* As such, can be viewed as a simplified version of a {@linkplain View} using a
* single {@linkplain ClassMatcher}, and provides some extra configuration such
* as output path configuration (and it is specified in code rather than in
* javadoc comments).
* @author wolf
*
*/
public class PackageView implements OptionProvider {
private PackageDoc pd;
private OptionProvider parent;
private ClassMatcher matcher;
private String outputPath;
public PackageView(String outputFolder, PackageDoc pd, RootDoc root, OptionProvider parent) {
this.parent = parent;
this.pd = pd;
this.matcher = new PackageMatcher(pd);
this.outputPath = pd.name().replace('.', '/') + "/" + pd.name() + ".dot";
}
public String getDisplayName() {
return "Package view for packages" + pd;
}
public Options getGlobalOptions() {
Options go = parent.getGlobalOptions();
go.setOption(new String[] { "-output", outputPath });
go.setOption(new String[] { "-hide" });
return go;
}
public Options getOptionsFor(ClassDoc cd) {
Options go = parent.getGlobalOptions();
overrideForClass(go, cd);
return go;
}
public Options getOptionsFor(String name) {
Options go = parent.getGlobalOptions();
overrideForClass(go, name);
return go;
}
public void overrideForClass(Options opt, ClassDoc cd) {
if (!matcher.matches(cd) || parent.getGlobalOptions().matchesHideExpression(cd.name()))
opt.setOption(new String[] { "-hide" });
}
public void overrideForClass(Options opt, String className) {
if (!matcher.matches(className))
opt.setOption(new String[] { "-hide" });
}
}

View File

@ -0,0 +1,196 @@
package gr.spinellis.umlgraph.doclet;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.PackageDoc;
import com.sun.javadoc.RootDoc;
import com.sun.tools.doclets.standard.Standard;
/**
* Chaining doclet that runs the standart Javadoc doclet first, and on success,
* runs the generation of dot files by UMLGraph
* @author wolf
*
*/
public class UmlDoc {
public static int optionLength(String option) {
int result = Standard.optionLength(option);
if (result != 0)
return result;
else
return UmlGraph.optionLength(option);
}
public static boolean start(RootDoc root) throws IOException {
if (Standard.start(root)) {
String outputFolder = findOutputPath(root.options());
Options opt = new Options();
opt.setOptions(root.options());
opt.relativeLinksForSourcePackages = true;
generatePackageDiagrams(root, opt, outputFolder);
generateContextDiagrams(root, opt, outputFolder);
return true;
} else {
return false;
}
}
public static LanguageVersion languageVersion() {
return Standard.languageVersion();
}
private static void generatePackageDiagrams(RootDoc root, Options opt, String outputFolder)
throws IOException {
if (opt.verbose2)
System.out.println("Examining packages");
for (PackageDoc packageDoc : root.specifiedPackages()) {
if (opt.verbose2)
System.out.println("Altering javadocs for pacakge " + packageDoc);
OptionProvider view = new PackageView(outputFolder, packageDoc, root, opt);
UmlGraph.buildGraph(root, view, packageDoc);
runGraphviz(outputFolder, packageDoc.name(), packageDoc.name());
alterHtmlDocs(outputFolder, packageDoc.name(), packageDoc.name(),
"package-summary.html", "</H2>");
}
}
private static void generateContextDiagrams(RootDoc root, Options opt, String outputFolder)
throws IOException {
if (opt.verbose2)
System.out.println("Examining classes");
for (ClassDoc classDoc : root.classes()) {
if (!isIncluded(classDoc, root.specifiedPackages()))
continue;
if (opt.verbose2)
System.out.println("Altering class doc for " + classDoc);
ContextView view = new ContextView(outputFolder, classDoc, root, opt);
UmlGraph.buildGraph(root, view, classDoc);
runGraphviz(outputFolder, classDoc.containingPackage().name(), classDoc.name());
alterHtmlDocs(outputFolder, classDoc.containingPackage().name(), classDoc.name(),
classDoc.name() + ".html", classDoc.name() + "</H2>");
}
}
private static void runGraphviz(String outputFolder, String packageName, String name) {
File dotFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".dot");
File pngFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".png");
File mapFile = new File(outputFolder, packageName.replace(".", "/") + "/" + name + ".map");
try {
String command = "dot -Tcmapx -o" + mapFile.getAbsolutePath() + " -Tpng -o"
+ pngFile.getAbsolutePath() + " " + dotFile.getAbsolutePath();
Process p = Runtime.getRuntime().exec(command);
int result = p.waitFor();
if (result != 0)
System.out.println("Errors running Graphviz on " + dotFile);
} catch (Exception e) {
e.printStackTrace();
}
}
private static void alterHtmlDocs(String outputFolder, String packageName, String name,
String htmlFileName, String matchLineEnd) throws IOException {
// setup files
File output = new File(outputFolder, packageName.replace(".", "/"));
File htmlFile = new File(output, htmlFileName);
File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
File mapFile = new File(output, name + ".map");
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 FileWriter(alteredFile));
reader = new BufferedReader(new FileReader(htmlFile));
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
if (!matched && line.endsWith(matchLineEnd)) {
matched = true;
if (mapFile.exists())
insertClientSideMap(mapFile, writer);
else
System.out.println("Could not find map file " + mapFile);
writer.write("<div align=\"center\"><img src=\"" + name
+ ".png\" alt=\"Package class diagram package " + name
+ "\" usemap=\"#G\" border=0/></a></div>");
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 {
System.out.println("Error, could not find a line that ends with '" + matchLineEnd
+ "'.\n Class diagram reference not inserted");
alteredFile.delete();
}
}
/**
* Reads the map file and outputs in to the specified writer
* @throws IOException
*/
private static void insertClientSideMap(File mapFile, BufferedWriter writer) throws IOException {
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader(mapFile));
String line = null;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} finally {
if (reader != null)
reader.close();
}
}
/**
* Return true if the class name has been specified in the command line
*/
private static boolean isIncluded(ClassDoc cd, PackageDoc[] docs) {
for (PackageDoc pd : docs) {
for (ClassDoc pcd : pd.allClasses())
if (pcd.equals(cd))
return true;
}
return false;
}
/**
* Returns the output path specified on the javadoc options
*/
private static String findOutputPath(String[][] options) {
for (int i = 0; i < options.length; i++) {
if (options[i][0].equals("-d"))
return options[i][1];
}
return ".";
}
}

View File

@ -25,6 +25,7 @@ import java.util.ArrayList;
import java.util.List;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.LanguageVersion;
import com.sun.javadoc.RootDoc;
@ -52,10 +53,10 @@ public class UmlGraph {
if(views == null)
return false;
if (views.length == 0) {
buildGraph(root, opt);
buildGraph(root, opt, null);
} else {
for (int i = 0; i < views.length; i++) {
buildGraph(root, views[i]);
buildGraph(root, views[i], null);
}
}
@ -76,13 +77,13 @@ public class UmlGraph {
/**
* Builds and outputs a single graph according to the view overrides
*/
private static void buildGraph(RootDoc root, OptionProvider op) throws IOException {
public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException {
Options opt = op.getGlobalOptions();
if (opt.verbose2)
System.out.println("Building " + op.getDisplayName());
ClassDoc[] classes = root.classes();
ClassGraph c = new ClassGraph(root, op);
ClassGraph c = new ClassGraph(root, op, contextDoc);
c.prologue();
for (int i = 0; i < classes.length; i++) {
c.printClass(classes[i]);

View File

@ -40,6 +40,7 @@ import com.sun.javadoc.Tag;
* @depend - - - InterfaceMatcher
* @depend - - - PatternMatcher
* @depend - - - SubclassMatcher
* @depend - - - ContextMatcher
*
*/
class View implements OptionProvider {

View File

@ -0,0 +1,48 @@
/*
* UmlGraph class diagram testing framework
*
* Contibuted by Andrea Aime
* (C) Copyright 2005 Diomidis Spinellis
*
* Permission to use, copy, and distribute this software and its
* documentation for any purpose and without fee is hereby granted,
* provided that the above copyright notice appear in all copies and that
* both that copyright notice and this permission notice appear in
* supporting documentation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*
* $Id$
*
*/
package gr.spinellis.umlgraph.test;
import java.io.File;
import java.io.PrintWriter;
public class RunDoc {
static String sourcesFolder = "src";
static String docFolder = "javadoc";
static PrintWriter pw = new PrintWriter(System.out);
public static void main(String[] args) {
File outFolder = new File(docFolder);
if (!outFolder.exists())
outFolder.mkdirs();
String[] options = new String[] { "-docletpath", "build", "-private", "-d", docFolder,
"-sourcepath", sourcesFolder, "-subpackages", "gr.spinellis" };
runDoclet(options);
}
private static void runDoclet(String[] options) {
com.sun.tools.javadoc.Main.execute("UMLGraph test", pw, pw, pw,
"gr.spinellis.umlgraph.doclet.UmlDoc", options);
}
}

View File

@ -36,8 +36,8 @@ public class RunOne {
if (!outFolder.exists())
outFolder.mkdirs();
runView("gr.spinellis.views.ViewChildEmpty");
runSingleClass("foo");
// runView("gr.spinellis.views.ViewChildEmpty");
runSingleClass("TestHiddenOp");
}
public static void runView(String viewClass) {