From 956673ffb7302bc7bb677bfbf2884bf3b65a06ff Mon Sep 17 00:00:00 2001 From: Andrea Aime Date: Mon, 26 Dec 2005 21:14:36 +0000 Subject: [PATCH] View support code --- src/gr/spinellis/umlgraph/doclet/Options.java | 95 +++++++++++++++- .../spinellis/umlgraph/doclet/UmlGraph.java | 78 +++++++++++--- src/gr/spinellis/umlgraph/doclet/View.java | 101 ++++++++++++++++++ src/org/umlgraph/doclet/Options.java | 95 +++++++++++++++- src/org/umlgraph/doclet/UmlGraph.java | 78 +++++++++++--- src/org/umlgraph/doclet/View.java | 101 ++++++++++++++++++ 6 files changed, 514 insertions(+), 34 deletions(-) create mode 100644 src/gr/spinellis/umlgraph/doclet/View.java create mode 100644 src/org/umlgraph/doclet/View.java diff --git a/src/gr/spinellis/umlgraph/doclet/Options.java b/src/gr/spinellis/umlgraph/doclet/Options.java index ed3526b..afb7725 100644 --- a/src/gr/spinellis/umlgraph/doclet/Options.java +++ b/src/gr/spinellis/umlgraph/doclet/Options.java @@ -60,6 +60,9 @@ class Options implements Cloneable { String apiDocMapFileName; String apiDocRoot; boolean useGuillemot; + boolean findViews; + String viewName; + String outputDirectory; /** Guillemot left (open) */ String guilOpen = "\u00ab"; /** Guillemot right (close) */ @@ -90,6 +93,8 @@ class Options implements Cloneable { apiDocMapFileName = null; apiDocRoot = null; useGuillemot = true; + findViews = false; + viewName = null; } public Object clone() { @@ -127,7 +132,10 @@ class Options implements Cloneable { option.equals("-visibility") || option.equals("-types") || option.equals("-all") || - option.equals("-noguillemot")) + option.equals("-noguillemot") || + option.equals("-hideall") || + option.equals("-views")) + return 1; else if(option.equals("-nodefillcolor") || option.equals("-nodefontcolor") || @@ -143,14 +151,82 @@ class Options implements Cloneable { option.equals("-bgcolor") || option.equals("-hide") || option.equals("-apidocroot") || - option.equals("-apidocmap")) + option.equals("-apidocmap") || + option.equals("-d") || + option.equals("-view")) return 2; else return 0; } + + /** Resets the specified option to its default value */ + void resetOption(String[] opt) { + if (opt[0].equals("-qualify")) { + showQualified = false; + } else if (opt[0].equals("-horizontal")) { + horizontal = false; + } else if (opt[0].equals("-attributes")) { + showAttributes = false; + } else if (opt[0].equals("-enumconstants")) { + showEnumConstants = false; + } else if (opt[0].equals("-operations")) { + showOperations = false; + } else if (opt[0].equals("-enumerations")) { + showEnumerations = false; + } else if (opt[0].equals("-constructors")) { + showConstructors = false; + } else if (opt[0].equals("-visibility")) { + showVisibility = false; + } else if (opt[0].equals("-types")) { + showType = false; + } else if (opt[0].equals("-bgcolor")) { + bgColor = null; + } else if (opt[0].equals("-edgecolor")) { + edgeColor = "black"; + } else if (opt[0].equals("-edgefontcolor")) { + edgeFontColor = "black"; + } else if (opt[0].equals("-edgefontname")) { + edgeFontName = "Helvetica"; + } else if (opt[0].equals("-edgefontsize")) { + edgeFontSize = 10; + } else if (opt[0].equals("-nodefontcolor")) { + nodeFontColor = "black"; + } else if (opt[0].equals("-nodefontname")) { + nodeFontName = "Helvetica"; + } else if (opt[0].equals("-nodefontabstractname")) { + nodeFontAbstractName = "Helvetica-Oblique"; + } else if (opt[0].equals("-nodefontsize")) { + nodeFontSize = 10; + } else if (opt[0].equals("-nodefillcolor")) { + nodeFillColor = null; + } else if (opt[0].equals("-output")) { + outputFileName = "graph.dot"; + } else if (opt[0].equals("-outputencoding")) { + outputEncoding = "ISO-8859-1"; + } else if (opt[0].equals("-hide")) { + new Vector(); + } else if (opt[0].equals("-apidocroot")) { + apiDocRoot = null; + } else if (opt[0].equals("-apidocmap")) { + apiDocMapFileName = null; + } else if (opt[0].equals("-noguillemot")) { + guilOpen = "\u00ab"; + guilClose = "\u00bb"; + } else if (opt[0].equals("-view")) { + viewName = null; + } else if (opt[0].equals("-views")) { + findViews = false; + } else if (opt[0].equals("-d")) { + outputDirectory = null; + } else if (opt[0].equals("-hideall")) { + hidePatterns.clear(); + } else + ; // Do nothing, javadoc will handle the option or complain, if + // needed. + } /** Set the options based on a lingle option and its arguments */ - private void setOption(String[] opt) { + void setOption(String[] opt) { if(opt[0].equals("-qualify")) { showQualified = true; } else if(opt[0].equals("-horizontal")) { @@ -195,6 +271,8 @@ class Options implements Cloneable { outputFileName = opt[1]; } else if(opt[0].equals("-outputencoding")) { outputEncoding = opt[1]; + } else if(opt[0].equals("-hideall")) { + hidePatterns.add(Pattern.compile(".*")); } else if(opt[0].equals("-hide")) { try { hidePatterns.add(Pattern.compile(opt[1])); @@ -208,6 +286,12 @@ class Options implements Cloneable { } else if(opt[0].equals("-noguillemot")) { guilOpen = "\\<\\<"; guilClose = "\\>\\>"; + } else if (opt[0].equals("-view")) { + viewName = opt[1]; + } else if (opt[0].equals("-views")) { + findViews = true; + } else if (opt[0].equals("-d")) { + outputDirectory = opt[1]; } else ; // Do nothing, javadoc will handle the option or complain, if needed. } @@ -235,6 +319,11 @@ class Options implements Cloneable { FileOutputStream fos = new FileOutputStream(outputFileName); w = new PrintWriter(new BufferedWriter(new OutputStreamWriter(fos, outputEncoding))); } + + public void closeFile() { + w.close(); + } + /** * Check if the supplied string matches an entity specified diff --git a/src/gr/spinellis/umlgraph/doclet/UmlGraph.java b/src/gr/spinellis/umlgraph/doclet/UmlGraph.java index a919112..59aacce 100644 --- a/src/gr/spinellis/umlgraph/doclet/UmlGraph.java +++ b/src/gr/spinellis/umlgraph/doclet/UmlGraph.java @@ -20,9 +20,13 @@ package gr.spinellis.umlgraph.doclet; -import com.sun.javadoc.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; -import java.io.*; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.LanguageVersion; +import com.sun.javadoc.RootDoc; /** * Doclet API implementation @@ -37,34 +41,80 @@ public class UmlGraph { /** Entry point */ public static boolean start(RootDoc root) throws IOException { Options opt = new Options(); - ClassInfo.reset(); - opt.setOptions(root.options()); - opt.openFile(); opt.setOptions(root.classNamed("UMLOptions")); + + View[] views = buildViews(opt, root); + if (views.length == 0) { + buildGraph(root, null, opt); + } else { + for (int i = 0; i < views.length; i++) { + buildGraph(root, views[i], (Options) opt.clone()); + } + } + + return true; + + } + + /** + * Builds and outputs a single graph according to the view overrides + */ + private static void buildGraph(RootDoc root, View view, Options opt) throws IOException { + if (view != null) + view.applyOverrides(opt); + opt.openFile(); prologue(opt); ClassDoc[] classes = root.classes(); - ClassGraph c = new ClassGraph(root.specifiedPackages(), - opt.apiDocRoot, opt.apiDocMapFileName); - for (ClassDoc cd : classes) { + ClassGraph c = new ClassGraph(root.specifiedPackages(), opt.apiDocRoot, + opt.apiDocMapFileName); + for (int i = 0; i < classes.length; i++) { // Process class-local options (through @opt tags) Options localOpt = (Options) opt.clone(); - localOpt.setOptions(cd); - c.printClass(localOpt, cd); + localOpt.setOptions(classes[i]); + if (view != null) + view.applyOverrides(localOpt, classes[i]); + c.printClass(localOpt, classes[i]); } - for (ClassDoc cd : classes) { + for (int i = 0; i < classes.length; i++) { // Process class-local options (through @opt tags) Options localOpt = (Options) opt.clone(); - localOpt.setOptions(cd); - c.printRelations(localOpt, cd); + localOpt.setOptions(classes[i]); + if (view != null) + view.applyOverrides(localOpt, classes[i]); + c.printRelations(localOpt, classes[i]); } c.printExtraClasses(opt, root); epilogue(opt); - return true; + opt.closeFile(); } + + + /** + * Builds the views according to the parameters on the command line + */ + private static View[] buildViews(Options opt, RootDoc root) { + if (opt.findViews) { + List views = new ArrayList(); + ClassDoc[] classes = root.classes(); + for (int i = 0; i < classes.length; i++) { + if (classes[i].tags("view").length > 0) { + views.add(new View(classes[i])); + } + } + return views.toArray(new View[views.size()]); + } else if (opt.viewName != null) { + ClassDoc viewClass = root.classNamed(opt.viewName); + return new View[] { new View(viewClass) }; + } else { + return new View[0]; + } + } + + /** Option checking */ public static int optionLength(String option) { return Options.optionLength(option); diff --git a/src/gr/spinellis/umlgraph/doclet/View.java b/src/gr/spinellis/umlgraph/doclet/View.java new file mode 100644 index 0000000..a001f03 --- /dev/null +++ b/src/gr/spinellis/umlgraph/doclet/View.java @@ -0,0 +1,101 @@ +/* + * 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.doclet; + +import java.io.File; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.Tag; + +/** + * 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 defined on the same source tree, effectively allowing to create multiple + * class diagram out of it. + * @author wolf + * + */ +class View { + Map optionOverrides = new LinkedHashMap(); + ClassDoc viewDoc; + + public View(ClassDoc c) { + this.viewDoc = c; + Tag[] tags = c.tags("opt_override"); + for (int i = 0; i < tags.length; i++) { + String[] opts = StringUtil.tokenize(tags[i].text()); + try { + optionOverrides.put(Pattern.compile(opts[0]), opts); + } catch (PatternSyntaxException e) { + System.err.println("Skipping invalid pattern " + opts[0] + " in view " + + c.toString()); + } + + } + } + + /** + * Applies global view overrides + */ + public void applyOverrides(Options o) { + o.setOptions(viewDoc); + File dotFile = new File(o.outputDirectory, viewDoc.name() + ".dot"); + o.setOption(new String[] {"-output", dotFile.getPath()}); + } + + /** + * Applies local view overrides + */ + public void applyOverrides(Options o, ClassDoc c) { + String className = c.toString(); + for (Iterator> iter = optionOverrides.entrySet().iterator(); iter + .hasNext();) { + Map.Entry mapEntry = iter.next(); + Pattern regex = mapEntry.getKey(); + Matcher matcher = regex.matcher(className); + if (matcher.matches()) { + String[] overrides = mapEntry.getValue(); // the first element is in fact the pattern + for (int i = 1; i < overrides.length; i++) { + String[] option = null; + boolean reset = false; + if (overrides[i].contains("=")) { + option = overrides[i].split("="); + } else { + option = new String[] { overrides[i] }; + } + if (option[0].charAt(0) == '!') { + reset = true; + option[0] = option[0].substring(1); + } + option[0] = "-" + option[0]; + if (reset) { + o.resetOption(option); + } else { + o.setOption(option); + } + } + } + } + } +} diff --git a/src/org/umlgraph/doclet/Options.java b/src/org/umlgraph/doclet/Options.java index ed3526b..afb7725 100644 --- a/src/org/umlgraph/doclet/Options.java +++ b/src/org/umlgraph/doclet/Options.java @@ -60,6 +60,9 @@ class Options implements Cloneable { String apiDocMapFileName; String apiDocRoot; boolean useGuillemot; + boolean findViews; + String viewName; + String outputDirectory; /** Guillemot left (open) */ String guilOpen = "\u00ab"; /** Guillemot right (close) */ @@ -90,6 +93,8 @@ class Options implements Cloneable { apiDocMapFileName = null; apiDocRoot = null; useGuillemot = true; + findViews = false; + viewName = null; } public Object clone() { @@ -127,7 +132,10 @@ class Options implements Cloneable { option.equals("-visibility") || option.equals("-types") || option.equals("-all") || - option.equals("-noguillemot")) + option.equals("-noguillemot") || + option.equals("-hideall") || + option.equals("-views")) + return 1; else if(option.equals("-nodefillcolor") || option.equals("-nodefontcolor") || @@ -143,14 +151,82 @@ class Options implements Cloneable { option.equals("-bgcolor") || option.equals("-hide") || option.equals("-apidocroot") || - option.equals("-apidocmap")) + option.equals("-apidocmap") || + option.equals("-d") || + option.equals("-view")) return 2; else return 0; } + + /** Resets the specified option to its default value */ + void resetOption(String[] opt) { + if (opt[0].equals("-qualify")) { + showQualified = false; + } else if (opt[0].equals("-horizontal")) { + horizontal = false; + } else if (opt[0].equals("-attributes")) { + showAttributes = false; + } else if (opt[0].equals("-enumconstants")) { + showEnumConstants = false; + } else if (opt[0].equals("-operations")) { + showOperations = false; + } else if (opt[0].equals("-enumerations")) { + showEnumerations = false; + } else if (opt[0].equals("-constructors")) { + showConstructors = false; + } else if (opt[0].equals("-visibility")) { + showVisibility = false; + } else if (opt[0].equals("-types")) { + showType = false; + } else if (opt[0].equals("-bgcolor")) { + bgColor = null; + } else if (opt[0].equals("-edgecolor")) { + edgeColor = "black"; + } else if (opt[0].equals("-edgefontcolor")) { + edgeFontColor = "black"; + } else if (opt[0].equals("-edgefontname")) { + edgeFontName = "Helvetica"; + } else if (opt[0].equals("-edgefontsize")) { + edgeFontSize = 10; + } else if (opt[0].equals("-nodefontcolor")) { + nodeFontColor = "black"; + } else if (opt[0].equals("-nodefontname")) { + nodeFontName = "Helvetica"; + } else if (opt[0].equals("-nodefontabstractname")) { + nodeFontAbstractName = "Helvetica-Oblique"; + } else if (opt[0].equals("-nodefontsize")) { + nodeFontSize = 10; + } else if (opt[0].equals("-nodefillcolor")) { + nodeFillColor = null; + } else if (opt[0].equals("-output")) { + outputFileName = "graph.dot"; + } else if (opt[0].equals("-outputencoding")) { + outputEncoding = "ISO-8859-1"; + } else if (opt[0].equals("-hide")) { + new Vector(); + } else if (opt[0].equals("-apidocroot")) { + apiDocRoot = null; + } else if (opt[0].equals("-apidocmap")) { + apiDocMapFileName = null; + } else if (opt[0].equals("-noguillemot")) { + guilOpen = "\u00ab"; + guilClose = "\u00bb"; + } else if (opt[0].equals("-view")) { + viewName = null; + } else if (opt[0].equals("-views")) { + findViews = false; + } else if (opt[0].equals("-d")) { + outputDirectory = null; + } else if (opt[0].equals("-hideall")) { + hidePatterns.clear(); + } else + ; // Do nothing, javadoc will handle the option or complain, if + // needed. + } /** Set the options based on a lingle option and its arguments */ - private void setOption(String[] opt) { + void setOption(String[] opt) { if(opt[0].equals("-qualify")) { showQualified = true; } else if(opt[0].equals("-horizontal")) { @@ -195,6 +271,8 @@ class Options implements Cloneable { outputFileName = opt[1]; } else if(opt[0].equals("-outputencoding")) { outputEncoding = opt[1]; + } else if(opt[0].equals("-hideall")) { + hidePatterns.add(Pattern.compile(".*")); } else if(opt[0].equals("-hide")) { try { hidePatterns.add(Pattern.compile(opt[1])); @@ -208,6 +286,12 @@ class Options implements Cloneable { } else if(opt[0].equals("-noguillemot")) { guilOpen = "\\<\\<"; guilClose = "\\>\\>"; + } else if (opt[0].equals("-view")) { + viewName = opt[1]; + } else if (opt[0].equals("-views")) { + findViews = true; + } else if (opt[0].equals("-d")) { + outputDirectory = opt[1]; } else ; // Do nothing, javadoc will handle the option or complain, if needed. } @@ -235,6 +319,11 @@ class Options implements Cloneable { FileOutputStream fos = new FileOutputStream(outputFileName); w = new PrintWriter(new BufferedWriter(new OutputStreamWriter(fos, outputEncoding))); } + + public void closeFile() { + w.close(); + } + /** * Check if the supplied string matches an entity specified diff --git a/src/org/umlgraph/doclet/UmlGraph.java b/src/org/umlgraph/doclet/UmlGraph.java index a919112..59aacce 100644 --- a/src/org/umlgraph/doclet/UmlGraph.java +++ b/src/org/umlgraph/doclet/UmlGraph.java @@ -20,9 +20,13 @@ package gr.spinellis.umlgraph.doclet; -import com.sun.javadoc.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; -import java.io.*; +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.LanguageVersion; +import com.sun.javadoc.RootDoc; /** * Doclet API implementation @@ -37,34 +41,80 @@ public class UmlGraph { /** Entry point */ public static boolean start(RootDoc root) throws IOException { Options opt = new Options(); - ClassInfo.reset(); - opt.setOptions(root.options()); - opt.openFile(); opt.setOptions(root.classNamed("UMLOptions")); + + View[] views = buildViews(opt, root); + if (views.length == 0) { + buildGraph(root, null, opt); + } else { + for (int i = 0; i < views.length; i++) { + buildGraph(root, views[i], (Options) opt.clone()); + } + } + + return true; + + } + + /** + * Builds and outputs a single graph according to the view overrides + */ + private static void buildGraph(RootDoc root, View view, Options opt) throws IOException { + if (view != null) + view.applyOverrides(opt); + opt.openFile(); prologue(opt); ClassDoc[] classes = root.classes(); - ClassGraph c = new ClassGraph(root.specifiedPackages(), - opt.apiDocRoot, opt.apiDocMapFileName); - for (ClassDoc cd : classes) { + ClassGraph c = new ClassGraph(root.specifiedPackages(), opt.apiDocRoot, + opt.apiDocMapFileName); + for (int i = 0; i < classes.length; i++) { // Process class-local options (through @opt tags) Options localOpt = (Options) opt.clone(); - localOpt.setOptions(cd); - c.printClass(localOpt, cd); + localOpt.setOptions(classes[i]); + if (view != null) + view.applyOverrides(localOpt, classes[i]); + c.printClass(localOpt, classes[i]); } - for (ClassDoc cd : classes) { + for (int i = 0; i < classes.length; i++) { // Process class-local options (through @opt tags) Options localOpt = (Options) opt.clone(); - localOpt.setOptions(cd); - c.printRelations(localOpt, cd); + localOpt.setOptions(classes[i]); + if (view != null) + view.applyOverrides(localOpt, classes[i]); + c.printRelations(localOpt, classes[i]); } c.printExtraClasses(opt, root); epilogue(opt); - return true; + opt.closeFile(); } + + + /** + * Builds the views according to the parameters on the command line + */ + private static View[] buildViews(Options opt, RootDoc root) { + if (opt.findViews) { + List views = new ArrayList(); + ClassDoc[] classes = root.classes(); + for (int i = 0; i < classes.length; i++) { + if (classes[i].tags("view").length > 0) { + views.add(new View(classes[i])); + } + } + return views.toArray(new View[views.size()]); + } else if (opt.viewName != null) { + ClassDoc viewClass = root.classNamed(opt.viewName); + return new View[] { new View(viewClass) }; + } else { + return new View[0]; + } + } + + /** Option checking */ public static int optionLength(String option) { return Options.optionLength(option); diff --git a/src/org/umlgraph/doclet/View.java b/src/org/umlgraph/doclet/View.java new file mode 100644 index 0000000..a001f03 --- /dev/null +++ b/src/org/umlgraph/doclet/View.java @@ -0,0 +1,101 @@ +/* + * 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.doclet; + +import java.io.File; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.Tag; + +/** + * 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 defined on the same source tree, effectively allowing to create multiple + * class diagram out of it. + * @author wolf + * + */ +class View { + Map optionOverrides = new LinkedHashMap(); + ClassDoc viewDoc; + + public View(ClassDoc c) { + this.viewDoc = c; + Tag[] tags = c.tags("opt_override"); + for (int i = 0; i < tags.length; i++) { + String[] opts = StringUtil.tokenize(tags[i].text()); + try { + optionOverrides.put(Pattern.compile(opts[0]), opts); + } catch (PatternSyntaxException e) { + System.err.println("Skipping invalid pattern " + opts[0] + " in view " + + c.toString()); + } + + } + } + + /** + * Applies global view overrides + */ + public void applyOverrides(Options o) { + o.setOptions(viewDoc); + File dotFile = new File(o.outputDirectory, viewDoc.name() + ".dot"); + o.setOption(new String[] {"-output", dotFile.getPath()}); + } + + /** + * Applies local view overrides + */ + public void applyOverrides(Options o, ClassDoc c) { + String className = c.toString(); + for (Iterator> iter = optionOverrides.entrySet().iterator(); iter + .hasNext();) { + Map.Entry mapEntry = iter.next(); + Pattern regex = mapEntry.getKey(); + Matcher matcher = regex.matcher(className); + if (matcher.matches()) { + String[] overrides = mapEntry.getValue(); // the first element is in fact the pattern + for (int i = 1; i < overrides.length; i++) { + String[] option = null; + boolean reset = false; + if (overrides[i].contains("=")) { + option = overrides[i].split("="); + } else { + option = new String[] { overrides[i] }; + } + if (option[0].charAt(0) == '!') { + reset = true; + option[0] = option[0].substring(1); + } + option[0] = "-" + option[0]; + if (reset) { + o.resetOption(option); + } else { + o.setOption(option); + } + } + } + } + } +}