/*
 * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 *
 */

package sun.jvm.hotspot.utilities;

import java.io.*;
import java.util.*;
import sun.jvm.hotspot.oops.*;
import sun.jvm.hotspot.runtime.*;

This class writes Java heap in Graph eXchange Language (GXL) format. GXL is an open standard for serializing arbitrary graphs in XML syntax.

A GXL document contains one or more graphs. A graph contains nodes and edges. Both nodes and edges can have attributes. graphs, nodes, edges and attributes are represented by XML elements graph, node, edge and attr respectively. Attributes can be typed. GXL supports locator, bool, int, float, bool, string, enum as well as set, seq, bag, tup types. Nodes must have a XML attribute 'id' that is unique id of the node in the GXL document. Edges must have 'from' and 'to' XML attributes that are ids of from and to nodes.

Java heap to GXL document mapping:

  • Java object - GXL node.
  • Java primitive field - GXL attribute (type mapping below).
  • Java reference field - GXL edge from referee to referent node.
  • Java primitive array - GXL node with seq type attribute.
  • Java char array - GXL node with one attribute of string type.
  • Java object array - GXL node and 'length' edges.

Java primitive to GXL type mapping:

  • Java byte, int, short, long - GXL int attribute
  • Java float, double - GXL float attribute
  • Java boolean - GXL bool atttribute
  • Java char - GXL string attribute
