001: package org.umlgraph.doclet;
002:
003: import java.io.BufferedReader;
004: import java.io.BufferedWriter;
005: import java.io.File;
006: import java.io.FileReader;
007: import java.io.FileWriter;
008: import java.io.IOException;
009: import java.io.InputStreamReader;
010: import java.util.HashSet;
011: import java.util.Set;
012: import java.util.regex.Pattern;
013:
014: import com.sun.javadoc.ClassDoc;
015: import com.sun.javadoc.LanguageVersion;
016: import com.sun.javadoc.PackageDoc;
017: import com.sun.javadoc.RootDoc;
018: import com.sun.tools.doclets.standard.Standard;
019:
020: /**
021: * Chaining doclet that runs the standart Javadoc doclet first, and on success,
022: * runs the generation of dot files by UMLGraph
023: * @author wolf
024: *
025: * @depend - - - WrappedClassDoc
026: * @depend - - - WrappedRootDoc
027: */
028: public class UmlGraphDoc {
029: /**
030: * Option check, forwards options to the standard doclet, if that one refuses them,
031: * they are sent to UmlGraph
032: */
033: public static int optionLength(String option) {
034: int result = Standard.optionLength(option);
035: if (result != 0)
036: return result;
037: else
038: return UmlGraph.optionLength(option);
039: }
040:
041: /**
042: * Standard doclet entry point
043: * @param root
044: * @return
045: */
046: public static boolean start(RootDoc root) {
047: root.printNotice("UmlGraphDoc version " + Version.VERSION
048: + ", running the standard doclet");
049: Standard.start(root);
050: root.printNotice("UmlGraphDoc version " + Version.VERSION
051: + ", altering javadocs");
052: try {
053: String outputFolder = findOutputPath(root.options());
054:
055: Options opt = new Options();
056: opt.setOptions(root.options());
057: // in javadoc enumerations are always printed
058: opt.showEnumerations = true;
059: opt.relativeLinksForSourcePackages = true;
060: // enable strict matching for hide expressions
061: opt.strictMatching = true;
062: // root.printNotice(opt.toString());
063:
064: root = new WrappedRootDoc(root);
065: generatePackageDiagrams(root, opt, outputFolder);
066: generateContextDiagrams(root, opt, outputFolder);
067: } catch (Throwable t) {
068: root.printWarning("Error!");
069: root.printWarning(t.toString());
070: t.printStackTrace();
071: return false;
072: }
073: return true;
074: }
075:
076: /**
077: * Standand doclet entry
078: * @return
079: */
080: public static LanguageVersion languageVersion() {
081: return Standard.languageVersion();
082: }
083:
084: /**
085: * Generates the package diagrams for all of the packages that contain classes among those
086: * returned by RootDoc.class()
087: */
088: private static void generatePackageDiagrams(RootDoc root,
089: Options opt, String outputFolder) throws IOException {
090: Set<String> packages = new HashSet<String>();
091: for (ClassDoc classDoc : root.classes()) {
092: PackageDoc packageDoc = classDoc.containingPackage();
093: if (!packages.contains(packageDoc.name())) {
094: packages.add(packageDoc.name());
095: OptionProvider view = new PackageView(outputFolder,
096: packageDoc, root, opt);
097: UmlGraph.buildGraph(root, view, packageDoc);
098: runGraphviz(outputFolder, packageDoc.name(), packageDoc
099: .name(), root);
100: alterHtmlDocs(outputFolder, packageDoc.name(),
101: packageDoc.name(), "package-summary.html",
102: Pattern.compile("</H2>"), root);
103: }
104: }
105: }
106:
107: /**
108: * Generates the context diagram for a single class
109: */
110: private static void generateContextDiagrams(RootDoc root,
111: Options opt, String outputFolder) throws IOException {
112: ContextView view = null;
113: for (ClassDoc classDoc : root.classes()) {
114: if (view == null)
115: view = new ContextView(outputFolder, classDoc, root,
116: opt);
117: else
118: view.setContextCenter(classDoc);
119: UmlGraph.buildGraph(root, view, classDoc);
120: runGraphviz(outputFolder, classDoc.containingPackage()
121: .name(), classDoc.name(), root);
122: alterHtmlDocs(outputFolder, classDoc.containingPackage()
123: .name(), classDoc.name(),
124: classDoc.name() + ".html", Pattern
125: .compile("(Class|Interface|Enum) "
126: + classDoc.name() + ".*"), root);
127: }
128: }
129:
130: /**
131: * Runs Graphviz dot building both a diagram (in png format) and a client side map for it.
132: * <p>
133: * At the moment, it assumes dot.exe is in the classpahth
134: */
135: private static void runGraphviz(String outputFolder,
136: String packageName, String name, RootDoc root) {
137: File dotFile = new File(outputFolder, packageName.replace(".",
138: "/")
139: + "/" + name + ".dot");
140: File pngFile = new File(outputFolder, packageName.replace(".",
141: "/")
142: + "/" + name + ".png");
143: File mapFile = new File(outputFolder, packageName.replace(".",
144: "/")
145: + "/" + name + ".map");
146:
147: try {
148: Process p = Runtime.getRuntime().exec(
149: new String[] { "dot", "-Tcmapx", "-o",
150: mapFile.getAbsolutePath(), "-Tpng", "-o",
151: pngFile.getAbsolutePath(),
152: dotFile.getAbsolutePath() });
153: BufferedReader reader = new BufferedReader(
154: new InputStreamReader(p.getErrorStream()));
155: String line = null;
156: while ((line = reader.readLine()) != null)
157: root.printWarning(line);
158: int result = p.waitFor();
159: if (result != 0)
160: root.printWarning("Errors running Graphviz on "
161: + dotFile);
162: } catch (Exception e) {
163: e.printStackTrace();
164: System.err
165: .println("Ensure that dot is in your path and that its path does not contain spaces");
166: }
167: }
168:
169: /**
170: * Takes an HTML file, looks for the first instance of the specified insertion point, and
171: * inserts the diagram image reference and a client side map in that point.
172: */
173: private static void alterHtmlDocs(String outputFolder,
174: String packageName, String className, String htmlFileName,
175: Pattern insertPointPattern, RootDoc root)
176: throws IOException {
177: // setup files
178: File output = new File(outputFolder, packageName.replace(".",
179: "/"));
180: File htmlFile = new File(output, htmlFileName);
181: File alteredFile = new File(htmlFile.getAbsolutePath() + ".uml");
182: File mapFile = new File(output, className + ".map");
183: if (!htmlFile.exists()) {
184: System.err.println("Expected file not found: "
185: + htmlFile.getAbsolutePath());
186: return;
187: }
188:
189: // parse & rewrite
190: BufferedWriter writer = null;
191: BufferedReader reader = null;
192: boolean matched = false;
193: try {
194: int BUFSIZE = (int) Math.pow(2, 20); // more or less one megabyte
195: writer = new BufferedWriter(new FileWriter(alteredFile),
196: BUFSIZE);
197: reader = new BufferedReader(new FileReader(htmlFile));
198:
199: String line;
200: while ((line = reader.readLine()) != null) {
201: writer.write(line);
202: writer.newLine();
203: if (!matched
204: && insertPointPattern.matcher(line).matches()) {
205: matched = true;
206: if (mapFile.exists())
207: insertClientSideMap(mapFile, writer);
208: else
209: root.printWarning("Could not find map file "
210: + mapFile);
211: writer
212: .write("<div align=\"center\"><img src=\""
213: + className
214: + ".png\" alt=\"Package class diagram package "
215: + className
216: + "\" usemap=\"#G\" border=0/></a></div>");
217: writer.newLine();
218: }
219: }
220: } finally {
221: if (writer != null)
222: writer.close();
223: if (reader != null)
224: reader.close();
225: }
226:
227: // if altered, delete old file and rename new one to the old file name
228: if (matched) {
229: htmlFile.delete();
230: alteredFile.renameTo(htmlFile);
231: } else {
232: root
233: .printNotice("Warning, could not find a line that matches the pattern '"
234: + insertPointPattern.pattern()
235: + "'.\n Class diagram reference not inserted");
236: alteredFile.delete();
237: }
238: }
239:
240: /**
241: * Reads the map file and outputs in to the specified writer
242: * @throws IOException
243: */
244: private static void insertClientSideMap(File mapFile,
245: BufferedWriter writer) throws IOException {
246: BufferedReader reader = null;
247: try {
248: reader = new BufferedReader(new FileReader(mapFile));
249: String line = null;
250: while ((line = reader.readLine()) != null) {
251: writer.write(line);
252: writer.newLine();
253: }
254: } finally {
255: if (reader != null)
256: reader.close();
257: }
258: }
259:
260: /**
261: * Returns the output path specified on the javadoc options
262: */
263: private static String findOutputPath(String[][] options) {
264: for (int i = 0; i < options.length; i++) {
265: if (options[i][0].equals("-d"))
266: return options[i][1];
267: }
268: return ".";
269: }
270:
271: }
|