Package com.puppetlabs.geppetto.graph.dependency

Source Code of com.puppetlabs.geppetto.graph.dependency.DependencyDataCalculator$ModuleNodeData

/**
* Copyright (c) 2013 Puppet Labs, Inc. and other contributors, as listed below.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*   Puppet Labs
*/
package com.puppetlabs.geppetto.graph.dependency;

import static com.puppetlabs.geppetto.forge.Forge.METADATA_JSON_NAME;
import static com.puppetlabs.geppetto.forge.Forge.MODULEFILE_NAME;
import static com.puppetlabs.geppetto.forge.model.ModuleName.safeName;
import static com.puppetlabs.geppetto.forge.model.ModuleName.safeOwner;

import java.io.File;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.emf.ecore.EClass;

import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.puppetlabs.geppetto.diagnostic.Diagnostic;
import com.puppetlabs.geppetto.diagnostic.DiagnosticType;
import com.puppetlabs.geppetto.forge.model.Dependency;
import com.puppetlabs.geppetto.forge.model.Metadata;
import com.puppetlabs.geppetto.forge.model.ModuleName;
import com.puppetlabs.geppetto.graph.DependencyGraphProducer;
import com.puppetlabs.geppetto.graph.IHrefProducer;
import com.puppetlabs.geppetto.pp.PPPackage;
import com.puppetlabs.geppetto.pp.pptp.PPTPPackage;
import com.puppetlabs.geppetto.semver.Version;
import com.puppetlabs.geppetto.semver.VersionRange;
import com.puppetlabs.geppetto.validation.runner.AllModuleReferences;
import com.puppetlabs.geppetto.validation.runner.AllModuleReferences.Export;
import com.puppetlabs.geppetto.validation.runner.BuildResult;
import com.puppetlabs.geppetto.validation.runner.MetadataInfo;
import com.puppetlabs.graph.ICancel;
import com.puppetlabs.graph.ICancel.NullIndicator;
import com.puppetlabs.graph.IVertex;
import com.puppetlabs.graph.dot.DotRenderer;
import com.puppetlabs.graph.elements.Edge;
import com.puppetlabs.graph.elements.GraphElement;
import com.puppetlabs.graph.elements.RootGraph;
import com.puppetlabs.graph.elements.Vertex;
import com.puppetlabs.graph.graphcss.GraphCSS;
import com.puppetlabs.graph.graphcss.IFunctionFactory;
import com.puppetlabs.graph.graphcss.StyleSet;
import com.puppetlabs.graph.style.IStyle;
import com.puppetlabs.graph.style.IStyleFactory;
import com.puppetlabs.graph.style.labels.LabelRow;

/**
* Calculates dependency data for a set of modules. The logic handles:
* <ul>
* <li>Existing and non existing modules</li>
* <li>Resolved dependencies (stated in Modulefiles)</li>
* <li>Unresolved dependencies (dependency on existing module, but unsatisfied version range).</li>
* <li>Unresolved dependencies to missing modules (all versions aggregated into one 'missing module')</li>
* <li>Implied 'dependencies' existing due to use of export from module</li>
* <li>Self references are included in the result</li>
* </ul>
* Each module is represented by an instance of {@link ModuleNodeData}, and each relationship is represented
* by an {@link ModuleEdge}. An edge is further described by an {@link EdgeType} as well as the implied meaning
* of the given set of non null fields. It is up to the user of this data to decide how to represent multiple
* edges, although it is suggested that a combination of resolved/implied is to be treated as "normal", and
* unresolved/implied as "unresolved", if a dependency was not stated there may be only "implied" edges.
*
* For all unresolved references (not found anywhere), the edge leads to a "null" {@link ModuleEdge}.
*
*/
public class DependencyDataCalculator implements DependencyGraphStyles, DependencyGraphProducer {

  public static enum EdgeType {
    UNRESOLVED, RESOLVED, IMPLIED;
  }

  public static class ModuleEdge {
    final ModuleNodeData from;

    final ModuleNodeData to;

    final EdgeType edgeType;

    final Iterable<Export> imported;

    final Iterable<String> unresolved;

    /**
     * null if edgeType == IMPLIED
     */
    Dependency dependency;

    private Vertex vertex;

    /**
     * A (sub) collection of imported Exports that are ambiguous. May be null.
     */
    final Collection<Export> ambiguities;

