/**
  * 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);
       }
     }
   }
 }
Exemple #5
0
  /** 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);
 }