001/*
002 * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.
008 *
009 * This code is distributed in the hope that it will be useful, but WITHOUT
010 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
011 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
012 * version 2 for more details (a copy is included in the LICENSE file that
013 * accompanied this code).
014 *
015 * You should have received a copy of the GNU General Public License version
016 * 2 along with this work; if not, write to the Free Software Foundation,
017 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
018 *
019 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
020 * or visit www.oracle.com if you need additional information or have any
021 * questions.
022 */
023package com.oracle.graal.printer;
024
025import java.io.*;
026import java.nio.charset.*;
027import java.util.*;
028import java.util.Map.Entry;
029
030/**
031 * Elementary, generic generator of Ideal Graph Visualizer input for use in printers for specific
032 * data structures.
033 */
034class BasicIdealGraphPrinter {
035
036    /**
037     * Edge between two nodes.
038     */
039    protected static class Edge {
040
041        final String from;
042        final int fromIndex;
043        final String to;
044        final int toIndex;
045        final String label;
046
047        public Edge(String from, int fromIndex, String to, int toIndex, String label) {
048            assert (from != null && to != null);
049            this.from = from;
050            this.fromIndex = fromIndex;
051            this.to = to;
052            this.toIndex = toIndex;
053            this.label = label;
054        }
055
056        @Override
057        public int hashCode() {
058            int h = from.hashCode() ^ to.hashCode();
059            h = 3 * h + fromIndex;
060            h = 5 * h + toIndex;
061            if (label != null) {
062                h ^= label.hashCode();
063            }
064            return h;
065        }
066
067        @Override
068        public boolean equals(Object obj) {
069            if (obj == this) {
070                return true;
071            }
072            if (obj instanceof Edge) {
073                Edge other = (Edge) obj;
074                return from.equals(other.from) && fromIndex == other.fromIndex && to.equals(other.to) && toIndex == other.toIndex &&
075                                (label == other.label || (label != null && label.equals(other.label)));
076            }
077            return false;
078        }
079    }
080
081    private final PrintStream stream;
082
083    /**
084     * Creates a new {@link IdealGraphPrinter} that writes to the specified output stream.
085     */
086    protected BasicIdealGraphPrinter(OutputStream stream) {
087        try {
088            OutputStream buffered;
089            if (stream instanceof BufferedOutputStream) {
090                buffered = stream;
091            } else {
092                buffered = new BufferedOutputStream(stream, 256 * 1024);
093            }
094            this.stream = new PrintStream(buffered, false, Charset.defaultCharset().name());
095        } catch (UnsupportedEncodingException e) {
096            throw new RuntimeException(e);
097        }
098    }
099
100    /**
101     * Flushes any buffered output.
102     */
103    protected void flush() {
104        stream.flush();
105    }
106
107    /**
108     * Starts a new graph document.
109     */
110    protected void begin() {
111        stream.println("<graphDocument>");
112    }
113
114    protected void beginGroup() {
115        stream.println("<group>");
116    }
117
118    protected void beginMethod(String name, String shortName, int bci) {
119        stream.printf(" <method name='%s' shortName='%s' bci='%d'>%n", escape(name), escape(shortName), bci);
120    }
121
122    protected void beginBytecodes() {
123        stream.println("  <bytecodes>\n<![CDATA[");
124    }
125
126    protected void printBytecode(int bci, String mnemonic, int[] extra) {
127        stream.print(bci);
128        stream.print(' ');
129        stream.print(mnemonic);
130        if (extra != null) {
131            for (int b : extra) {
132                stream.print(' ');
133                stream.print(b);
134            }
135        }
136        stream.println();
137    }
138
139    protected void endBytecodes() {
140        stream.println("  ]]></bytecodes>");
141    }
142
143    protected void printBytecodes(String disassembly) {
144        beginBytecodes();
145        stream.println(disassembly);
146        endBytecodes();
147    }
148
149    protected void endMethod() {
150        stream.println(" </method>");
151    }
152
153    protected void beginGraph(String title) {
154        stream.printf(" <graph name='%s'>%n", escape(title));
155    }
156
157    protected void beginProperties() {
158        stream.print("<properties>");
159    }
160
161    protected void printProperty(String name, String value) {
162        stream.printf("<p name='%s'>%s</p>", escape(name), escape(value));
163    }
164
165    protected void endProperties() {
166        stream.print("</properties>");
167    }
168
169    protected void printProperties(Map<String, String> properties) {
170        beginProperties();
171        for (Entry<String, String> entry : properties.entrySet()) {
172            printProperty(entry.getKey(), entry.getValue());
173        }
174        endProperties();
175    }
176
177    protected void beginNodes() {
178        stream.println("  <nodes>");
179    }
180
181    protected void beginNode(String id) {
182        stream.printf("   <node id='%s'>", escape(id));
183    }
184
185    protected void endNode() {
186        stream.println("   </node>");
187    }
188
189    protected void printNode(String id, Map<String, String> properties) {
190        beginNode(id);
191        if (properties != null) {
192            printProperties(properties);
193        }
194        endNode();
195    }
196
197    protected void endNodes() {
198        stream.println("  </nodes>");
199    }
200
201    protected void beginEdges() {
202        stream.println("  <edges>");
203    }
204
205    protected void printEdge(Edge edge) {
206        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));
207    }
208
209    protected void endEdges() {
210        stream.println("  </edges>");
211    }
212
213    protected void beginControlFlow() {
214        stream.println("  <controlFlow>");
215    }
216
217    protected void beginBlock(String name) {
218        stream.printf("   <block name='%s'>%n", escape(name));
219    }
220
221    protected void beginSuccessors() {
222        stream.println("    <successors>");
223    }
224
225    protected void printSuccessor(String name) {
226        stream.printf("     <successor name='%s'/>%n", escape(name));
227    }
228
229    protected void endSuccessors() {
230        stream.println("    </successors>");
231    }
232
233    protected void beginBlockNodes() {
234        stream.println("    <nodes>");
235    }
236
237    protected void printBlockNode(String nodeId) {
238        stream.printf("     <node id='%s'/>%n", escape(nodeId));
239    }
240
241    protected void endBlockNodes() {
242        stream.println("    </nodes>");
243    }
244
245    protected void endBlock() {
246        stream.println("   </block>");
247    }
248
249    protected void endControlFlow() {
250        stream.println("  </controlFlow>");
251    }
252
253    protected void endGraph() {
254        stream.println(" </graph>");
255    }
256
257    /**
258     * Ends the current group.
259     */
260    public void endGroup() {
261        stream.println("</group>");
262    }
263
264    /**
265     * Finishes the graph document and flushes the output stream.
266     */
267    protected void end() {
268        stream.println("</graphDocument>");
269        flush();
270    }
271
272    public void close() {
273        end();
274        stream.close();
275    }
276
277    public boolean isValid() {
278        return !stream.checkError();
279    }
280
281    private static String escape(String s) {
282        StringBuilder str = null;
283        for (int i = 0; i < s.length(); i++) {
284            char c = s.charAt(i);
285            switch (c) {
286                case '&':
287                case '<':
288                case '>':
289                case '"':
290                case '\'':
291                    if (str == null) {
292                        str = new StringBuilder();
293                        str.append(s, 0, i);
294                    }
295                    switch (c) {
296                        case '&':
297                            str.append("&amp;");
298                            break;
299                        case '<':
300                            str.append("&lt;");
301                            break;
302                        case '>':
303                            str.append("&gt;");
304                            break;
305                        case '"':
306                            str.append("&quot;");
307                            break;
308                        case '\'':
309                            str.append("&apos;");
310                            break;
311                        default:
312                            assert false;
313                    }
314                    break;
315                case '\u0000':
316                case '\u0001':
317                case '\u0002':
318                case '\u0003':
319                case '\u0004':
320                case '\u0005':
321                case '\u0006':
322                case '\u0007':
323                case '\u0008':
324                case '\u000b':
325                case '\u000c':
326                case '\u000e':
327                case '\u000f':
328                case '\u0010':
329                case '\u0011':
330                case '\u0012':
331                case '\u0013':
332                case '\u0014':
333                case '\u0015':
334                case '\u0016':
335                case '\u0017':
336                case '\u0018':
337                case '\u0019':
338                case '\u001a':
339                case '\u001b':
340                case '\u001c':
341                case '\u001d':
342                case '\u001e':
343                case '\u001f':
344                    if (str == null) {
345                        str = new StringBuilder();
346                        str.append(s, 0, i);
347                    }
348                    str.append("'0x").append(Integer.toHexString(c));
349                    break;
350                default:
351                    if (str != null) {
352                        str.append(c);
353                    }
354                    break;
355            }
356        }
357        if (str == null) {
358            return s;
359        } else {
360            return str.toString();
361        }
362    }
363}