    /**
     * @param from
     * @param implied
     * @param unresolvedNames
     */
    public ModuleEdge(ModuleNodeData from, EdgeType implied, Iterable<String> unresolvedNames) {
      this(from, null, implied, null, null, unresolvedNames, null);
    }

    public ModuleEdge(ModuleNodeData from, ModuleNodeData to, EdgeType type, Dependency d) {
      this(from, to, type, d, null, null, null);
    }

    public ModuleEdge(ModuleNodeData from, ModuleNodeData to, EdgeType type, Dependency d,
        Iterable<Export> imported, Iterable<String> unresolved, Collection<Export> ambiguities) {
      this.from = from;
      this.to = to;
      this.edgeType = type;
      this.dependency = d;
      this.imported = imported;
      this.unresolved = unresolved;
      this.ambiguities = ambiguities;
      from.addOutgoing(to, this);
    }

    /**
     * @param from
     * @param to
     * @param type
     * @param imported
     */
    public ModuleEdge(ModuleNodeData from, ModuleNodeData to, EdgeType type, Iterable<Export> imported,
        Collection<Export> ambiguities) {
      this(from, to, type, null, imported, null, ambiguities);
    }

    public Vertex getVertex() {
      return vertex;
    }

    /**
     * @param edgeVertex
     */
    public void setVertex(Vertex edgeVertex) {
      this.vertex = edgeVertex;
    }
  }

  public static class ModuleNodeData {
    public static ModuleNodeData existing(ModuleName name, Version version, boolean isNode, String href) {
      return new ModuleNodeData(name, version, isNode
          ? ModuleType.PPNODE
          : ModuleType.MODULE, href);
    }

    /**
     * Formats the label of the node as [NameOf(root.parent)/]NameOf(root)
     *
     * @param root
     * @return
     */
    public static ModuleNodeData root(File root) {
      File parent = root.getParentFile();
      ModuleName moduleName;
      if(parent != null)
        moduleName = ModuleName.create(safeOwner(parent.getName()), safeName(root.getName(), false), false);
      else
        moduleName = ModuleName.create("root", ModuleName.safeName(root.getName(), false), false);
      return new ModuleNodeData(moduleName, null, ModuleType.ROOT, "");
    }

    public static ModuleNodeData unresolved(ModuleName name) {
      return new ModuleNodeData(name, null, ModuleType.NON_EXISTING, "");
    }

    IVertex vertex;

    ModuleName name;

    Version version;

    ModuleType moduleType;

    String href;

    boolean marked;

    Multimap<ModuleNodeData, ModuleEdge> outgoing;

    private int ambiguous;

    private ModuleNodeData(ModuleName name, Version version, ModuleType type, String href) {
      this.name = name;
      this.version = version;
      this.moduleType = type;
      this.outgoing = ArrayListMultimap.create();
      this.href = href;
      this.ambiguous = 0;
    }

    private void addOutgoing(ModuleNodeData to, ModuleEdge edge) {
      // filter out direct self references
      if(to == this)
        return;
      outgoing.put(to, edge);
    }

    public boolean exists() {
      return moduleType != ModuleType.NON_EXISTING;
    }

    private IVertex getVertex() {
      return vertex;
    }

    public boolean isAmbiguous() {
      return this.ambiguous > 0;
    }

    public boolean isNode() {
      return moduleType == ModuleType.PPNODE;
    }

    /**
     * mark all reachable module nodes.
     */
    public void mark() {
      if(marked)
        return;
      marked = true;
      for(ModuleNodeData reachable : outgoing.keySet())
        if(reachable != null) // i.e. if resolved
          reachable.mark();
    }

    /**
     * @param b
     */
    public void setAmbiguous(int counter) {
      this.ambiguous = counter;
    }

    private void setVertex(IVertex v) {
      vertex = v;
    }

  }

  public static enum ModuleType {
    ROOT, PPNODE, MODULE, NON_EXISTING, PPTP;
  }

  @Inject
  private IStyleFactory styles;

  @Inject
  private DependencyGraphTheme theme;

  @Inject
  private DotRenderer dotRenderer;

  @Inject
  private IHrefProducer hrefProducer;

  @Inject
  private GraphCSS instanceRules;

  private Map<Object, ModuleNodeData> moduleNodeData = Maps.newHashMap();

  private List<ModuleEdge> moduleEdges = Lists.newArrayList();

  private ModuleNodeData nonModularNode;

  private ModuleNodeData pptpNode;

  private File root;

