1/*
2 * Copyright (c) 2004, 2013, 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 */
24
25package sun.jvm.hotspot.utilities;
26
27import java.io.*;
28import java.util.*;
29import sun.jvm.hotspot.oops.*;
30import sun.jvm.hotspot.runtime.*;
31
32/**
33 * <p>This class writes Java heap in Graph eXchange Language (GXL)
34 * format. GXL is an open standard for serializing arbitrary graphs in
35 * XML syntax.</p>
36 *
37 * <p>A GXL document contains one or more graphs. A graph contains
38 * nodes and edges. Both nodes and edges can have attributes. graphs,
39 * nodes, edges and attributes are represented by XML elements graph,
40 * node, edge and attr respectively. Attributes can be typed. GXL
41 * supports locator, bool, int, float, bool, string, enum as well as
42 * set, seq, bag, tup types. Nodes must have a XML attribute 'id' that
43 * is unique id of the node in the GXL document. Edges must have
44 * 'from' and 'to' XML attributes that are ids of from and to nodes.</p>
45 *
46 * <p>Java heap to GXL document mapping:</p>
47 * <ul>
48 * <li>Java object - GXL node.
49 * <li>Java primitive field - GXL attribute (type mapping below).
50 * <li>Java reference field - GXL edge from referee to referent node.
51 * <li>Java primitive array - GXL node with seq type attribute.
52 * <li>Java char array - GXL node with one attribute of string type.
53 * <li>Java object array - GXL node and 'length' edges.
54 * </ul>
55 *
56 * <p>Java primitive to GXL type mapping:</p>
57 * <ul>
58 * <li>Java byte, int, short, long - GXL int attribute
59 * <li>Java float, double - GXL float attribute
60 * <li>Java boolean - GXL bool atttribute
61 * <li>Java char - GXL string attribute
62 * </ul>
63 *
64 * Exact Java primitive type code is written in 'kind' attribute of
65 * 'attr' element.  Type code is specified in JVM spec. second edition
66 * section 4.3.2 (Field Descriptor).
67 *
68 * @see <a href="http://www.gupro.de/GXL/">GXL</a>
69 * @see <a href="http://www.gupro.de/GXL/dtd/dtd.html">GXL DTD</a>
70 */
71
72public class HeapGXLWriter extends AbstractHeapGraphWriter {
73    public void write(String fileName) throws IOException {
74        out = new PrintWriter(new BufferedWriter(new FileWriter(fileName)));
75        super.write();
76        if (out.checkError()) {
77            throw new IOException();
78        }
79        out.flush();
80    }
81
82    protected void writeHeapHeader() throws IOException {
83        // XML processing instruction
84        out.print("<?xml version='1.0' encoding='");
85        out.print(ENCODING);
86        out.println("'?>");
87
88        out.println("<gxl>");
89        out.println("<graph id='JavaHeap'>");
90
91        // document properties
92        writeAttribute("creation-date", "string", new Date().toString());
93
94        // write VM info
95        writeVMInfo();
96
97        // emit a node for null
98        out.print("<node id='");
99        out.print(getID(null));
100        out.println("'/>");
101    }
102
103    protected void writeObjectHeader(Oop oop) throws IOException  {
104        refFields = new ArrayList();
105        isArray = oop.isArray();
106
107        // generate an edge for instanceof relation
108        // between object node and it's class node.
109        writeEdge(oop, oop.getKlass().getJavaMirror(), "instanceof");
110
111        out.print("<node id='");
112        out.print(getID(oop));
113        out.println("'>");
114    }
115
116    protected void writeObjectFooter(Oop oop) throws IOException  {
117        out.println("</node>");
118
119        // write the reference fields as edges
120        for (Iterator itr = refFields.iterator(); itr.hasNext();) {
121            OopField field = (OopField) itr.next();
122            Oop ref = field.getValue(oop);
123
124            String name = field.getID().getName();
125            if (isArray) {
126                // for arrays elements we use element<index> pattern
127                name = "element" + name;
128            } else {
129                name = identifierToXMLName(name);
130            }
131            writeEdge(oop, ref, name);
132        }
133        refFields = null;
134    }
135
136    protected void writeObjectArray(ObjArray array) throws IOException {
137        writeObjectHeader(array);
138        writeArrayLength(array);
139        writeObjectFields(array);
140        writeObjectFooter(array);
141    }
142
143    protected void writePrimitiveArray(TypeArray array)
144        throws IOException  {
145        writeObjectHeader(array);
146        // write array length
147        writeArrayLength(array);
148        // write array elements
149        out.println("\t<attr name='elements'>");
150        TypeArrayKlass klass = (TypeArrayKlass) array.getKlass();
151        if (klass.getElementType() == TypeArrayKlass.T_CHAR) {
152            // char[] special treatment -- write it as string
153            out.print("\t<string>");
154            out.print(escapeXMLChars(OopUtilities.charArrayToString(array)));
155            out.println("</string>");
156        } else {
157            out.println("\t<seq>");
158            writeObjectFields(array);
159            out.println("\t</seq>");
160        }
161        out.println("\t</attr>");
162        writeObjectFooter(array);
163    }
164
165    protected void writeClass(Instance instance) throws IOException  {
166        writeObjectHeader(instance);
167        Klass reflectedType = java_lang_Class.asKlass(instance);
168        boolean isInstanceKlass = (reflectedType instanceof InstanceKlass);
169        // reflectedType is null for primitive types (int.class etc).
170        if (reflectedType != null) {
171            Symbol name = reflectedType.getName();
172            if (name != null) {
173                // write class name as an attribute
174                writeAttribute("class-name", "string", name.asString());
175            }
176            if (isInstanceKlass) {
177                // write object-size as an attribute
178                long sizeInBytes = reflectedType.getLayoutHelper();
179                writeAttribute("object-size", "int",
180                               Long.toString(sizeInBytes));
181                // write static fields of this class.
182                writeObjectFields((InstanceKlass)reflectedType);
183            }
184        }
185        out.println("</node>");
186
187        // write edges for super class and direct interfaces
188        if (reflectedType != null) {
189            Klass superType = reflectedType.getSuper();
190            Oop superMirror = (superType == null)?
191                              null : superType.getJavaMirror();
192            writeEdge(instance, superMirror, "extends");
193            if (isInstanceKlass) {
194                // write edges for directly implemented interfaces
195                InstanceKlass ik = (InstanceKlass) reflectedType;
196                KlassArray interfaces = ik.getLocalInterfaces();
197                final int len = interfaces.length();
198                for (int i = 0; i < len; i++) {
199                    Klass k = interfaces.getAt(i);
200                    writeEdge(instance, k.getJavaMirror(), "implements");
201                }
202
203                // write loader
204                Oop loader = ik.getClassLoader();
205                writeEdge(instance, loader, "loaded-by");
206
207                // write signers NYI
208                // Oop signers = ik.getJavaMirror().getSigners();
209                writeEdge(instance, null, "signed-by");
210
211                // write protection domain NYI
212                // Oop protectionDomain = ik.getJavaMirror().getProtectionDomain();
213                writeEdge(instance, null, "protection-domain");
214
215                // write edges for static reference fields from this class
216                for (Iterator itr = refFields.iterator(); itr.hasNext();) {
217                    OopField field = (OopField) itr.next();
218                    Oop ref = field.getValue(reflectedType);
219                    String name = field.getID().getName();
220                    writeEdge(instance, ref, identifierToXMLName(name));
221                }
222            }
223        }
224        refFields = null;
225    }
226
227    protected void writeReferenceField(Oop oop, OopField field)
228        throws IOException {
229        refFields.add(field);
230    }
231
232    protected void writeByteField(Oop oop, ByteField field)
233        throws IOException {
234        writeField(field, "int", "B", Byte.toString(field.getValue(oop)));
235    }
236
237    protected void writeCharField(Oop oop, CharField field)
238        throws IOException {
239        writeField(field, "string", "C",
240                   escapeXMLChars(Character.toString(field.getValue(oop))));
241    }
242
243    protected void writeBooleanField(Oop oop, BooleanField field)
244        throws IOException {
245        writeField(field, "bool", "Z", Boolean.toString(field.getValue(oop)));
246    }
247
248    protected void writeShortField(Oop oop, ShortField field)
249        throws IOException {
250        writeField(field, "int", "S", Short.toString(field.getValue(oop)));
251    }
252
253    protected void writeIntField(Oop oop, IntField field)
254        throws IOException {
255        writeField(field, "int", "I", Integer.toString(field.getValue(oop)));
256    }
257
258    protected void writeLongField(Oop oop, LongField field)
259        throws IOException {
260        writeField(field, "int", "J", Long.toString(field.getValue(oop)));
261    }
262
263    protected void writeFloatField(Oop oop, FloatField field)
264        throws IOException {
265        writeField(field, "float", "F", Float.toString(field.getValue(oop)));
266    }
267
268    protected void writeDoubleField(Oop oop, DoubleField field)
269        throws IOException {
270        writeField(field, "float", "D", Double.toString(field.getValue(oop)));
271    }
272
273    protected void writeHeapFooter() throws IOException  {
274        out.println("</graph>");
275        out.println("</gxl>");
276    }
277
278    //-- Internals only below this point
279
280    // Java identifier to XML NMTOKEN type string
281    private static String identifierToXMLName(String name) {
282        // for now, just replace '$' with '_'
283        return name.replace('$', '_');
284    }
285
286    // escapes XML meta-characters and illegal characters
287    private static String escapeXMLChars(String s) {
288        // FIXME: is there a better way or API?
289        StringBuffer result = null;
290        for(int i = 0, max = s.length(), delta = 0; i < max; i++) {
291            char c = s.charAt(i);
292            String replacement = null;
293            if (c == '&') {
294                replacement = "&amp;";
295            } else if (c == '<') {
296                replacement = "&lt;";
297            } else if (c == '>') {
298                replacement = "&gt;";
299            } else if (c == '"') {
300                replacement = "&quot;";
301            } else if (c == '\'') {
302                replacement = "&apos;";
303            } else if (c <  '\u0020' || (c > '\ud7ff' && c < '\ue000') ||
304                       c == '\ufffe' || c == '\uffff') {
305                // These are illegal in XML -- put these in a CDATA section.
306                // Refer to section 2.2 Characters in XML specification at
307                // http://www.w3.org/TR/2004/REC-xml-20040204/
308                replacement = "<![CDATA[&#x" +
309                    Integer.toHexString((int)c) + ";]]>";
310            }
311
312            if (replacement != null) {
313                if (result == null) {
314                    result = new StringBuffer(s);
315                }
316                result.replace(i + delta, i + delta + 1, replacement);
317                delta += (replacement.length() - 1);
318            }
319        }
320        if (result == null) {
321            return s;
322        }
323        return result.toString();
324    }
325
326    private static String getID(Oop oop) {
327        // address as unique id for node -- prefixed by "ID_".
328        if (oop == null) {
329            return "ID_NULL";
330        } else {
331            return "ID_" + oop.getHandle().toString();
332        }
333    }
334
335    private void writeArrayLength(Array array) throws IOException {
336        writeAttribute("length", "int",
337                       Integer.toString((int) array.getLength()));
338    }
339
340    private void writeAttribute(String name, String type, String value) {
341        out.print("\t<attr name='");
342        out.print(name);
343        out.print("'><");
344        out.print(type);
345        out.print('>');
346        out.print(value);
347        out.print("</");
348        out.print(type);
349        out.println("></attr>");
350    }
351
352    private void writeEdge(Oop from, Oop to, String name) throws IOException {
353        out.print("<edge from='");
354        out.print(getID(from));
355        out.print("' to='");
356        out.print(getID(to));
357        out.println("'>");
358        writeAttribute("name", "string", name);
359        out.println("</edge>");
360    }
361
362    private void writeField(Field field, String type, String kind,
363                            String value) throws IOException  {
364        // 'type' is GXL type of the attribute
365        // 'kind' is Java type code ("B", "C", "Z", "S", "I", "J", "F", "D")
366        if (isArray) {
367            out.print('\t');
368        } else {
369            out.print("\t<attr name='");
370            String name = field.getID().getName();
371            out.print(identifierToXMLName(name));
372            out.print("' kind='");
373            out.print(kind);
374            out.print("'>");
375        }
376        out.print('<');
377        out.print(type);
378        out.print('>');
379        out.print(value);
380        out.print("</");
381        out.print(type);
382        out.print('>');
383        if (isArray) {
384            out.println();
385        } else {
386            out.println("</attr>");
387        }
388    }
389
390    private void writeVMInfo() throws IOException {
391        VM vm = VM.getVM();
392        writeAttribute("vm-version", "string", vm.getVMRelease());
393        writeAttribute("vm-type", "string",
394                       (vm.isClientCompiler())? "client" :
395                       ((vm.isServerCompiler())? "server" : "core"));
396        writeAttribute("os", "string", vm.getOS());
397        writeAttribute("cpu", "string", vm.getCPU());
398        writeAttribute("pointer-size", "string",
399                       Integer.toString((int)vm.getOopSize() * 8));
400    }
401
402    // XML encoding that we'll use
403    private static final String ENCODING = "UTF-8";
404
405    // reference fields of currently visited object
406    private List/*<OopField>*/ refFields;
407    // are we writing an array now?
408    private boolean isArray;
409    private PrintWriter out;
410}
411