001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019package org.apache.reef.tang.util.walk.graphviz; 020 021import org.apache.reef.tang.Configuration; 022import org.apache.reef.tang.types.ClassNode; 023import org.apache.reef.tang.types.NamedParameterNode; 024import org.apache.reef.tang.types.Node; 025import org.apache.reef.tang.types.PackageNode; 026import org.apache.reef.tang.util.walk.AbstractClassHierarchyNodeVisitor; 027import org.apache.reef.tang.util.walk.EdgeVisitor; 028import org.apache.reef.tang.util.walk.Walk; 029 030/** 031 * Build a Graphviz representation of the configuration graph. 032 */ 033public final class GraphvizConfigVisitor 034 extends AbstractClassHierarchyNodeVisitor implements EdgeVisitor<Node> { 035 036 /** 037 * Legend for the configuration graph in Graphviz format. 038 */ 039 private static final String LEGEND = 040 " subgraph cluster_legend {\n" 041 + " label=\"Legend\";\n" 042 + " shape=box;\n" 043 + " subgraph cluster_1 {\n" 044 + " style=invis; label=\"\";\n" 045 + " ex1l [shape=point, label=\"\"]; ex1r [shape=point, label=\"\"];\n" 046 + " ex2l [shape=point, label=\"\"]; ex2r [shape=point, label=\"\"];\n" 047 + " ex3l [shape=point, label=\"\"]; ex3r [shape=point, label=\"\"];\n" 048 + " ex4l [shape=point, label=\"\"]; ex4r [shape=point, label=\"\"];\n" 049 + " ex1l -> ex1r [style=solid, dir=back, arrowtail=diamond, label=\"contains\"];\n" 050 + " ex2l -> ex2r [style=dashed, dir=back, arrowtail=empty, label=\"implements\"];\n" 051 + " ex3l -> ex3r [style=\"dashed,bold\", dir=back, arrowtail=empty, label=\"external\"];\n" 052 + " ex4l -> ex4r [style=solid, dir=back, arrowtail=normal, label=\"binds\"];\n" 053 + " }\n" 054 + " subgraph cluster_2 {\n" 055 + " style=invis; label=\"\";\n" 056 + " PackageNode [shape=folder];\n" 057 + " ClassNode [shape=box];\n" 058 + " Singleton [shape=box, style=filled];\n" 059 + " NamedParameterNode [shape=oval];\n" 060 + " }\n" 061 + " }\n"; 062 063 /** 064 * Accumulate string representation of the graph here. 065 */ 066 private final transient StringBuilder graphStr = new StringBuilder( 067 "digraph ConfigMain {\n" 068 + " rankdir=LR;\n"); 069 070 /** 071 * Entire TANG configuration object. 072 */ 073 private final transient Configuration config; 074 075 /** 076 * If true, plot IS-A edges for know implementations. 077 */ 078 private final transient boolean showImpl; 079 080 /** 081 * Create a new TANG configuration visitor. 082 * 083 * @param config Entire TANG configuration object. 084 * @param showImpl If true, plot IS-A edges for know implementations. 085 * @param showLegend If true, add legend to the plot. 086 */ 087 public GraphvizConfigVisitor(final Configuration config, 088 final boolean showImpl, final boolean showLegend) { 089 super(); 090 this.config = config; 091 this.showImpl = showImpl; 092 if (showLegend) { 093 this.graphStr.append(LEGEND); 094 } 095 } 096 097 /** 098 * Produce a Graphviz DOT string for a given TANG configuration. 099 * 100 * @param config TANG configuration object. 101 * @param showImpl If true, plot IS-A edges for know implementations. 102 * @param showLegend If true, add legend to the plot. 103 * @return configuration graph represented as a string in Graphviz DOT format. 104 */ 105 public static String getGraphvizString(final Configuration config, 106 final boolean showImpl, final boolean showLegend) { 107 final GraphvizConfigVisitor visitor = new GraphvizConfigVisitor(config, showImpl, showLegend); 108 final Node root = config.getClassHierarchy().getNamespace(); 109 Walk.preorder(visitor, visitor, root); 110 return visitor.toString(); 111 } 112 113 /** 114 * @return TANG configuration represented as a Graphviz DOT string. 115 */ 116 @Override 117 public String toString() { 118 return this.graphStr.toString() + "}\n"; 119 } 120 121 /** 122 * Process current class configuration node. 123 * 124 * @param node Current configuration node. 125 * @return true to proceed with the next node, false to cancel. 126 */ 127 @Override 128 public boolean visit(final ClassNode<?> node) { 129 130 this.graphStr 131 .append(" ") 132 .append(node.getName()) 133 .append(" [label=\"") 134 .append(node.getName()) 135 .append("\", shape=box") 136// .append(config.isSingleton(node) ? ", style=filled" : "") 137 .append("];\n"); 138 139 final ClassNode<?> boundImplNode = config.getBoundImplementation(node); 140 if (boundImplNode != null) { 141 this.graphStr 142 .append(" ") 143 .append(node.getName()) 144 .append(" -> ") 145 .append(boundImplNode.getName()) 146 .append(" [style=solid, dir=back, arrowtail=normal];\n"); 147 } 148 149 for (final Object implNodeObj : node.getKnownImplementations()) { 150 final ClassNode<?> implNode = (ClassNode<?>) implNodeObj; 151 if (implNode != boundImplNode && implNode != node 152 && (implNode.isExternalConstructor() || this.showImpl)) { 153 this.graphStr 154 .append(" ") 155 .append(node.getName()) 156 .append(" -> ") 157 .append(implNode.getName()) 158 .append(" [style=\"dashed") 159 .append(implNode.isExternalConstructor() ? ",bold" : "") 160 .append("\", dir=back, arrowtail=empty];\n"); 161 } 162 } 163 164 return true; 165 } 166 167 /** 168 * Process current package configuration node. 169 * 170 * @param node Current configuration node. 171 * @return true to proceed with the next node, false to cancel. 172 */ 173 @Override 174 public boolean visit(final PackageNode node) { 175 if (!node.getName().isEmpty()) { 176 this.graphStr 177 .append(" ") 178 .append(node.getName()) 179 .append(" [label=\"") 180 .append(node.getFullName()) 181 .append("\", shape=folder];\n"); 182 } 183 return true; 184 } 185 186 /** 187 * Process current configuration node for the named parameter. 188 * 189 * @param node Current configuration node. 190 * @return true to proceed with the next node, false to cancel. 191 */ 192 @Override 193 public boolean visit(final NamedParameterNode<?> node) { 194 this.graphStr 195 .append(" ") 196 .append(node.getName()) 197 .append(" [label=\"") 198 .append(node.getSimpleArgName()) // parameter type, e.g. "Integer" 199 .append("\\n") 200 .append(node.getName()) // short name, e.g. "NumberOfThreads" 201 .append(" = ") 202 .append(config.getNamedParameter(node)) // bound value, e.g. "16" 203 .append("\\n(default = ") 204 .append(instancesToString(node.getDefaultInstanceAsStrings())) // default value, e.g. "4" 205 .append(")\", shape=oval];\n"); 206 return true; 207 } 208 209 private String instancesToString(final String[] s) { 210 if (s.length == 0) { 211 return "null"; 212 } else if (s.length == 1) { 213 return s[0]; 214 } else { 215 final StringBuffer sb = new StringBuffer("[" + s[0]); 216 for (int i = 1; i < s.length; i++) { 217 sb.append(","); 218 sb.append(s[i]); 219 } 220 sb.append("]"); 221 return sb.toString(); 222 } 223 } 224 225 /** 226 * Process current edge of the configuration graph. 227 * 228 * @param nodeFrom Current configuration node. 229 * @param nodeTo Destination configuration node. 230 * @return true to proceed with the next node, false to cancel. 231 */ 232 @Override 233 public boolean visit(final Node nodeFrom, final Node nodeTo) { 234 if (!nodeFrom.getName().isEmpty()) { 235 this.graphStr 236 .append(" ") 237 .append(nodeFrom.getName()) 238 .append(" -> ") 239 .append(nodeTo.getName()) 240 .append(" [style=solid, dir=back, arrowtail=diamond];\n"); 241 } 242 return true; 243 } 244}