  public static final DiagnosticType DEPENDENCY_DATA_CALCULATOR = new DiagnosticType(
    "DEPENDENCY_DATA_CALCULATOR", DependencyDataCalculator.class.getName());

  private ModuleNodeData _file2Module(File f, Map<File, ModuleNodeData> index) {
    String path = f.getPath();
    ModuleNodeData md;
    if(path.endsWith(MODULEFILE_NAME) || path.endsWith(METADATA_JSON_NAME))
      md = index.get(f);
    else {
      md = index.get(new File(path + '/' + MODULEFILE_NAME));
      if(md == null)
        md = index.get(new File(path + '/' + METADATA_JSON_NAME));
    }
    return md;
  }

  private void addEdgeHref(ModuleNodeData a, ModuleNodeData b, GraphElement... elements) {
    String aId = idOfVertex(a);
    String hrefForEdge = null;
    String bId = b == null || b == pptpNode
        ? ""
        : idOfVertex(b);
    boolean splitEdge = elements.length > 1;
    if(b == null)
      hrefForEdge = hrefProducer.hrefForEdgeToUnresolved(aId, splitEdge);
    else if(b == pptpNode)
      hrefForEdge = hrefProducer.hrefForEdgeToPptp(aId);
    else
      hrefForEdge = hrefProducer.hrefForEdge(aId, bId, splitEdge);
    IStyle<String> hrefStyle = styles.href(hrefForEdge);
    for(GraphElement e : elements)
      e.setStyles(StyleSet.withStyle(hrefStyle));
  }

  private void addStyleClasses(List<String> classes, GraphElement... elements) {
    for(GraphElement e : elements)
      e.addAllStyleClasses(classes);
  }

  /**
   * Install tooltip as instance style for edge.
   *
   * @param tooltip
   * @param edges
   */
  private void addTooltip(String tooltip, GraphElement... elements) {
    for(GraphElement e : elements)
      e.setStyles(StyleSet.withStyle(styles.tooltip(tooltip)));
  }

  /**
   * Calculates dependency data and returns a map from Modulefiles to ModuleNodeData.
   *
   * @param moduleData
   * @param exportData
   * @return
   */
  public Map<File, ModuleNodeData> calculateDependencyData(File root, Multimap<ModuleName, MetadataInfo> moduleData,
      AllModuleReferences exportData) {

    // create node data for all existing modules and check if there are ambiguities
    Multimap<ModuleName, ModuleNodeData> processedModules = ArrayListMultimap.create();
    for(MetadataInfo mi : moduleData.values()) {
      Metadata m = mi.getMetadata();
      ModuleNodeData mnd = ModuleNodeData.existing(m.getName(), m.getVersion(), mi.isRole(), toHREF_URL(mi));
      moduleNodeData.put(mi, mnd);
      processedModules.put(m.getName(), mnd);
    }
    for(ModuleName key : processedModules.keySet()) {
      Collection<ModuleNodeData> modules = processedModules.get(key);
      if(modules.size() > 1) {
        int counter = 0;
        for(ModuleNodeData mnd : modules)
          mnd.setAmbiguous(++counter);
      }
    }
    // moduleData is keyed by "fullName" to lower case

    // Create pseudo module for non modular content
    nonModularNode = ModuleNodeData.root(root);
    pptpNode = new ModuleNodeData(ModuleName.create("root", "puppet", false), null, ModuleType.PPTP, ""); // will not be rendered

    // create module nodes for missing (unsatisfied dependencies)
    // unless dependency is to represented module name, but version is not matched (in which case
    // the unmatched but existing node is used.
    // if a dependency appears more than once, use the first (skip the rest with same name)
    for(MetadataInfo mi : moduleData.values()) {
      final ModuleNodeData a = moduleNodeData.get(mi);
      Set<ModuleName> processed = Sets.newHashSet();
      for(Dependency d : mi.getUnresolvedDependencies()) {
        final ModuleName name = d.getName();
        if(!processed.add(name))
          continue;
        Collection<MetadataInfo> existingVersions = moduleData.get(name);
        ModuleNodeData b = null;
        if(existingVersions == null || existingVersions.size() < 1) {
          b = moduleNodeData.get(name);
          if(b == null) {
            // need a node for the missing module
            b = ModuleNodeData.unresolved(name);
            // need to generate one that can not be found if name is null
            moduleNodeData.put(name == null
                ? ModuleName.create("no", "name", false)
                : name, b);
          }
        }
        else {
          // pick (one of) the existing versions (it is actually illegal to have more
          // than one, so just pick the first one).
          MetadataInfo first = Iterables.get(existingVersions, 0);
          b = moduleNodeData.get(first);
        }
        createUnresolvedEdge(a, b, d);
      }
      // Add edges for all resolved dependencies
      for(MetadataInfo.Resolution r : mi.getResolvedDependencies()) {
        createResolvedEdge(a, moduleNodeData.get(r.metadata), r.dependency);
      }
    }
    Map<File, ModuleNodeData> fileIndex = Maps.newHashMap();
    for(Map.Entry<Object, ModuleNodeData> m : moduleNodeData.entrySet()) {
      if(!(m.getKey() instanceof ModuleName)) {
        MetadataInfo mi = (MetadataInfo) m.getKey();
        fileIndex.put(mi.getFile(), m.getValue());
      }
    }
    Map<File, Multimap<File, Export>> ambiguities = exportData.getAmbiguityMap();
    for(Map.Entry<File, Multimap<File, AllModuleReferences.Export>> x : exportData.getImportMap().entrySet()) {
      // get the imported
      File fromFile = x.getKey();
      Multimap<File, Export> m = x.getValue();

      // get any ambiguities
      Multimap<File, Export> ambiguitiesForFile = ambiguities.get(fromFile);
      for(File toFile : m.keySet()) {
        createImportEdge(
          file2Module(fromFile, fileIndex), file2Module(toFile, fileIndex), m.get(toFile),
          ambiguitiesForFile != null
              ? ambiguitiesForFile.get(toFile)
              : null);
      }
    }

    for(File fromFile : exportData.getUnresolvedMap().keySet()) {
      createUnresolvedEdge(file2Module(fromFile, fileIndex), exportData.getUnresolvedMap().get(fromFile));

    }
    return fileIndex;
  }

