1 /*
   2  * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 package org.graalvm.compiler.printer;
  24 
  25 import java.io.BufferedOutputStream;
  26 import java.io.OutputStream;
  27 import java.io.PrintStream;
  28 import java.io.UnsupportedEncodingException;
  29 import java.nio.charset.Charset;
  30 import java.util.Map;
  31 import java.util.Map.Entry;
  32 
  33 /**
  34  * Elementary, generic generator of Ideal Graph Visualizer input for use in printers for specific
  35  * data structures.
  36  */
  37 class BasicIdealGraphPrinter {
  38 
  39     /**
  40      * Edge between two nodes.
  41      */
  42     protected static class Edge {
  43 
  44         final String from;
  45         final int fromIndex;
  46         final String to;
  47         final int toIndex;
  48         final String label;
  49 
  50         public Edge(String from, int fromIndex, String to, int toIndex, String label) {
  51             assert (from != null && to != null);
  52             this.from = from;
  53             this.fromIndex = fromIndex;
  54             this.to = to;
  55             this.toIndex = toIndex;
  56             this.label = label;
  57         }
  58 
  59         @Override
  60         public int hashCode() {
  61             int h = from.hashCode() ^ to.hashCode();
  62             h = 3 * h + fromIndex;
  63             h = 5 * h + toIndex;
  64             if (label != null) {
  65                 h ^= label.hashCode();
  66             }
  67             return h;
  68         }
  69 
  70         @Override
  71         public boolean equals(Object obj) {
  72             if (obj == this) {
  73                 return true;
  74             }
  75             if (obj instanceof Edge) {
  76                 Edge other = (Edge) obj;
  77                 return from.equals(other.from) && fromIndex == other.fromIndex && to.equals(other.to) && toIndex == other.toIndex &&
  78                                 (label == other.label || (label != null && label.equals(other.label)));
  79             }
  80             return false;
  81         }
  82     }
  83 
  84     private final PrintStream stream;
  85 
  86     /**
  87      * Creates a new {@link IdealGraphPrinter} that writes to the specified output stream.
  88      */
  89     protected BasicIdealGraphPrinter(OutputStream stream) {
  90         try {
  91             OutputStream buffered;
  92             if (stream instanceof BufferedOutputStream) {
  93                 buffered = stream;
  94             } else {
  95                 buffered = new BufferedOutputStream(stream, 256 * 1024);
  96             }
  97             this.stream = new PrintStream(buffered, false, Charset.defaultCharset().name());
  98         } catch (UnsupportedEncodingException e) {
  99             throw new RuntimeException(e);
 100         }
 101     }
 102 
 103     /**
 104      * Flushes any buffered output.
 105      */
 106     protected void flush() {
 107         stream.flush();
 108     }
 109 
 110     /**
 111      * Starts a new graph document.
 112      */
 113     protected void begin() {
 114         stream.println("<graphDocument>");
 115     }
 116 
 117     protected void beginGroup() {
 118         stream.println("<group>");
 119     }
 120 
 121     protected void beginMethod(String name, String shortName, int bci) {
 122         stream.printf(" <method name='%s' shortName='%s' bci='%d'>%n", escape(name), escape(shortName), bci);
 123     }
 124 
 125     protected void beginBytecodes() {
 126         stream.println("  <bytecodes>\n<![CDATA[");
 127     }
 128 
 129     protected void printBytecode(int bci, String mnemonic, int[] extra) {
 130         stream.print(bci);
 131         stream.print(' ');
 132         stream.print(mnemonic);
 133         if (extra != null) {
 134             for (int b : extra) {
 135                 stream.print(' ');
 136                 stream.print(b);
 137             }
 138         }
 139         stream.println();
 140     }
 141 
 142     protected void endBytecodes() {
 143         stream.println("  ]]></bytecodes>");
 144     }
 145 
 146     protected void printBytecodes(String disassembly) {
 147         beginBytecodes();
 148         stream.println(disassembly);
 149         endBytecodes();
 150     }
 151 
 152     protected void endMethod() {
 153         stream.println(" </method>");
 154     }
 155 
 156     protected void beginGraph(String title) {
 157         stream.printf(" <graph name='%s'>%n", escape(title));
 158     }
 159 
 160     protected void beginProperties() {
 161         stream.print("<properties>");
 162     }
 163 
 164     protected void printProperty(String name, String value) {
 165         stream.printf("<p name='%s'>%s</p>", escape(name), escape(value));
 166     }
 167 
 168     protected void endProperties() {
 169         stream.print("</properties>");
 170     }
 171 
 172     protected void printProperties(Map<String, String> properties) {
 173         beginProperties();
 174         for (Entry<String, String> entry : properties.entrySet()) {
 175             printProperty(entry.getKey(), entry.getValue());
 176         }
 177         endProperties();
 178     }
 179 
 180     protected void beginNodes() {
 181         stream.println("  <nodes>");
 182     }
 183 
 184     protected void beginNode(String id) {
 185         stream.printf("   <node id='%s'>", escape(id));
 186     }
 187 
 188     protected void endNode() {
 189         stream.println("   </node>");
 190     }
 191 
 192     protected void printNode(String id, Map<String, String> properties) {
 193         beginNode(id);
 194         if (properties != null) {
 195             printProperties(properties);
 196         }
 197         endNode();
 198     }
 199 
 200     protected void endNodes() {
 201         stream.println("  </nodes>");
 202     }
 203 
 204     protected void beginEdges() {
 205         stream.println("  <edges>");
 206     }
 207 
 208     protected void printEdge(Edge edge) {
 209         stream.printf("   <edge from='%s' fromIndex='%d' to='%s' toIndex='%d' label='%s' />%n", escape(edge.from), edge.fromIndex, escape(edge.to), edge.toIndex, escape(edge.label));
 210     }
 211 
 212     protected void endEdges() {
 213         stream.println("  </edges>");
 214     }
 215 
 216     protected void beginControlFlow() {
 217         stream.println("  <controlFlow>");
 218     }
 219 
 220     protected void beginBlock(String name) {
 221         stream.printf("   <block name='%s'>%n", escape(name));
 222     }
 223 
 224     protected void beginSuccessors() {
 225         stream.println("    <successors>");
 226     }
 227 
 228     protected void printSuccessor(String name) {
 229         stream.printf("     <successor name='%s'/>%n", escape(name));
 230     }
 231 
 232     protected void endSuccessors() {
 233         stream.println("    </successors>");
 234     }
 235 
 236     protected void beginBlockNodes() {
 237         stream.println("    <nodes>");
 238     }
 239 
 240     protected void printBlockNode(String nodeId) {
 241         stream.printf("     <node id='%s'/>%n", escape(nodeId));
 242     }
 243 
 244     protected void endBlockNodes() {
 245         stream.println("    </nodes>");
 246     }
 247 
 248     protected void endBlock() {
 249         stream.println("   </block>");
 250     }
 251 
 252     protected void endControlFlow() {
 253         stream.println("  </controlFlow>");
 254     }
 255 
 256     protected void endGraph() {
 257         stream.println(" </graph>");
 258     }
 259 
 260     /**
 261      * Ends the current group.
 262      */
 263     public void endGroup() {
 264         stream.println("</group>");
 265     }
 266 
 267     /**
 268      * Finishes the graph document and flushes the output stream.
 269      */
 270     protected void end() {
 271         stream.println("</graphDocument>");
 272         flush();
 273     }
 274 
 275     public void close() {
 276         end();
 277         stream.close();
 278     }
 279 
 280     public boolean isValid() {
 281         return !stream.checkError();
 282     }
 283 
 284     private static String escape(String s) {
 285         StringBuilder str = null;
 286         for (int i = 0; i < s.length(); i++) {
 287             char c = s.charAt(i);
 288             switch (c) {
 289                 case '&':
 290                 case '<':
 291                 case '>':
 292                 case '"':
 293                 case '\'':
 294                     if (str == null) {
 295                         str = new StringBuilder();
 296                         str.append(s, 0, i);
 297                     }
 298                     switch (c) {
 299                         case '&':
 300                             str.append("&amp;");
 301                             break;
 302                         case '<':
 303                             str.append("&lt;");
 304                             break;
 305                         case '>':
 306                             str.append("&gt;");
 307                             break;
 308                         case '"':
 309                             str.append("&quot;");
 310                             break;
 311                         case '\'':
 312                             str.append("&apos;");
 313                             break;
 314                         default:
 315                             assert false;
 316                     }
 317                     break;
 318                 case '\u0000':
 319                 case '\u0001':
 320                 case '\u0002':
 321                 case '\u0003':
 322                 case '\u0004':
 323                 case '\u0005':
 324                 case '\u0006':
 325                 case '\u0007':
 326                 case '\u0008':
 327                 case '\u000b':
 328                 case '\u000c':
 329                 case '\u000e':
 330                 case '\u000f':
 331                 case '\u0010':
 332                 case '\u0011':
 333                 case '\u0012':
 334                 case '\u0013':
 335                 case '\u0014':
 336                 case '\u0015':
 337                 case '\u0016':
 338                 case '\u0017':
 339                 case '\u0018':
 340                 case '\u0019':
 341                 case '\u001a':
 342                 case '\u001b':
 343                 case '\u001c':
 344                 case '\u001d':
 345                 case '\u001e':
 346                 case '\u001f':
 347                     if (str == null) {
 348                         str = new StringBuilder();
 349                         str.append(s, 0, i);
 350                     }
 351                     str.append("'0x").append(Integer.toHexString(c));
 352                     break;
 353                 default:
 354                     if (str != null) {
 355                         str.append(c);
 356                     }
 357                     break;
 358             }
 359         }
 360         if (str == null) {
 361             return s;
 362         } else {
 363             return str.toString();
 364         }
 365     }
 366 }