/** * Return the full name of a relation's node c. This may involve appending the port :p for the * standard nodes whose outline is rendered through an inner table. * * @param c the node's class (may be null) * @param cName the node's class name */ private String relationNode(ClassDoc c, String cName) { Options opt; if (c == null) opt = optionProvider.getOptionsFor(cName); else opt = optionProvider.getOptionsFor(c); String name = getNodeName(cName); return name + opt.shape.landingPort(); }
/** Return true if c has a @hidden tag associated with it */ private boolean hidden(ProgramElementDoc c) { Tag tags[] = c.tags("hidden"); if (tags.length > 0) return true; tags = c.tags("view"); if (tags.length > 0) return true; Options opt; if (c instanceof ClassDoc) opt = optionProvider.getOptionsFor((ClassDoc) c); else opt = optionProvider.getOptionsFor(c.containingClass()); return opt.matchesHideExpression(c.toString()); }
/** * Returns the appropriate URL "root" for a given class name. The root will be used as the prefix * of the URL used to link the class in the final diagram to the associated JavaDoc page. */ private String mapApiDocRoot(String className) { String root = null; /* If no packages are specified, we use apiDocRoot for all of them. */ if (rootClasses.contains(className)) { root = optionProvider.getGlobalOptions().apiDocRoot; } else { Options globalOptions = optionProvider.getGlobalOptions(); root = globalOptions.getApiDocRoot(className); } return root; }
/** Print classes that were parts of relationships, but not parsed by javadoc */ public void printExtraClasses(RootDoc root) { Set<String> names = new HashSet<String>(classnames.keySet()); for (String className : names) { ClassInfo info = getClassInfo(className); if (!info.nodePrinted) { ClassDoc c = root.classNamed(className); if (c != null) { printClass(c, false); } else { Options opt = optionProvider.getOptionsFor(className); if (opt.matchesHideExpression(className)) continue; w.println("\t// " + className); w.print("\t" + info.name + "[label="); externalTableStart(opt, className, classToUrl(className)); innerTableStart(); int idx = className.lastIndexOf("."); if (opt.postfixPackage && idx > 0 && idx < (className.length() - 1)) { String packageName = className.substring(0, idx); String cn = className.substring(idx + 1); tableLine(Align.CENTER, escape(cn), opt, Font.CLASS); tableLine(Align.CENTER, packageName, opt, Font.PACKAGE); } else { tableLine(Align.CENTER, escape(className), opt, Font.CLASS); } innerTableEnd(); externalTableEnd(); if (className == null || className.length() == 0) w.print(", URL=\"" + classToUrl(className) + "\""); nodeProperties(opt); } } } }
/** Builds and outputs a single graph according to the view overrides */ public static void buildGraph(RootDoc root, OptionProvider op, Doc contextDoc) throws IOException { if (getCommentOptions() == null) buildOptions(root); Options opt = op.getGlobalOptions(); root.printNotice("Building " + op.getDisplayName()); ClassDoc[] classes = root.classes(); ClassGraph c = new ClassGraph(root, op, contextDoc); c.prologue(); for (int i = 0; i < classes.length; i++) c.printClass(classes[i], true); for (int i = 0; i < classes.length; i++) c.printRelations(classes[i]); if (opt.inferRelationships) c.printInferredRelations(classes); if (opt.inferDependencies) c.printInferredDependencies(classes); c.printExtraClasses(root); c.epilogue(); }
/** * Prints dependencies recovered from the methods of a class. A dependency is inferred only if * another relation between the two classes is not already in the graph. * * @param classes */ public void printInferredDependencies(ClassDoc c) { Options opt = optionProvider.getOptionsFor(c); String sourceName = c.toString(); if (hidden(c)) return; Set<Type> types = new HashSet<Type>(); // harvest method return and parameter types for (MethodDoc method : filterByVisibility(c.methods(false), opt.inferDependencyVisibility)) { types.add(method.returnType()); for (Parameter parameter : method.parameters()) { types.add(parameter.type()); } } // and the field types if (!opt.inferRelationships) { for (FieldDoc field : filterByVisibility(c.fields(false), opt.inferDependencyVisibility)) { types.add(field.type()); } } // see if there are some type parameters if (c.asParameterizedType() != null) { ParameterizedType pt = c.asParameterizedType(); types.addAll(Arrays.asList(pt.typeArguments())); } // see if type parameters extend something for (TypeVariable tv : c.typeParameters()) { if (tv.bounds().length > 0) types.addAll(Arrays.asList(tv.bounds())); } // and finally check for explicitly imported classes (this // assumes there are no unused imports...) if (opt.useImports) types.addAll(Arrays.asList(c.importedClasses())); // compute dependencies for (Type type : types) { // skip primitives and type variables, as well as dependencies // on the source class if (type.isPrimitive() || type instanceof WildcardType || type instanceof TypeVariable || c.toString().equals(type.asClassDoc().toString())) continue; // check if the destination is excluded from inference ClassDoc fc = type.asClassDoc(); if (hidden(fc)) continue; // check if source and destination are in the same package and if we are allowed // to infer dependencies between classes in the same package if (!opt.inferDepInPackage && c.containingPackage().equals(fc.containingPackage())) continue; // if source and dest are not already linked, add a dependency RelationPattern rp = getClassInfo(sourceName).getRelation(fc.toString()); if (rp == null || rp.matchesOne(new RelationPattern(RelationDirection.OUT))) { relation(opt, RelationType.DEPEND, c, fc, "", "", ""); } } }
/** * Dot prologue * * @throws IOException */ public void prologue() throws IOException { Options opt = optionProvider.getGlobalOptions(); OutputStream os = null; if (opt.outputFileName.equals("-")) os = System.out; else { // prepare output file. Use the output file name as a full path unless the output // directory is specified File file = null; if (opt.outputDirectory != null) file = new File(opt.outputDirectory, opt.outputFileName); else file = new File(opt.outputFileName); // make sure the output directory are there, otherwise create them if (file.getParentFile() != null && !file.getParentFile().exists()) file.getParentFile().mkdirs(); os = new FileOutputStream(file); } // print prologue w = new PrintWriter(new OutputStreamWriter(new BufferedOutputStream(os), opt.outputEncoding)); w.println( "#!/usr/local/bin/dot\n" + "#\n" + "# Class diagram \n" + "# Generated by UMLGraph version " + Version.VERSION + " (http://www.umlgraph.org/)\n" + "#\n\n" + "digraph G {\n" + "\tedge [fontname=\"" + opt.edgeFontName + "\",fontsize=10,labelfontname=\"" + opt.edgeFontName + "\",labelfontsize=10];\n" + "\tnode [fontname=\"" + opt.nodeFontName + "\",fontsize=10,shape=plaintext];"); w.println("\tnodesep=" + opt.nodeSep + ";"); w.println("\tranksep=" + opt.rankSep + ";"); if (opt.horizontal) w.println("\trankdir=LR;"); if (opt.bgColor != null) w.println("\tbgcolor=\"" + opt.bgColor + "\";\n"); }
private FieldRelationInfo getFieldRelationInfo(FieldDoc field) { Type type = field.type(); if (type.isPrimitive() || type instanceof WildcardType || type instanceof TypeVariable) return null; if (type.dimension().endsWith("[]")) { return new FieldRelationInfo(type.asClassDoc(), true); } Options opt = optionProvider.getOptionsFor(type.asClassDoc()); if (opt.matchesCollPackageExpression(type.qualifiedTypeName())) { Type[] argTypes = getInterfaceTypeArguments(collectionClassDoc, type); if (argTypes != null && argTypes.length == 1 && !argTypes[0].isPrimitive()) return new FieldRelationInfo(argTypes[0].asClassDoc(), true); argTypes = getInterfaceTypeArguments(mapClassDoc, type); if (argTypes != null && argTypes.length == 2 && !argTypes[1].isPrimitive()) return new FieldRelationInfo(argTypes[1].asClassDoc(), true); } return new FieldRelationInfo(type.asClassDoc(), false); }
/** * Prints associations recovered from the fields of a class. An association is inferred only if * another relation between the two classes is not already in the graph. * * @param classes */ public void printInferredRelations(ClassDoc c) { Options opt = optionProvider.getOptionsFor(c); // check if the source is excluded from inference if (hidden(c)) return; for (FieldDoc field : c.fields(false)) { if (hidden(field)) continue; // skip statics if (field.isStatic()) continue; // skip primitives FieldRelationInfo fri = getFieldRelationInfo(field); if (fri == null) continue; // check if the destination is excluded from inference if (hidden(fri.cd)) continue; String destAdornment = fri.multiple ? "*" : ""; relation(opt, opt.inferRelationshipType, c, fri.cd, "", "", destAdornment); } }
/** * Create a new ClassGraph. * * <p>The packages passed as an argument are the ones specified on the command line. * * <p>Local URLs will be generated for these packages. * * @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, Doc contextDoc) { this.optionProvider = optionProvider; this.collectionClassDoc = root.classNamed("java.util.Collection"); this.mapClassDoc = root.classNamed("java.util.Map"); this.contextDoc = contextDoc; // to gather the packages containing specified classes, loop thru them and gather // package definitions. User root.specifiedPackages is not safe, since the user // may specify just a list of classes (human users usually don't, but automated tools do) rootClasses = new HashSet<String>(); for (ClassDoc classDoc : root.classes()) { rootClasses.add(classDoc.qualifiedName()); rootClassdocs.put(classDoc.qualifiedName(), classDoc); } Options opt = optionProvider.getGlobalOptions(); if (opt.compact) { linePrefix = ""; linePostfix = ""; } else { linePrefix = "\t"; linePostfix = "\n"; } }
/** Print a class's relations */ public void printRelations(ClassDoc c) { Options opt = optionProvider.getOptionsFor(c); if (hidden(c) || c.name() .equals("")) // avoid phantom classes, they may pop up when the source uses annotations return; String className = c.toString(); // Print generalization (through the Java superclass) Type s = c.superclassType(); if (s != null && !s.toString().equals("java.lang.Object") && !c.isEnum() && !hidden(s.asClassDoc())) { ClassDoc sc = s.asClassDoc(); w.println( "\t//" + c + " extends " + s + "\n" + "\t" + relationNode(sc) + " -> " + relationNode(c) + " [dir=back,arrowtail=empty];"); getClassInfo(className) .addRelation(sc.toString(), RelationType.EXTENDS, RelationDirection.OUT); getClassInfo(sc.toString()) .addRelation(className, RelationType.EXTENDS, RelationDirection.IN); } // Print generalizations (through @extends tags) for (Tag tag : c.tags("extends")) if (!hidden(tag.text())) { ClassDoc from = c.findClass(tag.text()); w.println( "\t//" + c + " extends " + tag.text() + "\n" + "\t" + relationNode(from, tag.text()) + " -> " + relationNode(c) + " [dir=back,arrowtail=empty];"); getClassInfo(className) .addRelation(tag.text(), RelationType.EXTENDS, RelationDirection.OUT); getClassInfo(tag.text()).addRelation(className, RelationType.EXTENDS, RelationDirection.IN); } // Print realizations (Java interfaces) for (Type iface : c.interfaceTypes()) { ClassDoc ic = iface.asClassDoc(); if (!hidden(ic)) { w.println( "\t//" + c + " implements " + ic + "\n\t" + relationNode(ic) + " -> " + relationNode(c) + " [dir=back,arrowtail=empty,style=dashed];"); getClassInfo(className) .addRelation(ic.toString(), RelationType.IMPLEMENTS, RelationDirection.OUT); getClassInfo(ic.toString()) .addRelation(className, RelationType.IMPLEMENTS, RelationDirection.IN); } } // Print other associations allRelation(opt, RelationType.ASSOC, c); allRelation(opt, RelationType.NAVASSOC, c); allRelation(opt, RelationType.HAS, c); allRelation(opt, RelationType.NAVHAS, c); allRelation(opt, RelationType.COMPOSED, c); allRelation(opt, RelationType.NAVCOMPOSED, c); allRelation(opt, RelationType.DEPEND, c); }
/** * Return the full name of a relation's node. This may involve appending the port :p for the * standard nodes whose outline is rendered through an inner table. */ private String relationNode(ClassDoc c) { Options opt = optionProvider.getOptionsFor(c); String name = getNodeName(c); return name + opt.shape.landingPort(); }
/** * Prints the class if needed. * * <p>A class is a rootClass if it's included among the classes returned by RootDoc.classes(), * this information is used to properly compute relative links in diagrams for UMLDoc */ public String printClass(ClassDoc c, boolean rootClass) { ClassInfo ci; boolean toPrint; Options opt = optionProvider.getOptionsFor(c); String className = c.toString(); if ((ci = getClassInfo(className)) != null) toPrint = !ci.nodePrinted; else { toPrint = true; ci = newClassInfo(className, true, hidden(c)); } if (toPrint && !hidden(c) && (!c.isEnum() || opt.showEnumerations)) { // Associate classname's alias String r = className; w.println("\t// " + r); // Create label w.print("\t" + ci.name + " [label="); boolean showMembers = (opt.showAttributes && c.fields().length > 0) || (c.isEnum() && opt.showEnumConstants && c.enumConstants().length > 0) || (opt.showOperations && c.methods().length > 0) || (opt.showConstructors && c.constructors().length > 0); externalTableStart(opt, c.qualifiedName(), classToUrl(c, rootClass)); // Calculate the number of innerTable rows we will emmit int nRows = 1; if (showMembers) { if (opt.showAttributes) nRows++; else if (!c.isEnum() && (opt.showConstructors || opt.showOperations)) nRows++; if (c.isEnum() && opt.showEnumConstants) nRows++; if (!c.isEnum() && (opt.showConstructors || opt.showOperations)) nRows++; } firstInnerTableStart(opt, nRows); if (c.isInterface()) tableLine(Align.CENTER, guilWrap(opt, "interface")); if (c.isEnum()) tableLine(Align.CENTER, guilWrap(opt, "enumeration")); stereotype(opt, c, Align.CENTER); Font font = c.isAbstract() && !c.isInterface() ? Font.CLASS_ABSTRACT : Font.CLASS; String qualifiedName = qualifiedName(opt, r); int startTemplate = qualifiedName.indexOf('<'); int idx = 0; if (startTemplate < 0) idx = qualifiedName.lastIndexOf('.'); else idx = qualifiedName.lastIndexOf('.', startTemplate); if (opt.showComment) tableLine(Align.LEFT, htmlNewline(escape(c.commentText())), opt, Font.CLASS); else if (opt.postfixPackage && idx > 0 && idx < (qualifiedName.length() - 1)) { String packageName = qualifiedName.substring(0, idx); String cn = className.substring(idx + 1); tableLine(Align.CENTER, escape(cn), opt, font); tableLine(Align.CENTER, packageName, opt, Font.PACKAGE); } else { tableLine(Align.CENTER, escape(qualifiedName), opt, font); } tagvalue(opt, c); firstInnerTableEnd(opt, nRows); /* * Warning: The boolean expressions guarding innerTableStart() * in this block, should match those in the code block above * marked: "Calculate the number of innerTable rows we will emmit" */ if (showMembers) { if (opt.showAttributes) { innerTableStart(); FieldDoc[] fields = c.fields(); // if there are no fields, print an empty line to generate proper HTML if (fields.length == 0) tableLine(Align.LEFT, ""); else attributes(opt, c.fields()); innerTableEnd(); } else if (!c.isEnum() && (opt.showConstructors || opt.showOperations)) { // show an emtpy box if we don't show attributes but // we show operations innerTableStart(); tableLine(Align.LEFT, ""); innerTableEnd(); } if (c.isEnum() && opt.showEnumConstants) { innerTableStart(); FieldDoc[] ecs = c.enumConstants(); // if there are no constants, print an empty line to generate proper HTML if (ecs.length == 0) { tableLine(Align.LEFT, ""); } else { for (FieldDoc fd : c.enumConstants()) { tableLine(Align.LEFT, fd.name()); } } innerTableEnd(); } if (!c.isEnum() && (opt.showConstructors || opt.showOperations)) { innerTableStart(); boolean printedLines = false; if (opt.showConstructors) printedLines |= operations(opt, c.constructors()); if (opt.showOperations) printedLines |= operations(opt, c.methods()); if (!printedLines) // if there are no operations nor constructors, // print an empty line to generate proper HTML tableLine(Align.LEFT, ""); innerTableEnd(); } } externalTableEnd(); w.print(", URL=\"" + classToUrl(c, rootClass) + "\""); nodeProperties(opt); // If needed, add a note for this node int ni = 0; for (Tag t : c.tags("note")) { String noteName = "n" + ni + "c" + ci.name; w.print("\t// Note annotation\n"); w.print("\t" + noteName + " [label="); externalTableStart( UmlGraph.getCommentOptions(), c.qualifiedName(), classToUrl(c, rootClass)); innerTableStart(); tableLine( Align.LEFT, htmlNewline(escape(t.text())), UmlGraph.getCommentOptions(), Font.CLASS); innerTableEnd(); externalTableEnd(); nodeProperties(UmlGraph.getCommentOptions()); w.print("\t" + noteName + " -> " + relationNode(c) + "[arrowhead=none];\n"); ni++; } ci.nodePrinted = true; } return ci.name; }
/** Return true if the class name is associated to an hidden class or matches a hide expression */ private boolean hidden(String s) { ClassInfo ci = getClassInfo(s); Options opt = optionProvider.getOptionsFor(s); if (ci != null) return ci.hidden || opt.matchesHideExpression(s); else return opt.matchesHideExpression(s); }