  private List<String> classesFor(ModuleNodeData from, ModuleNodeData to) {
    List<String> styleClasses = Lists.newArrayList();
    if(from != null)
      styleClasses.add("FROM__" + idOfVertex(from));
    if(to != null && to != pptpNode)
      styleClasses.add("TO__" + idOfVertex(to));
    return styleClasses;
  }

  private List<String> classesOfEdge(ModuleEdge edge) {
    return classesFor(edge.from, edge.to);
  }

  private void createImportEdge(ModuleNodeData from, ModuleNodeData to, Iterable<Export> imported,
      Collection<Export> ambiguities) {
    moduleEdges.add(new ModuleEdge(from, to, EdgeType.IMPLIED, imported, ambiguities));

  }

  private void createResolvedEdge(final ModuleNodeData a, final ModuleNodeData b, Dependency d) {
    moduleEdges.add(new ModuleEdge(a, b, EdgeType.RESOLVED, d));
  }

  private void createUnresolvedEdge(ModuleNodeData from, Iterable<String> unresolvedNames) {
    moduleEdges.add(new ModuleEdge(from, EdgeType.IMPLIED, unresolvedNames));

  }

  private void createUnresolvedEdge(final ModuleNodeData a, final ModuleNodeData b, Dependency d) {
    moduleEdges.add(new ModuleEdge(a, b, EdgeType.UNRESOLVED, d));
  }

  private Vertex createVertexForEdge(ModuleEdge me) {
    if(me.imported == null && me.unresolved == null)
      // if neither imported nor unresolved, a vertex is not needed
      return null;

    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append("References from ");
    stringBuilder.append(makeTooltip(me.from, me.to));
    String tooltipString = stringBuilder.toString();
    Vertex edgeVertex = null;
    if(me.imported != null) {
      edgeVertex = new Vertex("", STYLE_CLASS_IMPORTS);
      edgeVertex.setStyles(labelStyleForImported(me.imported, me.ambiguities).add(
        StyleSet.withStyle(styles.tooltip(tooltipString))));
      me.setVertex(edgeVertex);
    }
    else {
      edgeVertex = new Vertex("", STYLE_CLASS_UNRESOLVED_IMPORTS);
      edgeVertex.setStyles(labelStyleForUnresolved(me).add(StyleSet.withStyle(styles.tooltip(tooltipString))));
      me.setVertex(edgeVertex);
    }
    // Set style classes for FROM and TO
    edgeVertex.addAllStyleClasses(classesOfEdge(me));
    return edgeVertex;
  }

