From 5423bea7eb366eb28c9d2b7916a0bae9dace288f Mon Sep 17 00:00:00 2001 From: Andrea Aime Date: Sat, 28 Jan 2006 10:13:17 +0000 Subject: [PATCH] Added new @match types, with an general interface, an implementation for each kind, and a factory method in the View class to build the right implementation. Changed View implementation to work agains ClassMatchers instead of Patterns --- .../umlgraph/doclet/ClassMatcher.java | 39 +++++ .../umlgraph/doclet/ContextMatcher.java | 161 ++++++++++++++++++ .../umlgraph/doclet/InterfaceMatcher.java | 48 ++++++ .../umlgraph/doclet/PatternMatcher.java | 46 +++++ .../umlgraph/doclet/SubclassMatcher.java | 41 +++++ src/gr/spinellis/umlgraph/doclet/View.java | 106 +++++++----- src/org/umlgraph/doclet/ClassMatcher.java | 39 +++++ src/org/umlgraph/doclet/ContextMatcher.java | 161 ++++++++++++++++++ src/org/umlgraph/doclet/InterfaceMatcher.java | 48 ++++++ src/org/umlgraph/doclet/PatternMatcher.java | 46 +++++ src/org/umlgraph/doclet/SubclassMatcher.java | 41 +++++ src/org/umlgraph/doclet/View.java | 106 +++++++----- 12 files changed, 792 insertions(+), 90 deletions(-) create mode 100644 src/gr/spinellis/umlgraph/doclet/ClassMatcher.java create mode 100644 src/gr/spinellis/umlgraph/doclet/ContextMatcher.java create mode 100644 src/gr/spinellis/umlgraph/doclet/InterfaceMatcher.java create mode 100644 src/gr/spinellis/umlgraph/doclet/PatternMatcher.java create mode 100644 src/gr/spinellis/umlgraph/doclet/SubclassMatcher.java create mode 100644 src/org/umlgraph/doclet/ClassMatcher.java create mode 100644 src/org/umlgraph/doclet/ContextMatcher.java create mode 100644 src/org/umlgraph/doclet/InterfaceMatcher.java create mode 100644 src/org/umlgraph/doclet/PatternMatcher.java create mode 100644 src/org/umlgraph/doclet/SubclassMatcher.java diff --git a/src/gr/spinellis/umlgraph/doclet/ClassMatcher.java b/src/gr/spinellis/umlgraph/doclet/ClassMatcher.java new file mode 100644 index 0000000..f8b7f92 --- /dev/null +++ b/src/gr/spinellis/umlgraph/doclet/ClassMatcher.java @@ -0,0 +1,39 @@ +/* + * 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 com.sun.javadoc.ClassDoc; + +/** + * A ClassMatcher is used to check if a class definition matches a + * specific condition. The nature of the condition is dependent on + * the kind of matcher + * @author wolf + */ +public interface ClassMatcher { + /** + * Returns the options for the specified class. + */ + public boolean matches(ClassDoc cd); + + /** + * Returns the options for the specified class. + */ + public boolean matches(String name); +} diff --git a/src/gr/spinellis/umlgraph/doclet/ContextMatcher.java b/src/gr/spinellis/umlgraph/doclet/ContextMatcher.java new file mode 100644 index 0000000..1734245 --- /dev/null +++ b/src/gr/spinellis/umlgraph/doclet/ContextMatcher.java @@ -0,0 +1,161 @@ +/* + * 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.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; + +/** + * Matches classes that are directly connected to one of the classes matched by + * the regual expression specified. The context center is computed by regex lookup. + * Depending on the specified Options, inferred relations and dependencies will + * be used as well. + * @author wolf + */ +public class ContextMatcher implements ClassMatcher { + ClassGraphHack cg; + Pattern pattern; + ArrayList matched; + Options opt; + + /** + * Builds the context matcher + * @param root The root doc returned by JavaDoc + * @param pattern The pattern that will match the "center" of this + * context + * @param opt The options will be used to decide on inference + * @throws IOException + */ + public ContextMatcher(RootDoc root, Pattern pattern, Options options) throws IOException { + this.pattern = pattern; + opt = (Options) options.clone(); + opt.setOption(new String[] {"-!hide"}); + opt.setOption(new String[] {"-!attributes"}); + opt.setOption(new String[] {"-!operations"}); + this.cg = new ClassGraphHack(root, opt); + + // build up the classgraph printing the relations for all of the + // classes that make up the "center" of this context + matched = new ArrayList(); + for (ClassDoc cd : root.classes()) { + if (pattern.matcher(cd.toString()).matches()) { + matched.add(cd); + addToGraph(cd); + } + } + } + + /** + * Adds the specified class to the internal class graph along with its + * relations and depencies, eventually inferring them, according to the + * Options specified for this matcher + * @param cd + */ + private void addToGraph(ClassDoc cd) { + cg.printClass(cd); + cg.printRelations(cd); + if (opt.inferRelationships) { + cg.printInferredRelations(cd); + } + if (opt.inferDependencies) { + cg.printInferredDependencies(cd); + } + } + + /** + * @see gr.spinellis.umlgraph.doclet.ClassMatcher#matches(com.sun.javadoc.ClassDoc) + */ + public boolean matches(ClassDoc cd) { + // if the class is matched, it's in by default. + if (matched.contains(cd)) + return true; + + // otherwise, add the class to the graph and see if it's associated + // with any of the matched classes using the classgraph hack + addToGraph(cd); + return matches(cd.toString()); + } + + /** + * @see gr.spinellis.umlgraph.doclet.ClassMatcher#matches(java.lang.String) + */ + public boolean matches(String name) { + if (pattern.matcher(name).matches()) + return true; + + for (ClassDoc mcd : matched) { + String mcName = mcd.toString(); + ClassInfo ciMatched = cg.getClassInfo(mcName); + if (ciMatched != null && ciMatched.isRelated(name)) + return true; + ClassInfo ci = cg.getClassInfo(name); + if (ci != null && ci.isRelated(mcName)) + return true; + } + return false; + } + + /** + * A quick hack to compute class dependencies reusing ClassGraph but + * without generating output. Will be removed once the ClassGraph class + * will be split into two classes for graph computation and output + * generation. + * @author wolf + * + */ + private static class ClassGraphHack extends ClassGraph { + + public ClassGraphHack(RootDoc root, OptionProvider optionProvider) throws IOException { + super(root, optionProvider); + prologue(); + } + + public void prologue() throws IOException { + w = new PrintWriter(new DevNullWriter()); + } + + } + + /** + * Simple dev/null imitation + * @author wolf + */ + private static class DevNullWriter extends Writer { + + public void write(char[] cbuf, int off, int len) throws IOException { + // nothing to do + } + + public void flush() throws IOException { + // nothing to do + } + + public void close() throws IOException { + // nothing to do + } + + } + +} diff --git a/src/gr/spinellis/umlgraph/doclet/InterfaceMatcher.java b/src/gr/spinellis/umlgraph/doclet/InterfaceMatcher.java new file mode 100644 index 0000000..ad2396e --- /dev/null +++ b/src/gr/spinellis/umlgraph/doclet/InterfaceMatcher.java @@ -0,0 +1,48 @@ +package gr.spinellis.umlgraph.doclet; + +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; + +/** + * Matches every class that implements (directly or indirectly) an + * interfaces matched by regular expression provided. + */ +public class InterfaceMatcher implements ClassMatcher { + + protected RootDoc root; + protected Pattern pattern; + + public InterfaceMatcher(RootDoc root, Pattern pattern) { + this.root = root; + this.pattern = pattern; + } + + public boolean matches(ClassDoc cd) { + // if it's the interface we're looking for, match + if(cd.isInterface() && pattern.matcher(cd.toString()).matches()) + return true; + + // for each interface, recurse, since classes and interfaces + // are treated the same in the doclet API + for (ClassDoc iface : cd.interfaces()) { + if(matches(iface)) + return true; + } + + // recurse on supeclass, if available + if(cd.superclass() != null) + return matches(cd.superclass()); + + return false; + } + + public boolean matches(String name) { + ClassDoc cd = root.classNamed(name); + if(cd == null) + return false; + return matches(cd); + } + +} diff --git a/src/gr/spinellis/umlgraph/doclet/PatternMatcher.java b/src/gr/spinellis/umlgraph/doclet/PatternMatcher.java new file mode 100644 index 0000000..5e86a5b --- /dev/null +++ b/src/gr/spinellis/umlgraph/doclet/PatternMatcher.java @@ -0,0 +1,46 @@ +/* + * 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.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; + +/** + * Matches classes performing a regular expression match on the qualified class + * name + * @author wolf + */ +public class PatternMatcher implements ClassMatcher { + Pattern pattern; + + public PatternMatcher(Pattern pattern) { + this.pattern = pattern; + } + + public boolean matches(ClassDoc cd) { + return matches(cd.toString()); + } + + public boolean matches(String name) { + Matcher matcher = pattern.matcher(name); + return matcher.matches(); + } + +} diff --git a/src/gr/spinellis/umlgraph/doclet/SubclassMatcher.java b/src/gr/spinellis/umlgraph/doclet/SubclassMatcher.java new file mode 100644 index 0000000..04965a1 --- /dev/null +++ b/src/gr/spinellis/umlgraph/doclet/SubclassMatcher.java @@ -0,0 +1,41 @@ +package gr.spinellis.umlgraph.doclet; + +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; + +/** + * Matches every class that extends (directly or indirectly) a class + * matched by the regular expression provided. + */ +public class SubclassMatcher implements ClassMatcher { + + protected RootDoc root; + protected Pattern pattern; + + public SubclassMatcher(RootDoc root, Pattern pattern) { + this.root = root; + this.pattern = pattern; + } + + public boolean matches(ClassDoc cd) { + // if it's the class we're looking for return + if(pattern.matcher(cd.toString()).matches()) + return true; + + // recurse on supeclass, if available + if(cd.superclass() != null) + return matches(cd.superclass()); + + return false; + } + + public boolean matches(String name) { + ClassDoc cd = root.classNamed(name); + if(cd == null) + return false; + return matches(cd); + } + +} diff --git a/src/gr/spinellis/umlgraph/doclet/View.java b/src/gr/spinellis/umlgraph/doclet/View.java index 778774d..64df88b 100644 --- a/src/gr/spinellis/umlgraph/doclet/View.java +++ b/src/gr/spinellis/umlgraph/doclet/View.java @@ -18,15 +18,14 @@ package gr.spinellis.umlgraph.doclet; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; 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.RootDoc; import com.sun.javadoc.Tag; /** @@ -37,63 +36,83 @@ import com.sun.javadoc.Tag; * @author wolf * * @depend - - - Options + * @depend - - - ClassMatcher + * @depend - - - InterfaceMatcher + * @depend - - - PatternMatcher + * @depend - - - SubclassMatcher * */ class View implements OptionProvider { - Map optionOverrides = new LinkedHashMap(); + Map> optionOverrides = new LinkedHashMap>(); ClassDoc viewDoc; OptionProvider provider; List globalOptions; + RootDoc root; /** * Builds a view given the class that contains its definition - * @param c - * @throws PatternSyntaxException */ - public View(ClassDoc c, OptionProvider provider) throws PatternSyntaxException { + public View(RootDoc root, ClassDoc c, OptionProvider provider) { this.viewDoc = c; this.provider = provider; + this.root = root; Tag[] tags = c.tags(); - String currPattern = null; + ClassMatcher currMatcher = null; // parse options, get the global ones, and build a map of the // pattern matched overrides - List patternOptions = new ArrayList(); globalOptions = new ArrayList(); for (int i = 0; i < tags.length; i++) { if (tags[i].name().equals("@match")) { - // store the current pattern and its options - if (currPattern != null) { - String[][] options = patternOptions - .toArray(new String[patternOptions.size()][]); - optionOverrides.put(Pattern.compile(currPattern), options); + currMatcher = buildMatcher(tags[i].text()); + if(currMatcher != null) { + optionOverrides.put(currMatcher, new ArrayList()); } - // start gathering data for the new patters - String[] strings = StringUtil.tokenize(tags[i].text()); - if(strings.length < 2) { - System.err.println("Skipping uncomplete @match tag, type missing. "); - currPattern = null; - } else if(!strings[0].equals("class")) { - System.err.println("Skipping @match tag, unknown match type (only 'class' is supported for the moment). "); - currPattern = null; - } else { - currPattern = strings[1]; - } - patternOptions.clear(); } else if (tags[i].name().equals("@opt")) { String[] opts = StringUtil.tokenize(tags[i].text()); opts[0] = "-" + opts[0]; - if (currPattern == null) { + if (currMatcher == null) { globalOptions.add(opts); } else { - patternOptions.add(opts); + optionOverrides.get(currMatcher).add(opts); } } } - if (currPattern != null) { - String[][] options = patternOptions.toArray(new String[patternOptions.size()][]); - optionOverrides.put(Pattern.compile(currPattern), options); - } } + + /** + * Factory method that builds the appropriate matcher for @match tags + */ + private ClassMatcher buildMatcher(String tagText) { + // check there are at least @match and a parameter + String[] strings = StringUtil.tokenize(tagText); + if (strings.length < 2) { + System.err.println("Skipping uncomplete @match tag, type missing: " + tagText + " in view " + viewDoc); + 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()); + } 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 @@ -129,24 +148,21 @@ class View implements OptionProvider { public void overrideForClass(Options opt, ClassDoc cd) { provider.overrideForClass(opt, cd); - overrideForClass_(opt, cd.toString()); + for (ClassMatcher cm : optionOverrides.keySet()) { + if(cm.matches(cd)) { + for (String[] override : optionOverrides.get(cm)) { + opt.setOption(override); + } + } + } } public void overrideForClass(Options opt, String className) { provider.overrideForClass(opt, className); - overrideForClass_(opt, className); - } - - private void overrideForClass_(Options opt, String className) { - 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(); - for (int i = 0; i < overrides.length; i++) { - opt.setOption(overrides[i]); + for (ClassMatcher cm : optionOverrides.keySet()) { + if(cm.matches(className)) { + for (String[] override : optionOverrides.get(cm)) { + opt.setOption(override); } } } diff --git a/src/org/umlgraph/doclet/ClassMatcher.java b/src/org/umlgraph/doclet/ClassMatcher.java new file mode 100644 index 0000000..f8b7f92 --- /dev/null +++ b/src/org/umlgraph/doclet/ClassMatcher.java @@ -0,0 +1,39 @@ +/* + * 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 com.sun.javadoc.ClassDoc; + +/** + * A ClassMatcher is used to check if a class definition matches a + * specific condition. The nature of the condition is dependent on + * the kind of matcher + * @author wolf + */ +public interface ClassMatcher { + /** + * Returns the options for the specified class. + */ + public boolean matches(ClassDoc cd); + + /** + * Returns the options for the specified class. + */ + public boolean matches(String name); +} diff --git a/src/org/umlgraph/doclet/ContextMatcher.java b/src/org/umlgraph/doclet/ContextMatcher.java new file mode 100644 index 0000000..1734245 --- /dev/null +++ b/src/org/umlgraph/doclet/ContextMatcher.java @@ -0,0 +1,161 @@ +/* + * 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.IOException; +import java.io.PrintWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; + +/** + * Matches classes that are directly connected to one of the classes matched by + * the regual expression specified. The context center is computed by regex lookup. + * Depending on the specified Options, inferred relations and dependencies will + * be used as well. + * @author wolf + */ +public class ContextMatcher implements ClassMatcher { + ClassGraphHack cg; + Pattern pattern; + ArrayList matched; + Options opt; + + /** + * Builds the context matcher + * @param root The root doc returned by JavaDoc + * @param pattern The pattern that will match the "center" of this + * context + * @param opt The options will be used to decide on inference + * @throws IOException + */ + public ContextMatcher(RootDoc root, Pattern pattern, Options options) throws IOException { + this.pattern = pattern; + opt = (Options) options.clone(); + opt.setOption(new String[] {"-!hide"}); + opt.setOption(new String[] {"-!attributes"}); + opt.setOption(new String[] {"-!operations"}); + this.cg = new ClassGraphHack(root, opt); + + // build up the classgraph printing the relations for all of the + // classes that make up the "center" of this context + matched = new ArrayList(); + for (ClassDoc cd : root.classes()) { + if (pattern.matcher(cd.toString()).matches()) { + matched.add(cd); + addToGraph(cd); + } + } + } + + /** + * Adds the specified class to the internal class graph along with its + * relations and depencies, eventually inferring them, according to the + * Options specified for this matcher + * @param cd + */ + private void addToGraph(ClassDoc cd) { + cg.printClass(cd); + cg.printRelations(cd); + if (opt.inferRelationships) { + cg.printInferredRelations(cd); + } + if (opt.inferDependencies) { + cg.printInferredDependencies(cd); + } + } + + /** + * @see gr.spinellis.umlgraph.doclet.ClassMatcher#matches(com.sun.javadoc.ClassDoc) + */ + public boolean matches(ClassDoc cd) { + // if the class is matched, it's in by default. + if (matched.contains(cd)) + return true; + + // otherwise, add the class to the graph and see if it's associated + // with any of the matched classes using the classgraph hack + addToGraph(cd); + return matches(cd.toString()); + } + + /** + * @see gr.spinellis.umlgraph.doclet.ClassMatcher#matches(java.lang.String) + */ + public boolean matches(String name) { + if (pattern.matcher(name).matches()) + return true; + + for (ClassDoc mcd : matched) { + String mcName = mcd.toString(); + ClassInfo ciMatched = cg.getClassInfo(mcName); + if (ciMatched != null && ciMatched.isRelated(name)) + return true; + ClassInfo ci = cg.getClassInfo(name); + if (ci != null && ci.isRelated(mcName)) + return true; + } + return false; + } + + /** + * A quick hack to compute class dependencies reusing ClassGraph but + * without generating output. Will be removed once the ClassGraph class + * will be split into two classes for graph computation and output + * generation. + * @author wolf + * + */ + private static class ClassGraphHack extends ClassGraph { + + public ClassGraphHack(RootDoc root, OptionProvider optionProvider) throws IOException { + super(root, optionProvider); + prologue(); + } + + public void prologue() throws IOException { + w = new PrintWriter(new DevNullWriter()); + } + + } + + /** + * Simple dev/null imitation + * @author wolf + */ + private static class DevNullWriter extends Writer { + + public void write(char[] cbuf, int off, int len) throws IOException { + // nothing to do + } + + public void flush() throws IOException { + // nothing to do + } + + public void close() throws IOException { + // nothing to do + } + + } + +} diff --git a/src/org/umlgraph/doclet/InterfaceMatcher.java b/src/org/umlgraph/doclet/InterfaceMatcher.java new file mode 100644 index 0000000..ad2396e --- /dev/null +++ b/src/org/umlgraph/doclet/InterfaceMatcher.java @@ -0,0 +1,48 @@ +package gr.spinellis.umlgraph.doclet; + +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; + +/** + * Matches every class that implements (directly or indirectly) an + * interfaces matched by regular expression provided. + */ +public class InterfaceMatcher implements ClassMatcher { + + protected RootDoc root; + protected Pattern pattern; + + public InterfaceMatcher(RootDoc root, Pattern pattern) { + this.root = root; + this.pattern = pattern; + } + + public boolean matches(ClassDoc cd) { + // if it's the interface we're looking for, match + if(cd.isInterface() && pattern.matcher(cd.toString()).matches()) + return true; + + // for each interface, recurse, since classes and interfaces + // are treated the same in the doclet API + for (ClassDoc iface : cd.interfaces()) { + if(matches(iface)) + return true; + } + + // recurse on supeclass, if available + if(cd.superclass() != null) + return matches(cd.superclass()); + + return false; + } + + public boolean matches(String name) { + ClassDoc cd = root.classNamed(name); + if(cd == null) + return false; + return matches(cd); + } + +} diff --git a/src/org/umlgraph/doclet/PatternMatcher.java b/src/org/umlgraph/doclet/PatternMatcher.java new file mode 100644 index 0000000..5e86a5b --- /dev/null +++ b/src/org/umlgraph/doclet/PatternMatcher.java @@ -0,0 +1,46 @@ +/* + * 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.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; + +/** + * Matches classes performing a regular expression match on the qualified class + * name + * @author wolf + */ +public class PatternMatcher implements ClassMatcher { + Pattern pattern; + + public PatternMatcher(Pattern pattern) { + this.pattern = pattern; + } + + public boolean matches(ClassDoc cd) { + return matches(cd.toString()); + } + + public boolean matches(String name) { + Matcher matcher = pattern.matcher(name); + return matcher.matches(); + } + +} diff --git a/src/org/umlgraph/doclet/SubclassMatcher.java b/src/org/umlgraph/doclet/SubclassMatcher.java new file mode 100644 index 0000000..04965a1 --- /dev/null +++ b/src/org/umlgraph/doclet/SubclassMatcher.java @@ -0,0 +1,41 @@ +package gr.spinellis.umlgraph.doclet; + +import java.util.regex.Pattern; + +import com.sun.javadoc.ClassDoc; +import com.sun.javadoc.RootDoc; + +/** + * Matches every class that extends (directly or indirectly) a class + * matched by the regular expression provided. + */ +public class SubclassMatcher implements ClassMatcher { + + protected RootDoc root; + protected Pattern pattern; + + public SubclassMatcher(RootDoc root, Pattern pattern) { + this.root = root; + this.pattern = pattern; + } + + public boolean matches(ClassDoc cd) { + // if it's the class we're looking for return + if(pattern.matcher(cd.toString()).matches()) + return true; + + // recurse on supeclass, if available + if(cd.superclass() != null) + return matches(cd.superclass()); + + return false; + } + + public boolean matches(String name) { + ClassDoc cd = root.classNamed(name); + if(cd == null) + return false; + return matches(cd); + } + +} diff --git a/src/org/umlgraph/doclet/View.java b/src/org/umlgraph/doclet/View.java index 778774d..64df88b 100644 --- a/src/org/umlgraph/doclet/View.java +++ b/src/org/umlgraph/doclet/View.java @@ -18,15 +18,14 @@ package gr.spinellis.umlgraph.doclet; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; 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.RootDoc; import com.sun.javadoc.Tag; /** @@ -37,63 +36,83 @@ import com.sun.javadoc.Tag; * @author wolf * * @depend - - - Options + * @depend - - - ClassMatcher + * @depend - - - InterfaceMatcher + * @depend - - - PatternMatcher + * @depend - - - SubclassMatcher * */ class View implements OptionProvider { - Map optionOverrides = new LinkedHashMap(); + Map> optionOverrides = new LinkedHashMap>(); ClassDoc viewDoc; OptionProvider provider; List globalOptions; + RootDoc root; /** * Builds a view given the class that contains its definition - * @param c - * @throws PatternSyntaxException */ - public View(ClassDoc c, OptionProvider provider) throws PatternSyntaxException { + public View(RootDoc root, ClassDoc c, OptionProvider provider) { this.viewDoc = c; this.provider = provider; + this.root = root; Tag[] tags = c.tags(); - String currPattern = null; + ClassMatcher currMatcher = null; // parse options, get the global ones, and build a map of the // pattern matched overrides - List patternOptions = new ArrayList(); globalOptions = new ArrayList(); for (int i = 0; i < tags.length; i++) { if (tags[i].name().equals("@match")) { - // store the current pattern and its options - if (currPattern != null) { - String[][] options = patternOptions - .toArray(new String[patternOptions.size()][]); - optionOverrides.put(Pattern.compile(currPattern), options); + currMatcher = buildMatcher(tags[i].text()); + if(currMatcher != null) { + optionOverrides.put(currMatcher, new ArrayList()); } - // start gathering data for the new patters - String[] strings = StringUtil.tokenize(tags[i].text()); - if(strings.length < 2) { - System.err.println("Skipping uncomplete @match tag, type missing. "); - currPattern = null; - } else if(!strings[0].equals("class")) { - System.err.println("Skipping @match tag, unknown match type (only 'class' is supported for the moment). "); - currPattern = null; - } else { - currPattern = strings[1]; - } - patternOptions.clear(); } else if (tags[i].name().equals("@opt")) { String[] opts = StringUtil.tokenize(tags[i].text()); opts[0] = "-" + opts[0]; - if (currPattern == null) { + if (currMatcher == null) { globalOptions.add(opts); } else { - patternOptions.add(opts); + optionOverrides.get(currMatcher).add(opts); } } } - if (currPattern != null) { - String[][] options = patternOptions.toArray(new String[patternOptions.size()][]); - optionOverrides.put(Pattern.compile(currPattern), options); - } } + + /** + * Factory method that builds the appropriate matcher for @match tags + */ + private ClassMatcher buildMatcher(String tagText) { + // check there are at least @match and a parameter + String[] strings = StringUtil.tokenize(tagText); + if (strings.length < 2) { + System.err.println("Skipping uncomplete @match tag, type missing: " + tagText + " in view " + viewDoc); + 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()); + } 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 @@ -129,24 +148,21 @@ class View implements OptionProvider { public void overrideForClass(Options opt, ClassDoc cd) { provider.overrideForClass(opt, cd); - overrideForClass_(opt, cd.toString()); + for (ClassMatcher cm : optionOverrides.keySet()) { + if(cm.matches(cd)) { + for (String[] override : optionOverrides.get(cm)) { + opt.setOption(override); + } + } + } } public void overrideForClass(Options opt, String className) { provider.overrideForClass(opt, className); - overrideForClass_(opt, className); - } - - private void overrideForClass_(Options opt, String className) { - 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(); - for (int i = 0; i < overrides.length; i++) { - opt.setOption(overrides[i]); + for (ClassMatcher cm : optionOverrides.keySet()) { + if(cm.matches(className)) { + for (String[] override : optionOverrides.get(cm)) { + opt.setOption(override); } } }