Exact Java primitive type code is written in 'kind' attribute of 'attr' element. Type code is specified in JVM spec. second edition section 4.3.2 (Field Descriptor).
See Also:
/** * <p>This class writes Java heap in Graph eXchange Language (GXL) * format. GXL is an open standard for serializing arbitrary graphs in * XML syntax.</p> * * <p>A GXL document contains one or more graphs. A graph contains * nodes and edges. Both nodes and edges can have attributes. graphs, * nodes, edges and attributes are represented by XML elements graph, * node, edge and attr respectively. Attributes can be typed. GXL * supports locator, bool, int, float, bool, string, enum as well as * set, seq, bag, tup types. Nodes must have a XML attribute 'id' that * is unique id of the node in the GXL document. Edges must have * 'from' and 'to' XML attributes that are ids of from and to nodes.</p> * * <p>Java heap to GXL document mapping:</p> * <ul> * <li>Java object - GXL node. * <li>Java primitive field - GXL attribute (type mapping below). * <li>Java reference field - GXL edge from referee to referent node. * <li>Java primitive array - GXL node with seq type attribute. * <li>Java char array - GXL node with one attribute of string type. * <li>Java object array - GXL node and 'length' edges. * </ul> * * <p>Java primitive to GXL type mapping:</p> * <ul> * <li>Java byte, int, short, long - GXL int attribute * <li>Java float, double - GXL float attribute * <li>Java boolean - GXL bool atttribute * <li>Java char - GXL string attribute * </ul> * * Exact Java primitive type code is written in 'kind' attribute of * 'attr' element. Type code is specified in JVM spec. second edition * section 4.3.2 (Field Descriptor). * * @see <a href="http://www.gupro.de/GXL/">GXL</a> * @see <a href="http://www.gupro.de/GXL/dtd/dtd.html">GXL DTD</a> */
public class HeapGXLWriter extends AbstractHeapGraphWriter { public void write(String fileName) throws IOException { out = new PrintWriter(new BufferedWriter(new FileWriter(fileName))); super.write(); if (out.checkError()) { throw new IOException(); } out.flush(); } protected void writeHeapHeader() throws IOException { // XML processing instruction out.print("<?xml version='1.0' encoding='"); out.print(ENCODING); out.println("'?>"); out.println("<gxl>"); out.println("<graph id='JavaHeap'>"); // document properties writeAttribute("creation-date", "string", new Date().toString()); // write VM info writeVMInfo(); // emit a node for null out.print("<node id='"); out.print(getID(null)); out.println("'/>"); } protected void writeObjectHeader(Oop oop) throws IOException { refFields = new ArrayList(); isArray = oop.isArray(); // generate an edge for instanceof relation // between object node and it's class node. writeEdge(oop, oop.getKlass().getJavaMirror(), "instanceof"); out.print("<node id='"); out.print(getID(oop)); out.println("'>"); } protected void writeObjectFooter(Oop oop) throws IOException { out.println("</node>"); // write the reference fields as edges for (Iterator itr = refFields.iterator(); itr.hasNext();) { OopField field = (OopField) itr.next(); Oop ref = field.getValue(oop); String name = field.getID().getName(); if (isArray) { // for arrays elements we use element<index> pattern name = "element" + name; } else { name = identifierToXMLName(name); } writeEdge(oop, ref, name); } refFields = null; } protected void writeObjectArray(ObjArray array) throws IOException { writeObjectHeader(array); writeArrayLength(array); writeObjectFields(array); writeObjectFooter(array); } protected void writePrimitiveArray(TypeArray array) throws IOException { writeObjectHeader(array); // write array length writeArrayLength(array); // write array elements out.println("\t<attr name='elements'>"); TypeArrayKlass klass = (TypeArrayKlass) array.getKlass(); if (klass.getElementType() == TypeArrayKlass.T_CHAR) { // char[] special treatment -- write it as string out.print("\t<string>"); out.print(escapeXMLChars(OopUtilities.charArrayToString(array))); out.println("</string>"); } else { out.println("\t<seq>"); writeObjectFields(array); out.println("\t</seq>"); } out.println("\t</attr>"); writeObjectFooter(array); } protected void writeClass(Instance instance) throws IOException { writeObjectHeader(instance); Klass reflectedType = java_lang_Class.asKlass(instance); boolean isInstanceKlass = (reflectedType instanceof InstanceKlass); // reflectedType is null for primitive types (int.class etc). if (reflectedType != null) { Symbol name = reflectedType.getName(); if (name != null) { // write class name as an attribute writeAttribute("class-name", "string", name.asString()); } if (isInstanceKlass) { // write object-size as an attribute long sizeInBytes = reflectedType.getLayoutHelper(); writeAttribute("object-size", "int", Long.toString(sizeInBytes)); // write static fields of this class. writeObjectFields((InstanceKlass)reflectedType); } } out.println("</node>"); // write edges for super class and direct interfaces if (reflectedType != null) { Klass superType = reflectedType.getSuper(); Oop superMirror = (superType == null)? null : superType.getJavaMirror(); writeEdge(instance, superMirror, "extends"); if (isInstanceKlass) { // write edges for directly implemented interfaces InstanceKlass ik = (InstanceKlass) reflectedType; KlassArray interfaces = ik.getLocalInterfaces(); final int len = interfaces.length(); for (int i = 0; i < len; i++) { Klass k = interfaces.getAt(i); writeEdge(instance, k.getJavaMirror(), "implements"); } // write loader Oop loader = ik.getClassLoader(); writeEdge(instance, loader, "loaded-by"); // write signers NYI // Oop signers = ik.getJavaMirror().getSigners(); writeEdge(instance, null, "signed-by"); // write protection domain NYI // Oop protectionDomain = ik.getJavaMirror().getProtectionDomain(); writeEdge(instance, null, "protection-domain"); // write edges for static reference fields from this class for (Iterator itr = refFields.iterator(); itr.hasNext();) { OopField field = (OopField) itr.next(); Oop ref = field.getValue(reflectedType); String name = field.getID().getName(); writeEdge(instance, ref, identifierToXMLName(name)); } } } refFields = null; } protected void writeReferenceField(Oop oop, OopField field) throws IOException { refFields.add(field); } protected void writeByteField(Oop oop, ByteField field) throws IOException { writeField(field, "int", "B", Byte.toString(field.getValue(oop))); } protected void writeCharField(Oop oop, CharField field) throws IOException { writeField(field, "string", "C", escapeXMLChars(Character.toString(field.getValue(oop)))); } protected void writeBooleanField(Oop oop, BooleanField field) throws IOException { writeField(field, "bool", "Z", Boolean.toString(field.getValue(oop))); } protected void writeShortField(Oop oop, ShortField field) throws IOException { writeField(field, "int", "S", Short.toString(field.getValue(oop))); } protected void writeIntField(Oop oop, IntField field) throws IOException { writeField(field, "int", "I", Integer.toString(field.getValue(oop))); } protected void writeLongField(Oop oop, LongField field) throws IOException { writeField(field, "int", "J", Long.toString(field.getValue(oop))); } protected void writeFloatField(Oop oop, FloatField field) throws IOException { writeField(field, "float", "F", Float.toString(field.getValue(oop))); } protected void writeDoubleField(Oop oop, DoubleField field) throws IOException { writeField(field, "float", "D", Double.toString(field.getValue(oop))); } protected void writeHeapFooter() throws IOException { out.println("</graph>"); out.println("</gxl>"); } //-- Internals only below this point // Java identifier to XML NMTOKEN type string private static String identifierToXMLName(String name) { // for now, just replace '$' with '_' return name.replace('$', '_'); } // escapes XML meta-characters and illegal characters private static String escapeXMLChars(String s) { // FIXME: is there a better way or API? StringBuffer result = null; for(int i = 0, max = s.length(), delta = 0; i < max; i++) { char c = s.charAt(i); String replacement = null; if (c == '&') { replacement = "&amp;"; } else if (c == '<') { replacement = "&lt;"; } else if (c == '>') { replacement = "&gt;"; } else if (c == '"') { replacement = "&quot;"; } else if (c == '\'') { replacement = "&apos;"; } else if (c < '\u0020' || (c > '\ud7ff' && c < '\ue000') || c == '\ufffe' || c == '\uffff') { // These are illegal in XML -- put these in a CDATA section. // Refer to section 2.2 Characters in XML specification at // http://www.w3.org/TR/2004/REC-xml-20040204/ replacement = "<![CDATA[&#x" + Integer.toHexString((int)c) + ";]]>"; } if (replacement != null) { if (result == null) { result = new StringBuffer(s); } result.replace(i + delta, i + delta + 1, replacement); delta += (replacement.length() - 1); } } if (result == null) { return s; } return result.toString(); } private static String getID(Oop oop) { // address as unique id for node -- prefixed by "ID_". if (oop == null) { return "ID_NULL"; } else { return "ID_" + oop.getHandle().toString(); } } private void writeArrayLength(Array array) throws IOException { writeAttribute("length", "int", Integer.toString((int) array.getLength())); } private void writeAttribute(String name, String type, String value) { out.print("\t<attr name='"); out.print(name); out.print("'><"); out.print(type); out.print('>'); out.print(value); out.print("</"); out.print(type); out.println("></attr>"); } private void writeEdge(Oop from, Oop to, String name) throws IOException { out.print("<edge from='"); out.print(getID(from)); out.print("' to='"); out.print(getID(to)); out.println("'>"); writeAttribute("name", "string", name); out.println("</edge>"); } private void writeField(Field field, String type, String kind, String value) throws IOException { // 'type' is GXL type of the attribute // 'kind' is Java type code ("B", "C", "Z", "S", "I", "J", "F", "D") if (isArray) { out.print('\t'); } else { out.print("\t<attr name='"); String name = field.getID().getName(); out.print(identifierToXMLName(name)); out.print("' kind='"); out.print(kind); out.print("'>"); } out.print('<'); out.print(type); out.print('>'); out.print(value); out.print("</"); out.print(type); out.print('>'); if (isArray) { out.println(); } else { out.println("</attr>"); } } private void writeVMInfo() throws IOException { VM vm = VM.getVM(); writeAttribute("vm-version", "string", vm.getVMRelease()); writeAttribute("vm-type", "string", (vm.isClientCompiler())? "client" : ((vm.isServerCompiler())? "server" : "core")); writeAttribute("os", "string", vm.getOS()); writeAttribute("cpu", "string", vm.getCPU()); writeAttribute("pointer-size", "string", Integer.toString((int)vm.getOopSize() * 8)); } // XML encoding that we'll use private static final String ENCODING = "UTF-8"; // reference fields of currently visited object private List/*<OopField>*/ refFields; // are we writing an array now? private boolean isArray; private PrintWriter out; }