  private Vertex createVertexForModuleNode(ModuleNodeData mnd) {
    StringBuilder builder = new StringBuilder();
    builder.append(mnd.name);
    if(mnd.isAmbiguous()) {
      builder.append(" [");
      builder.append(mnd.ambiguous);
      builder.append("]");
    }
    if(mnd.version != null) {
      builder.append("\n");
      builder.append(mnd.version);
    }
    String label = builder.toString();

    String style = mnd.exists()
        ? (mnd.isAmbiguous())
            ? STYLE_CLASS_AMBIGUOUSLY_RESOLVED_MODULE
            : STYLE_CLASS_RESOLVED_MODULE
        : STYLE_CLASS_UNRESOLVED_MODULE;
    Vertex v = new Vertex(label, style);
    v.setStyles(styles.href(mnd.href));
    v.putUserData(IFunctionFactory.ID_KEY, idOfVertex(mnd));
    mnd.setVertex(v);
    return v;
  }

  private Vertex createVertexForPPNodeNode(ModuleNodeData mnd) {
    String label = mnd.name == null
        ? ""
        : mnd.name.toString();
    // no version (user is not aware of one).
    String style = STYLE_CLASS_PPNODE_MODULE;
    Vertex v = new Vertex(label, style);
    v.setStyles(styles.href(mnd.href));
    v.putUserData(IFunctionFactory.ID_KEY, idOfVertex(mnd));
    mnd.setVertex(v);
    return v;
  }

  private Vertex createVertexForRootNode(ModuleNodeData mnd) {
    String label = mnd.name.toString();
    if(mnd.version != null)
      label += "\n" + mnd.version;
    Vertex v = new Vertex(label, STYLE_CLASS_ROOT);
    mnd.setVertex(v);
    return v;
  }

  protected void dumpInfo() {
    for(ModuleNodeData m : moduleNodeData.values()) {
      System.err.printf("module %s(%s) = %s\n", m.name, m.version, m.exists()
          ? "exists"
          : "missing");
      for(ModuleNodeData to : m.outgoing.keySet()) {
        System.err.printf("    -> %s\n", to != null
            ? to.name
            : "null 'to'");
        for(ModuleEdge edge : m.outgoing.get(to)) {
          System.err.printf("        %s, ", edge.edgeType);
          if(edge.imported != null)
            for(Export e : edge.imported)
              System.err.printf("%s ", e.getName());
          if(edge.unresolved != null)
            for(String s : edge.unresolved)
              System.err.printf("(unresolved) %s ", s);
          if(edge.imported != null || edge.unresolved != null)
            System.err.println();
          if(edge.dependency != null)
            if(edge.dependency.getVersionRequirement() == null)
              System.err.println(">= 0");
            else
              System.err.printf("%s\n", edge.dependency.getVersionRequirement());

        }
      }
    }

  }

  private ModuleNodeData file2Module(File f, Map<File, ModuleNodeData> index) {
    if(f.getPath().equals("_pptp"))
      return pptpNode;
    ModuleNodeData mnd = _file2Module(f, index);
    return mnd != null
        ? mnd
        : nonModularNode;
  }

  public String getVersionLabel(ModuleEdge edge) {
    VersionRange vreq = edge.dependency.getVersionRequirement();
    if(vreq == null)
      return "unversioned";
    return vreq.toString();

  }

  /**
   * Produce id on the form NAME[@ambiguityIdx] e.g. Foo, Bar-1, Bar-2
   *
   * @param mnd
   * @return
   */
  private String idOfVertex(ModuleNodeData mnd) {
    StringBuilder idBuilder = new StringBuilder();
    idBuilder.append(mnd.name);
    if(mnd.isAmbiguous()) {
      idBuilder.append("__");
      idBuilder.append(mnd.ambiguous);
    }
    return idBuilder.toString();

  }

  private String labelOfType(EClass clazz) {
    if(clazz == PPPackage.Literals.DEFINITION)
      return "define";
    if(clazz == PPPackage.Literals.HOST_CLASS_DEFINITION)
      return "class";
    if(clazz == PPPackage.Literals.NODE_DEFINITION)
      return "node";
    if(clazz == PPTPPackage.Literals.FUNCTION)
      return "function";
    if(clazz == PPTPPackage.Literals.TYPE)
      return "type";
    return clazz.getName(); // give up, but output something (should not really happen)
  }

