001package stdlib; 002 003import java.io.File; 004import java.io.FileWriter; 005import java.io.IOException; 006import java.io.PrintWriter; 007import java.lang.reflect.AccessibleObject; 008import java.lang.reflect.Field; 009import java.util.ArrayList; 010import java.util.HashMap; 011import java.util.HashSet; 012import java.util.Locale; 013import java.util.Map; 014import java.util.Objects; 015import java.util.Set; 016 017// A fancy way to draw trees: http://stackoverflow.com/questions/10902745/enforcing-horizontal-node-ordering-in-a-dot-tree 018// GraphvizBuilder.nodesToFile (stack.first, "rankdir=\"LR\""); 019 020// TODO: Documentation 021public final class GraphvizBuilder { 022 private static enum Type { DIGRAPH, GRAPH } 023 024 private static ArrayList<String> DOT_EXTENSIONS; 025 static { 026 DOT_EXTENSIONS = new ArrayList<> (); 027 DOT_EXTENSIONS.add ("gv"); 028 DOT_EXTENSIONS.add ("dot"); 029 } 030 private static HashSet<String> DOT_OUTPUT_FORMATS; 031 static { 032 DOT_OUTPUT_FORMATS = new HashSet<> (); 033 DOT_OUTPUT_FORMATS.add ("bmp"); 034 DOT_OUTPUT_FORMATS.add ("cgimage"); 035 DOT_OUTPUT_FORMATS.add ("cmap"); 036 DOT_OUTPUT_FORMATS.add ("cmapx"); 037 DOT_OUTPUT_FORMATS.add ("eps"); 038 DOT_OUTPUT_FORMATS.add ("exr"); 039 DOT_OUTPUT_FORMATS.add ("fig"); 040 DOT_OUTPUT_FORMATS.add ("gif"); 041 DOT_OUTPUT_FORMATS.add ("icns"); 042 DOT_OUTPUT_FORMATS.add ("ico"); 043 DOT_OUTPUT_FORMATS.add ("imap"); 044 DOT_OUTPUT_FORMATS.add ("ismap"); 045 DOT_OUTPUT_FORMATS.add ("jp2"); 046 DOT_OUTPUT_FORMATS.add ("jpe"); 047 DOT_OUTPUT_FORMATS.add ("jpeg"); 048 DOT_OUTPUT_FORMATS.add ("jpg"); 049 DOT_OUTPUT_FORMATS.add ("pct"); 050 DOT_OUTPUT_FORMATS.add ("pdf"); 051 DOT_OUTPUT_FORMATS.add ("pic"); 052 DOT_OUTPUT_FORMATS.add ("pict"); 053 DOT_OUTPUT_FORMATS.add ("png"); 054 DOT_OUTPUT_FORMATS.add ("pov"); 055 DOT_OUTPUT_FORMATS.add ("ps"); 056 DOT_OUTPUT_FORMATS.add ("ps2"); 057 DOT_OUTPUT_FORMATS.add ("psd"); 058 DOT_OUTPUT_FORMATS.add ("sgi"); 059 DOT_OUTPUT_FORMATS.add ("svg"); 060 DOT_OUTPUT_FORMATS.add ("svgz"); 061 DOT_OUTPUT_FORMATS.add ("tga"); 062 DOT_OUTPUT_FORMATS.add ("tif"); 063 DOT_OUTPUT_FORMATS.add ("tiff"); 064 DOT_OUTPUT_FORMATS.add ("tk"); 065 DOT_OUTPUT_FORMATS.add ("vml"); 066 DOT_OUTPUT_FORMATS.add ("vmlz"); 067 } 068 private static ArrayList<String> GRAPHVIZ_POSSIBLE_DOT_LOCATIONS; 069 static { 070 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS = new ArrayList<> (); 071 String os = System.getProperty ("os.name").toLowerCase (); 072 if (os.startsWith ("win")) { 073 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/Program Files (x86)/Graphviz2.38/bin/dot.exe"); // installer 32 bit 074 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/Program Files/Graphviz/bin/dot.exe"); // installer 64 bit 075 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("c:/ProgramData/chocolatey/bin/dot.exe"); // choco 076 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.home") + "/scoop/shims/dot.exe"); // scoop 077 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.dir") + "/lib/graphviz-windows/bin/dot.exe"); 078 } else if (os.startsWith ("mac")) { 079 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/opt/homebrew/bin/dot"); // homebrew 080 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/opt/local/bin/dot"); // macports 081 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/local/bin/dot"); // homebrew 082 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/bin/dot"); // native 083 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (System.getProperty ("user.dir") + "/lib/graphviz-mac/bin/dot"); 084 } else { 085 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/local/bin/dot"); 086 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add ("/usr/bin/dot"); 087 } 088 // Annoyingly, the PATH is usually not what you want when running inside an IDE like eclipse, thus the nonsense above. 089 String[] paths = System.getenv ("PATH").split(File.pathSeparator); 090 for (String p : paths) 091 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (p + File.separator + "dot"); 092 } 093 public static void graphvizAddPossibleDotLocation (String value) { 094 GRAPHVIZ_POSSIBLE_DOT_LOCATIONS.add (value); 095 } 096 097 private static interface Element { 098 public String toString (Type type); 099 } 100 private static String getProperties (String label, String properties) { 101 if (properties == null || "".equals (properties)) { 102 if (label == null || "".equals (label)) { 103 return ";"; 104 } else { 105 return "[label=\"" + label + "\"];"; 106 } 107 } else { 108 if (label == null || "".equals (label)) { 109 return "[" + properties + "];"; 110 } else { 111 return "[label=\"" + label + "\"," + properties + "];"; 112 } 113 } 114 } 115 private static final class Node implements Element { 116 private final String id; 117 private final String properties; 118 public Node (String id, String label, String properties) { 119 if (id == null || "".equals (id)) throw new IllegalArgumentException (); 120 this.id = id; 121 this.properties = getProperties (label, properties); 122 } 123 public String toString (Type type) { 124 return " " + id + properties; 125 } 126 } 127 private static final class Edge implements Element { 128 private final String src; 129 private final String dst; 130 private final String properties; 131 public Edge (String src, String dst, String label, String properties) { 132 if (src == null || "".equals (src)) throw new IllegalArgumentException (); 133 if (dst == null || "".equals (dst)) throw new IllegalArgumentException (); 134 this.src = src; 135 this.dst = dst; 136 this.properties = getProperties (label, properties); 137 } 138 public String toString (Type type) { 139 String arrow = (type == Type.DIGRAPH ? " -> " : " -- "); 140 return " " + src + arrow + dst + properties; 141 } 142 } 143 144 private static String defaultGraphProperties = ""; 145 private static String defaultNodeProperties = ""; 146 private static String defaultEdgeProperties = "fontsize=12"; 147 private static String nullNodeProperties = "shape=\"point\""; 148 private static String nullEdgeProperties = "shape=\"point\""; 149 150 private static int fileCount = 0; 151 public static void binaryHeapToFile (double[] heap, int N) { 152 binaryHeapToFile (heap, N, "heap" + fileCount + ".png"); 153 fileCount++; 154 } 155 public static void binaryHeapToFile (Object[] heap, int N) { 156 binaryHeapToFile (heap, N, "heap" + fileCount + ".png"); 157 fileCount++; 158 } 159 public static void ufToFile (int[] uf) { 160 ufToFile (uf, "uf" + fileCount + ".png"); 161 fileCount++; 162 } 163 /** 164 * Shows simple recursive data structures. 165 * The class of the root parameter is taken to be the must be an element of the recursive node class. 166 * 167 * @param root 168 */ 169 public static void nodesToFile (Object root) { nodesToFile (root, defaultNodeProperties, true); } 170 public static void nodesToFile (Object root, String filename) { nodesToFile (root, filename, defaultNodeProperties, true); } 171 private static void nodesToFile (Object root, String properties, boolean withLabeledEdges) { 172 nodesToFile (root, "nodes" + fileCount + ".png", properties, withLabeledEdges); 173 fileCount++; 174 } 175 176 177 public static void binaryHeapToFile (double[] heap, int N, String filename) { 178 GraphvizBuilder gb = new GraphvizBuilder (); 179 gb.addBinaryHeap (heap, N); 180 gb.toFile (filename, "rankdir=\"BT\""); 181 } 182 public static void binaryHeapToFile (Object[] heap, int N, String filename) { 183 GraphvizBuilder gb = new GraphvizBuilder (); 184 gb.addBinaryHeap (heap, N); 185 gb.toFile (filename, "rankdir=\"BT\""); 186 } 187 public static void ufToFile (int[] uf, String filename) { 188 GraphvizBuilder gb = new GraphvizBuilder (); 189 gb.addUF (uf); 190 gb.toFile (filename, "rankdir=\"BT\""); 191 } 192 public static void nodesToFile (Object root, String filename, String properties, boolean withLabeledEdges) { 193 GraphvizBuilder gb = new GraphvizBuilder (); 194 gb.addNodes (root, withLabeledEdges); 195 gb.toFile (filename, properties); 196 } 197 198 199 private void addBinaryHeap (Object[] heap, int N) { 200 if (heap == null) throw new IllegalArgumentException ("null heap"); 201 if (N<0 || N>heap.length) throw new IllegalArgumentException ("N=" + N + " is not in range [0.." + heap.length + "]"); 202 String prefix = "heap__" + heap.hashCode (); 203 for (int i = 1; i <= N; i++) { 204 addLabeledNodeString (prefix + i, heap[i].toString ()); 205 } 206 for (int i = 2; i <= N; i++) { 207 addEdgeString (prefix + i, prefix + (i/2)); 208 } 209 int logN = 31 - Integer.numberOfLeadingZeros(N); 210 for (int i = N+1; i<2<<logN; i++) { 211 addFromNullEdgeString (prefix + (i/2)); 212 } 213 // if (N%2==0) { 214 // addFromNullEdgeString (prefix + (N/2)); 215 // } 216 } 217 private void addBinaryHeap (double[] heap, int N) { 218 if (heap == null) throw new IllegalArgumentException ("null heap"); 219 if (N<0 || N>heap.length) throw new IllegalArgumentException ("N=" + N + " is not in range [0.." + heap.length + "]"); 220 String prefix = "heap__" + heap.hashCode (); 221 for (int i = 1; i <= N; i++) { 222 addLabeledNodeString (prefix + i, Double.toString(heap[i])); 223 } 224 for (int i = 2; i <= N; i++) { 225 addEdgeString (prefix + i, prefix + (i/2)); 226 } 227 int logN = 31 - Integer.numberOfLeadingZeros(N); 228 for (int i = N+1; i<2<<logN; i++) { 229 addFromNullEdgeString (prefix + (i/2)); 230 } 231 // if (N%2==0) { 232 // addFromNullEdgeString (prefix + (N/2)); 233 // } 234 } 235 private void addUF (int[] uf) { 236 if (uf == null) throw new IllegalArgumentException ("null uf"); 237 String prefix = "uf__" + uf.hashCode (); 238 for (int i = 0; i < uf.length; i++) { 239 addLabeledNodeString (prefix + i, Integer.toString (i)); 240 } 241 for (int i = 0; i < uf.length; i++) { 242 if (i != uf[i]) 243 addEdgeString (prefix + i, prefix + uf[i]); 244 } 245 } 246 private void addNodes (Object root, boolean withLabeledEdges) { 247 if (root == null) { 248 addNullEdge (null); 249 } else { 250 addNodes (root, new HashSet<Object> (), root.getClass (), withLabeledEdges); 251 } 252 } 253 private void addNodes (Object obj, Set<Object> visited, Class<?> nodeClass, boolean withLabeledEdges) { 254 visited.add (obj); 255 Class<?> clazz = obj.getClass (); 256 if (clazz.isArray ()) { 257 throw new Error ("Can't deal with arrays"); 258 } else { 259 StringBuilder labelBuilder = new StringBuilder (); 260 Map<Object,Field> children = new HashMap<> (); 261 262 Field[] fields = clazz.getDeclaredFields (); 263 AccessibleObject.setAccessible (fields, true); 264 boolean first = true; 265 for (int i = 0; i < fields.length; i++) { 266 Field field = fields[i]; 267 Object value; 268 try { value = field.get (obj); } catch (IllegalAccessException e) { throw new Error (e); } 269 if (field.getType () == nodeClass) { 270 if (value == null) { 271 if (withLabeledEdges) 272 addLabeledNullEdge (obj, field.getName ()); 273 else 274 addNullEdge (obj); 275 } else { 276 children.put (value, field); 277 } 278 } else { 279 if (first) { first = false; } else { labelBuilder.append (", "); } 280 labelBuilder.append (value == null ? "null" : value.toString ()); 281 } 282 } 283 addLabeledNode (obj, labelBuilder.toString ()); 284 for (Object child : children.keySet ()) { 285 if (!visited.contains (child)) 286 addNodes (child, visited, nodeClass, withLabeledEdges); 287 } 288 for (Object child : children.keySet ()) { 289 if (withLabeledEdges) 290 addLabeledEdge (obj, child, children.get (child).getName ()); 291 else 292 addEdge (obj, child); 293 } 294 } 295 296 } 297 298 private static String getId (Object o) { 299 return "hash__" + Objects.hashCode (o); 300 } 301 private static String getId (int i) { 302 return Integer.toString (i); 303 } 304 305 private static String getLabel (Object o) { 306 return quote (Objects.toString (o)); 307 } 308 private static String getLabel (String s) { 309 return quote (s); 310 } 311 private static String getLabel (int i) { 312 return Integer.toString (i); 313 } 314 private static String getLabel (double d) { 315 return Double.toString (d); 316 } 317 private static String getLabel (float f) { 318 return Float.toString (f); 319 } 320 //private static final String canAppearUnquotedInLabelChars = " /$&*@#!-+()^%;_[],;.="; 321 private static boolean canAppearUnquotedInLabel (char c) { 322 return true; 323 //return canAppearUnquotedInLabelChars.indexOf (c) != -1 || Character.isLetter (c) || Character.isDigit (c); 324 } 325 private static final String quotable = "\\\"<>{}|"; 326 protected static String quote (String s) { 327 s = unescapeJavaString (s); 328 StringBuffer sb = new StringBuffer (); 329 for (int i = 0, n = s.length (); i < n; i++) { 330 char c = s.charAt (i); 331 if (quotable.indexOf (c) != -1) sb.append ('\\').append (c); 332 else if (canAppearUnquotedInLabel (c)) sb.append (c); 333 else sb.append ("\\\\u").append (Integer.toHexString (c)); 334 } 335 return sb.toString (); 336 } 337 /** 338 * Unescapes a string that contains standard Java escape sequences. 339 * <ul> 340 * <li><strong>\\b \\f \\n \\r \\t \\" \\'</strong> : 341 * BS, FF, NL, CR, TAB, double and single quote.</li> 342 * <li><strong>\\N \\NN \\NNN</strong> : Octal character 343 * specification (0 - 377, 0x00 - 0xFF).</li> 344 * <li><strong>\\uNNNN</strong> : Hexadecimal based Unicode character.</li> 345 * </ul> 346 * 347 * @param st 348 * A string optionally containing standard java escape sequences. 349 * @return The translated string. 350 */ 351 // from http://udojava.com/2013/09/28/unescape-a-string-that-contains-standard-java-escape-sequences/ 352 private static String unescapeJavaString(String st) { 353 StringBuilder sb = new StringBuilder(st.length()); 354 for (int i = 0; i < st.length(); i++) { 355 char ch = st.charAt(i); 356 if (ch == '\\') { 357 char nextChar = (i == st.length() - 1) ? '\\' : st.charAt(i + 1); 358 // Octal escape? 359 if (nextChar >= '0' && nextChar <= '7') { 360 String code = "" + nextChar; 361 i++; 362 if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { 363 code += st.charAt(i + 1); 364 i++; 365 if ((i < st.length() - 1) && st.charAt(i + 1) >= '0' && st.charAt(i + 1) <= '7') { 366 code += st.charAt(i + 1); 367 i++; 368 } 369 } 370 sb.append((char) Integer.parseInt(code, 8)); 371 continue; 372 } 373 switch (nextChar) { 374 case '\\': ch = '\\'; break; 375 case 'b': ch = '\b'; break; 376 case 'f': ch = '\f'; break; 377 case 'n': ch = '\n'; break; 378 case 'r': ch = '\r'; break; 379 case 't': ch = '\t'; break; 380 case '\"': ch = '\"'; break; 381 case '\'': ch = '\''; break; 382 // Hex Unicode: u???? 383 case 'u': 384 if (i >= st.length() - 5) { ch = 'u'; break; } 385 int code = Integer.parseInt(st.substring (i+2,i+6), 16); 386 sb.append(Character.toChars(code)); 387 i += 5; 388 continue; 389 } 390 i++; 391 } 392 sb.append(ch); 393 } 394 return sb.toString(); 395 } 396 397 398 private final ArrayList<Element> elements = new ArrayList<> (); 399 400 public void addNode (Object o) { addLabeledNodeString (getId (o), getLabel (o), defaultNodeProperties); } 401 public void addNode (Object o, String properties) { addLabeledNodeString (getId (o), getLabel (o), properties); } 402 public void addLabeledNode (Object o, String label) { addLabeledNodeString (getId (o), getLabel (label), defaultNodeProperties); } 403 public void addLabeledNode (Object o, String label, String properties) { addLabeledNodeString (getId (o), getLabel (label), properties); } 404 405 public void addNode (int id) { addLabeledNodeString (getId (id), getLabel (id), defaultNodeProperties); } 406 public void addNode (int id, String properties) { addLabeledNodeString (getId (id), getLabel (id), properties); } 407 public void addLabeledNode (int id, String label) { addLabeledNodeString (getId (id), getLabel (label), defaultNodeProperties); } 408 public void addLabeledNode (int id, String label, String properties) { addLabeledNodeString (getId (id), getLabel (label), properties); } 409 410 private void addNodeString (String id) { addLabeledNodeString (id, id, defaultNodeProperties); } 411 private void addNodeString (String id, String properties) { addLabeledNodeString (id, id, properties); } 412 private void addLabeledNodeString (String id, String label) { addLabeledNodeString (id, getLabel (label), defaultNodeProperties); } 413 private void addLabeledNodeString (String id, String label, String properties) { elements.add (new Node (id, getLabel (label), properties)); } 414 415 public void addEdge (Object src, Object dst) { addEdgeString (getId (src), getId (dst), defaultEdgeProperties); } 416 public void addEdge (Object src, Object dst, String properties) { addEdgeString (getId (src), getId (dst), properties); } 417 public void addLabeledEdge (Object src, Object dst, float label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), defaultEdgeProperties); } 418 public void addLabeledEdge (Object src, Object dst, float label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), properties); } 419 public void addLabeledEdge (Object src, Object dst, double label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), defaultEdgeProperties); } 420 public void addLabeledEdge (Object src, Object dst, double label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), properties); } 421 public void addLabeledEdge (Object src, Object dst, int label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getId (label), defaultEdgeProperties); } 422 public void addLabeledEdge (Object src, Object dst, int label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getId (label), properties); } 423 public void addLabeledEdge (Object src, Object dst, String label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label), defaultEdgeProperties); } 424 public void addLabeledEdge (Object src, Object dst, String label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label), properties); } 425 426 public void addEdge (int src, int dst) { addEdgeString (getId (src), getId (dst), defaultEdgeProperties); } 427 public void addEdge (int src, int dst, String properties) { addEdgeString (getId (src), getId (dst), properties); } 428 public void addLabeledEdge (int src, int dst, float label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), defaultEdgeProperties); } 429 public void addLabeledEdge (int src, int dst, float label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), properties); } 430 public void addLabeledEdge (int src, int dst, double label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), defaultEdgeProperties); } 431 public void addLabeledEdge (int src, int dst, double label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), properties); } 432 public void addLabeledEdge (int src, int dst, int label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), defaultEdgeProperties); } 433 public void addLabeledEdge (int src, int dst, int label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label) + "\", length=\"" + getLabel (label), properties); } 434 public void addLabeledEdge (int src, int dst, String label) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label), defaultEdgeProperties); } 435 public void addLabeledEdge (int src, int dst, String label, String properties) { addLabeledEdgeString (getId (src), getId (dst), getLabel (label), properties); } 436 437 private void addEdgeString (String src, String dst) { addEdgeString (src, dst, defaultEdgeProperties); } 438 private void addEdgeString (String src, String dst, String properties) { elements.add (new Edge (src, dst, null, properties)); } 439 private void addLabeledEdgeString (String src, String dst, float label) { addLabeledEdgeString (src, dst, getLabel (label) + ", length=" + getLabel (label), defaultEdgeProperties); } 440 private void addLabeledEdgeString (String src, String dst, float label, String properties) { addLabeledEdgeString (src, dst, getLabel (label) + ", length=" + getLabel (label), properties); } 441 private void addLabeledEdgeString (String src, String dst, double label) { addLabeledEdgeString (src, dst, getLabel (label) + ", length=" + getLabel (label), defaultEdgeProperties); } 442 private void addLabeledEdgeString (String src, String dst, double label, String properties) { addLabeledEdgeString (src, dst, getLabel (label) + ", length=" + getLabel (label), properties); } 443 private void addLabeledEdgeString (String src, String dst, int label) { addLabeledEdgeString (src, dst, getLabel (label) + ", length=" + getLabel (label), defaultEdgeProperties); } 444 private void addLabeledEdgeString (String src, String dst, int label, String properties) { addLabeledEdgeString (src, dst, getLabel (label) + ", length=" + getLabel (label), properties); } 445 private void addLabeledEdgeString (String src, String dst, String label) { addLabeledEdgeString (src, dst, getLabel (label), defaultEdgeProperties); } 446 private void addLabeledEdgeString (String src, String dst, String label, String properties) { elements.add (new Edge (src, dst, getLabel (label), properties)); } 447 448 private int nullId = 0; 449 public void addNullEdge (Object src) { addNullEdgeString(getId (src)); } 450 public void addNullEdgeString (String src) { 451 String id = "null__" + nullId; 452 nullId++; 453 addLabeledNodeString (id, "", nullNodeProperties); 454 if (src != null) addEdgeString (src, id, nullEdgeProperties); 455 } 456 public void addFromNullEdgeString (String src) { 457 String id = "null__" + nullId; 458 nullId++; 459 addLabeledNodeString (id, "", nullNodeProperties); 460 if (src != null) addEdgeString (id, src, nullEdgeProperties); 461 } 462 public void addLabeledNullEdge (Object src, String label) { addLabeledNullEdgeString(getId (src), label); } 463 public void addLabeledNullEdgeString (String src, String label) { 464 String id = "null__" + nullId; 465 nullId++; 466 addLabeledNodeString (id, "", nullNodeProperties); 467 if (src != null) addLabeledEdgeString (src, id, getLabel (label), nullEdgeProperties); 468 } 469 470 private static PrintWriter getPrintWriter (File file) { 471 PrintWriter out; 472 try { 473 out = new PrintWriter (new FileWriter (file)); 474 } catch (IOException e) { 475 throw new Error ("\n!!!! Cannot open " + file + " for writing"); 476 } 477 return out; 478 } 479 private static String getGvExecutable () { 480 String executable = null; 481 for (String s : GRAPHVIZ_POSSIBLE_DOT_LOCATIONS) { 482 if (new File (s).canExecute ()) executable = s; 483 } 484 if (executable == null) 485 throw new Error ("\n!!!! Cannot find dot executable in " + GRAPHVIZ_POSSIBLE_DOT_LOCATIONS 486 + "\n!!!! Check the value of POSSIBLE_DOT_LOCATIONS in " + GraphvizBuilder.class.getCanonicalName ()); 487 return executable; 488 } 489 490 //public void toFileUndirected (boolean keepGvFile, String filename) { toFile (Type.GRAPH, keepGvFile, filename, defaultGraphProperties); } 491 //public void toFileUndirected (boolean keepGvFile, String filename, String properties) { toFile (Type.GRAPH, keepGvFile, filename, properties); } 492 public void toFileUndirected (String filename) { toFile (Type.GRAPH, false, filename, defaultGraphProperties); } 493 public void toFileUndirected (String filename, String properties) { toFile (Type.GRAPH, false, filename, properties); } 494 //public void toFile (boolean keepGvFile, String filename) { toFile (Type.DIGRAPH, keepGvFile, filename, defaultGraphProperties); } 495 //public void toFile (boolean keepGvFile, String filename, String properties) { toFile (Type.DIGRAPH, keepGvFile, filename, properties); } 496 public void toFile (String filename) { toFile (Type.DIGRAPH, false, filename, defaultGraphProperties); } 497 public void toFile (String filename, String properties) { toFile (Type.DIGRAPH, false, filename, properties); } 498 //public void toFile (Type type, boolean keepGvFile, String filename) { toFile (type, keepGvFile, filename, defaultGraphProperties); } 499 private void toFile (Type type, boolean keepGvFile, String filename, String properties) { 500 // keepGvFile = true; 501 // Get basename and ext 502 String basename; 503 String ext; 504 if (filename.indexOf ('.') < 0) { 505 ext = "png"; 506 basename = filename; 507 } else { 508 int period = filename.lastIndexOf ('.'); 509 ext = filename.substring (period + 1); 510 basename = filename.substring (0, period); 511 } 512 if (!new File (basename).isAbsolute ()) { 513 if (dirName == null) setDirName (); 514 basename = dirName + basename; 515 } 516 517 boolean extIsDot = DOT_EXTENSIONS.contains (ext); 518 boolean extIsOut = DOT_OUTPUT_FORMATS.contains (ext); 519 520 if ((!extIsDot) && (!extIsOut)) 521 throw new Error ("\n!!!! unrecognized extension \"" + ext + "\"" 522 + "\n!!!! filename must end in \".ext\", where ext is \"gv\" or one of the following:\n!!!! " + DOT_OUTPUT_FORMATS); 523 524 525 String newBasename = basename; 526 File newFile = new File (newBasename + "." + ext); 527 int suffix = 0; 528 while (newFile.exists()) { 529 suffix++; 530 newBasename = basename + " " + suffix; 531 newFile = new File(newBasename + "." + ext); 532 } 533 basename = newBasename; 534 535 // Create dot input file 536 File gvFile; 537 if (extIsDot) { 538 gvFile = new File (basename + "." + ext); 539 } else { 540 gvFile = new File (basename + ".gv"); 541 } 542 PrintWriter out = getPrintWriter (gvFile); 543 switch (type) { 544 case DIGRAPH: out.print ("digraph"); break; 545 case GRAPH: out.print ("graph"); break; 546 } 547 out.println (" G {"); 548 if (properties != null && !"".equals (properties)) 549 out.println (" graph [" + properties + "];"); 550 for (Element e : elements) 551 out.println (e.toString (type)); 552 out.println ("}"); 553 out.close (); 554 555 556 // Run dot 557 if (extIsOut) { 558 File outFile = new File (basename + "." + ext); 559 560 String executable = getGvExecutable (); 561 ProcessBuilder pb = new ProcessBuilder (executable, "-T", ext); 562 pb.redirectInput (gvFile); 563 pb.redirectOutput (outFile); 564 int result = -1; 565 try { 566 result = pb.start ().waitFor (); 567 } catch (IOException e) { 568 throw new Error ("\n!!!! Cannot execute \"" + executable + "\"\n!!!! Make sure you have installed http://www.graphviz.org/" 569 + "\n!!!! Check the value of POSSIBLE_DOT_LOCATIONS in " + GraphvizBuilder.class.getCanonicalName ()); 570 } catch (InterruptedException e) { 571 throw new Error ("\n!!!! Execution of \"" + executable + "\" interrupted"); 572 } 573 if (result == 0) { 574 if (!keepGvFile) gvFile.delete (); 575 } else { 576 outFile.delete (); 577 } 578 } 579 } 580 /** 581 * Graphics files are saved in directory baseDirName/Graphviz. 582 * If baseDirName is a relative pathname, then it is placed in the users Desktop folder. 583 * baseDirName directory is created if it does not already exist. 584 * If baseDirName/Graphviz exists, then numbers are appended to the directory name: 585 * "baseDirName/Graphviz 1", "baseDirName/Graphviz 2", etc. 586 */ 587 public static void setBaseDirName (String baseDirName) { 588 if (baseDirName == null) throw new Error ("\n!!!! no nulls please"); 589 GraphvizBuilder.baseDirName = baseDirName; 590 } 591 private static String baseDirName = "GraphvizOutput"; 592 593 private static boolean isWindows () { 594 String osName = System.getProperty("os.name"); 595 if (osName == null) { 596 throw new Error("\n!!! os.name not found"); 597 } 598 osName = osName.toLowerCase(Locale.ENGLISH); 599 return osName.contains("windows"); 600 } 601 private static String getDesktop () { 602 if (isWindows()) { 603 // Supposedly this selects the windows desktop folder: 604 return javax.swing.filechooser.FileSystemView.getFileSystemView().getHomeDirectory().toString(); 605 } else { 606 return System.getProperty ("user.home") + File.separator + "Desktop"; 607 } 608 } 609 private static String dirName = null; 610 private static void setDirName () { 611 // create dir 612 File dir = new File (baseDirName); 613 if (!dir.isAbsolute ()) { 614 baseDirName = getDesktop() + File.separator + baseDirName; 615 dir = new File (baseDirName); 616 } 617 if (dir.exists ()) { 618 if (!dir.isDirectory ()) 619 throw new Error ("\n!!!! \"" + dir + "\" is not a directory"); 620 if (!dir.canWrite ()) 621 throw new Error ("\n!!!! Unable to write directory: \"" + dir + "\""); 622 } else { 623 dir.mkdirs (); 624 } 625 626 // create newDir 627 String mainClassName = "Graphviz"; 628 String prefix = baseDirName + File.separator; 629 File newDir = new File (prefix + mainClassName); 630 int suffix = 0; 631 while (newDir.exists()) { 632 suffix++; 633 newDir = new File(prefix + mainClassName + " " + suffix); 634 } 635 newDir.mkdir (); 636 637 if (!newDir.isDirectory () || !newDir.canWrite ()) 638 throw new Error ("Failed setOutputDirectory \"" + newDir + "\""); 639 dirName = newDir + File.separator; 640 } 641}