  private StyleSet labelStyleForImported(Iterable<Export> imports, Collection<Export> ambiguities) {
    List<LabelRow> labelRows = Lists.newArrayList();
    Export[] sortedImports = Iterables.toArray(imports, Export.class);
    Arrays.sort(sortedImports, new Comparator<Export>() {

      @Override
      public int compare(Export o1, Export o2) {
        // they are not supposed to be null, but just to make sure we don't get a NPE
        // because of a bug elsewhere
        if(o1 == null || o2 == null)
          return 0;
        return o1.getName().compareTo(o2.getName());
      }

    });
    for(Export a : sortedImports) {
      if(a == null) {
        System.err.println("Null Export found");
        continue;
      }
      final boolean ambiguous = ambiguities == null
          ? false
          : ambiguities.contains(a);
      labelRows.add(styles.labelRow(STYLE__IMPORT_ROW, //
        styles.labelCell( //
          ambiguous
              ? STYLE__IMPORT_AMBIGUOUS_NAME_CELL
              : STYLE__IMPORT_NAME_CELL, //
          a.getName()).withStyle(//
          styles.href(toHREF_URL(a))), //
        styles.labelCell(STYLE__IMPORT_TYPE_CELL, labelOfType(a.getEClass()))));
    }
    return StyleSet.withStyle(styles.labelFormat(styles.labelTable(
      STYLE__IMPORT_TABLE, labelRows.toArray(new LabelRow[0]))));

  }

  private StyleSet labelStyleForUnresolved(ModuleEdge me) {
    Iterable<String> unresolved = me.unresolved;

    List<LabelRow> labelRows = Lists.newArrayList();
    String[] sortedImports = Iterables.toArray(unresolved, String.class);
    Arrays.sort(sortedImports, new Comparator<String>() {

      @Override
      public int compare(String o1, String o2) {
        return o1.compareTo(o2);
      }

    });
    for(String a : sortedImports) {
      labelRows.add(styles.labelRow(STYLE__UNRESOLVED_ROW, //
        styles.labelCell(STYLE__UNRESOLVED_NAME_CELL, a).withStyle( //
          styles.href(toHREF_UNRESOLVED(me.from.name, a))) //

      ));
    }
    return StyleSet.withStyle(styles.labelFormat(styles.labelTable(
      STYLE__IMPORT_TABLE, labelRows.toArray(new LabelRow[0]))));

  }

  /**
   * @param a
   * @param b
   * @return
   */
  private String makeTooltip(ModuleNodeData a, ModuleNodeData b) {
    final StringBuilder tooltipBuilder = new StringBuilder();
    tooltipBuilder.append(a.name);
    tooltipBuilder.append(" &#8658; "); // i.e. &rArr; a non defined entity in SVG
    if(b != null)
      tooltipBuilder.append(b.name);
    return tooltipBuilder.toString();
  }

  public void produceGraph(ICancel cancel, String title, File[] roots, OutputStream output, BuildResult buildResult,
      Diagnostic chain) {
    if(title == null)
      title = "Module Dependencies";

    AllModuleReferences all = buildResult.getAllModuleReferences();
    produceGraph(cancel, title, roots, output, all.getRoot(), buildResult.getModuleData(), all, chain);
  }

  /**
   *
   * @param root
   * @param moduleData
   *            Name -> 0* MetadataInfo representing one version of a module with given name
   */
  public void produceGraph(ICancel cancel, String title, File[] roots, OutputStream output, File root,
      Multimap<ModuleName, MetadataInfo> moduleData, AllModuleReferences exportData, Diagnostic chain) {

    if(cancel == null)
      cancel = new NullIndicator();

    this.root = root;

    Map<File, ModuleNodeData> fileMap = calculateDependencyData(root, moduleData, exportData);

    // Render all unless there is a list of roots to include. If roots are given, mark their
    // transitive closures, and tell renderer to render only those that are marked.
    boolean renderAll = roots == null || roots.length == 0;
    if(!renderAll)
      for(File f : roots) {
        ModuleNodeData x = file2Module(f, fileMap);
        if(x != null)
          x.mark();
      }
    else {
      // mark all
      for(ModuleNodeData x : fileMap.values())
        x.mark();
    }
    RootGraph g = produceRootGraph(cancel, title, moduleData, exportData, renderAll, chain);

    instanceRules.addAll(theme.getInstanceRules());
    dotRenderer.write(cancel, output, g, theme.getDefaultRules(), instanceRules);
  }

  /**
   * Produces the graph with vertex and edge data. If renderAll is false, only marked ModuleNodeData
   * will be rendered.
   *
   * @param title
   * @param moduleData
   * @param exportData
   * @param renderAll
   * @return
   */
  private RootGraph produceRootGraph(ICancel cancel, String title, Multimap<ModuleName, MetadataInfo> moduleData,
      AllModuleReferences exportData, boolean renderAll, Diagnostic chain) {

    if(title == null)
      title = "";
    if(cancel == null)
      cancel = new NullIndicator();

    RootGraph g = new RootGraph(title, "RootGraph", "root");

    // Create Graph vertexes, one per existing module
    // and one for the root/non-modular
    for(ModuleNodeData mnd : moduleNodeData.values()) {
      cancel.assertContinue();
      if(renderAll || mnd.marked)
        if(mnd.isNode())
          g.addVertex(createVertexForPPNodeNode(mnd));
        else
          g.addVertex(createVertexForModuleNode(mnd));
    }

    // only draw the root node if it has been marked (incoming dependency), or has outgoing
    // when not rendering all, the non modular must have incoming dependencies to show.
    if(renderAll && nonModularNode.outgoing.size() > 0 || nonModularNode.marked) {
      Vertex v = createVertexForRootNode(nonModularNode);
      g.addVertex(v);
    }

    // Create Vertexes for all Edges that have data
    // (very large labels on edges does not work that well as they often overlap)
    // Skip edges that are from non marked module nodes unless everything is rendered
    for(ModuleEdge me : moduleEdges) {
      // filter out self references
      if(me.from == me.to)
        continue;
      if(renderAll || me.from.marked) {
        cancel.assertContinue();
        Vertex v = createVertexForEdge(me);
        if(v != null) {
          g.addVertex(v);
        }
      }
    }

    // Create Edges
    // If an edge has a vertex, it needs to be drawn as two separate graph edges

    for(ModuleNodeData a : Iterables.concat(moduleNodeData.values(), Collections.singleton(nonModularNode))) {
      if(!(renderAll || a.marked))
        continue; // skip non marked unless all is rendered

      for(ModuleNodeData b : a.outgoing.keySet()) {
        cancel.assertContinue();

        int resolved = 0;
        int unresolved = 0;
        int implied = 0;
        int count = 0;
        List<ModuleEdge> edges = new ArrayList<ModuleEdge>();
        Collection<ModuleEdge> outgoing = a.outgoing.get(b);
        for(ModuleEdge e : outgoing) {
          edges.add(e);
          switch(e.edgeType) {
            case IMPLIED:
              implied++;
              break;
            case UNRESOLVED:
              unresolved++;
              break;
            case RESOLVED:
              resolved++;
              break;
            default:
              throw new IllegalStateException("Illegal edge type found");
          }
          ++count;
        }
        String tooltipString = makeTooltip(a, b);
        List<String> styleClasses = classesFor(a, b);

        // CASE 1
        // A --> [...] --> B
        // A imports from resolved B
        if(count == 2 && resolved == 1 && implied == 1) {
          Edge e1 = new Edge("", STYLE_EDGE__IMPORT, a.getVertex(), edges.get(1).getVertex());
          Edge e2 = new Edge(getVersionLabel(edges.get(0)), STYLE_EDGE__RESOLVED_DEP, //
          edges.get(1).getVertex(), b.getVertex());
          g.addEdge(e1, e2);
          addTooltip(tooltipString, e1, e2);
          addStyleClasses(styleClasses, e1, e2);
          addEdgeHref(a, b, e1, e2);
        }
        else if(count == 1 && implied == 1) {
          // CASE 5
          // A --> [...]
          if(edges.get(0).to == null) {
            Edge e1 = new Edge("unresolved", STYLE_EDGE__UIMPORT, a.getVertex(), edges.get(0).getVertex());
            g.addEdge(e1);
            addTooltip(tooltipString, e1);
            addStyleClasses(styleClasses, e1);
            addEdgeHref(a, b, e1);
          }
          // CASE 8 (reference to pptp - do not draw imports -> pptp part)
          // A -->[...]
          else if(edges.get(0).to == pptpNode) {
            Edge e1 = new Edge("puppet", STYLE_EDGE__IMPORT, a.getVertex(), edges.get(0).getVertex());
            g.addEdge(e1);
            // default tooltip == label
            addStyleClasses(styleClasses, e1);
            addEdgeHref(a, b, e1);

          }
          // CASE 2 (and CASE 0 - self reference)
          // A -->[...] ~~> B
          else {
            if(edges.get(0).from != edges.get(0).to) { // skip self references
              Edge e1 = new Edge("", STYLE_EDGE__IMPORT, a.getVertex(), edges.get(0).getVertex());
              Edge e2 = new Edge(
                "implicit", STYLE_EDGE__IMPLIED_DEP, edges.get(0).getVertex(),
                edges.get(0).to.getVertex());
              g.addEdge(e1, e2);
              addTooltip(tooltipString, e1, e2);
              addStyleClasses(styleClasses, e1, e2);
              addEdgeHref(a, b, e1, e2);
            }
          }
        }
        // CASE 3
        // A --> [...] ~~>(not in range) B
        else if(count == 2 && implied == 1 && unresolved == 1) {
          Edge e1 = new Edge("", STYLE_EDGE__IMPORT, a.getVertex(), edges.get(1).getVertex());
          String label = "implicit\\nunresolved\\n" + getVersionLabel(edges.get(0));
          Edge e2 = new Edge(label, STYLE_EDGE__UNRESOLVED_IMPLIED_DEP, //
          edges.get(1).getVertex(), edges.get(1).to.getVertex());
          g.addEdge(e1, e2);
          addTooltip(tooltipString, e1, e2);
          addStyleClasses(styleClasses, e1, e2);
          addEdgeHref(a, b, e1, e2);
        }

        else if(count == 1 && unresolved == 1) {
          // CASE 7
          // A ~~> B where B is not in range
          if(edges.get(0).to.exists()) {
            String label = "unresolved\\n" + getVersionLabel(edges.get(0));
            Edge e1 = new Edge(label, STYLE_EDGE__UNRESOLVED_IMPLIED_DEP, //
            a.getVertex(), b.getVertex());
            g.addEdge(e1);
            addTooltip(tooltipString, e1);
            addStyleClasses(styleClasses, e1);
            addEdgeHref(a, b, e1);
          }
          // CASE 4
          // A --> B where B does not exist
          else {
            String label = "unresolved\\n" + getVersionLabel(edges.get(0));
            Edge e1 = new Edge(label, STYLE_EDGE__UNRESOLVED_DEP, //
            a.getVertex(), b.getVertex());
            g.addEdge(e1);
            addTooltip(tooltipString, e1);
            addStyleClasses(styleClasses, e1);
            addEdgeHref(a, b, e1);
          }
        }
        // CASE 6
        // A --> B
        // (nothing is imported from B)
        else if(count == 1 && resolved == 1) {
          Edge e1 = new Edge(getVersionLabel(edges.get(0)), STYLE_EDGE__RESOLVED_DEP, //
          a.getVertex(), b.getVertex());
          g.addEdge(e1);
          addTooltip(tooltipString, e1);
          addStyleClasses(styleClasses, e1);
          addEdgeHref(a, b, e1);
        }
        else if(count == implied) {
          // TODO: Figure out what this means. It happens when importing puppetlabs/stdlib into
          // cloudsmith/wordpress:
          //
          // a=thallgren/wordpress
          // b=root/puppet
        }
        else {
          //          StringBuilder builder = new StringBuilder();
          //          builder.append("Internal Error - illegal combination of recorded edges");
          //          builder.append(" count=");
          //          builder.append(count);
          //          builder.append(" resolved=");
          //          builder.append(resolved);
          //          builder.append(" unresolved=");
          //          builder.append(unresolved);
          //          builder.append(" a =");
          //          builder.append(a.name);
          //          builder.append(" b=");
          //          builder.append(b == null
          //              ? "implied"
          //              : b.name);
          //          chain.addChild(new Diagnostic(Diagnostic.ERROR, DEPENDENCY_DATA_CALCULATOR, builder.toString()));
        }

      }
    }
    return g;
  }

  public String toHREF_UNRESOLVED(ModuleName fromModuleName, String name) {
    return hrefProducer.hrefForUnresolved(fromModuleName, name);
  }

  private String toHREF_URL(Export e) {
    return hrefProducer.href(e, this.root);
  }

  public String toHREF_URL(MetadataInfo mi) {
    return hrefProducer.href(mi, this.root);
  }
}
TOP

Related Classes of com.puppetlabs.geppetto.graph.dependency.DependencyDataCalculator$ModuleNodeData

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.