1 /*
   2  * Copyright 2004-2007 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  *
  23  */
  24 
  25 package sun.jvm.hotspot.utilities;
  26 
  27 import java.io.*;
  28 import java.util.*;
  29 import sun.jvm.hotspot.oops.*;
  30 import 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 
  72 public 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 = OopUtilities.classOopToKlass(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                 sizeInBytes &= ~Bits.rightNBits(Klass.LH_SIZE_LOW_BITS);
 180                 writeAttribute("object-size", "int",
 181                                Long.toString(sizeInBytes));
 182                 // write static fields of this class.
 183                 writeObjectFields(reflectedType);
 184             }
 185         }
 186         out.println("</node>");
 187 
 188         // write edges for super class and direct interfaces
 189         if (reflectedType != null) {
 190             Klass superType = reflectedType.getSuper();
 191             Oop superMirror = (superType == null)?
 192                               null : superType.getJavaMirror();
 193             writeEdge(instance, superMirror, "extends");
 194             if (isInstanceKlass) {
 195                 // write edges for directly implemented interfaces
 196                 InstanceKlass ik = (InstanceKlass) reflectedType;
 197                 ObjArray interfaces = ik.getLocalInterfaces();
 198                 final int len = (int) interfaces.getLength();
 199                 for (int i = 0; i < len; i++) {
 200                     Klass k = (Klass) interfaces.getObjAt(i);
 201                     writeEdge(instance, k.getJavaMirror(), "implements");
 202                 }
 203 
 204                 // write loader
 205                 Oop loader = ik.getClassLoader();
 206                 writeEdge(instance, loader, "loaded-by");
 207 
 208                 // write signers
 209                 Oop signers = ik.getSigners();
 210                 writeEdge(instance, signers, "signed-by");
 211 
 212                 // write protection domain
 213                 Oop protectionDomain = ik.getProtectionDomain();
 214                 writeEdge(instance, protectionDomain, "protection-domain");
 215 
 216                 // write edges for static reference fields from this class
 217                 for (Iterator itr = refFields.iterator(); itr.hasNext();) {
 218                     OopField field = (OopField) itr.next();
 219                     Oop ref = field.getValue(reflectedType);
 220                     String name = field.getID().getName();
 221                     writeEdge(instance, ref, identifierToXMLName(name));
 222                 }
 223             }
 224         }
 225         refFields = null;
 226     }
 227 
 228     protected void writeReferenceField(Oop oop, OopField field)
 229         throws IOException {
 230         refFields.add(field);
 231     }
 232 
 233     protected void writeByteField(Oop oop, ByteField field)
 234         throws IOException {
 235         writeField(field, "int", "B", Byte.toString(field.getValue(oop)));
 236     }
 237 
 238     protected void writeCharField(Oop oop, CharField field)
 239         throws IOException {
 240         writeField(field, "string", "C",
 241                    escapeXMLChars(Character.toString(field.getValue(oop))));
 242     }
 243 
 244     protected void writeBooleanField(Oop oop, BooleanField field)
 245         throws IOException {
 246         writeField(field, "bool", "Z", Boolean.toString(field.getValue(oop)));
 247     }
 248 
 249     protected void writeShortField(Oop oop, ShortField field)
 250         throws IOException {
 251         writeField(field, "int", "S", Short.toString(field.getValue(oop)));
 252     }
 253 
 254     protected void writeIntField(Oop oop, IntField field)
 255         throws IOException {
 256         writeField(field, "int", "I", Integer.toString(field.getValue(oop)));
 257     }
 258 
 259     protected void writeLongField(Oop oop, LongField field)
 260         throws IOException {
 261         writeField(field, "int", "J", Long.toString(field.getValue(oop)));
 262     }
 263 
 264     protected void writeFloatField(Oop oop, FloatField field)
 265         throws IOException {
 266         writeField(field, "float", "F", Float.toString(field.getValue(oop)));
 267     }
 268 
 269     protected void writeDoubleField(Oop oop, DoubleField field)
 270         throws IOException {
 271         writeField(field, "float", "D", Double.toString(field.getValue(oop)));
 272     }
 273 
 274     protected void writeHeapFooter() throws IOException  {
 275         out.println("</graph>");
 276         out.println("</gxl>");
 277     }
 278 
 279     //-- Internals only below this point
 280 
 281     // Java identifier to XML NMTOKEN type string
 282     private static String identifierToXMLName(String name) {
 283         // for now, just replace '$' with '_'
 284         return name.replace('$', '_');
 285     }
 286 
 287     // escapes XML meta-characters and illegal characters
 288     private static String escapeXMLChars(String s) {
 289         // FIXME: is there a better way or API?
 290         StringBuffer result = null;
 291         for(int i = 0, max = s.length(), delta = 0; i < max; i++) {
 292             char c = s.charAt(i);
 293             String replacement = null;
 294             if (c == '&') {
 295                 replacement = "&amp;";
 296             } else if (c == '<') {
 297                 replacement = "&lt;";
 298             } else if (c == '>') {
 299                 replacement = "&gt;";
 300             } else if (c == '"') {
 301                 replacement = "&quot;";
 302             } else if (c == '\'') {
 303                 replacement = "&apos;";
 304             } else if (c <  '\u0020' || (c > '\ud7ff' && c < '\ue000') ||
 305                        c == '\ufffe' || c == '\uffff') {
 306                 // These are illegal in XML -- put these in a CDATA section.
 307                 // Refer to section 2.2 Characters in XML specification at
 308                 // http://www.w3.org/TR/2004/REC-xml-20040204/
 309                 replacement = "<![CDATA[&#x" +
 310                     Integer.toHexString((int)c) + ";]]>";
 311             }
 312 
 313             if (replacement != null) {
 314                 if (result == null) {
 315                     result = new StringBuffer(s);
 316                 }
 317                 result.replace(i + delta, i + delta + 1, replacement);
 318                 delta += (replacement.length() - 1);
 319             }
 320         }
 321         if (result == null) {
 322             return s;
 323         }
 324         return result.toString();
 325     }
 326 
 327     private static String getID(Oop oop) {
 328         // address as unique id for node -- prefixed by "ID_".
 329         if (oop == null) {
 330             return "ID_NULL";
 331         } else {
 332             return "ID_" + oop.getHandle().toString();
 333         }
 334     }
 335 
 336     private void writeArrayLength(Array array) throws IOException {
 337         writeAttribute("length", "int",
 338                        Integer.toString((int) array.getLength()));
 339     }
 340 
 341     private void writeAttribute(String name, String type, String value) {
 342         out.print("\t<attr name='");
 343         out.print(name);
 344         out.print("'><");
 345         out.print(type);
 346         out.print('>');
 347         out.print(value);
 348         out.print("</");
 349         out.print(type);
 350         out.println("></attr>");
 351     }
 352 
 353     private void writeEdge(Oop from, Oop to, String name) throws IOException {
 354         out.print("<edge from='");
 355         out.print(getID(from));
 356         out.print("' to='");
 357         out.print(getID(to));
 358         out.println("'>");
 359         writeAttribute("name", "string", name);
 360         out.println("</edge>");
 361     }
 362 
 363     private void writeField(Field field, String type, String kind,
 364                             String value) throws IOException  {
 365         // 'type' is GXL type of the attribute
 366         // 'kind' is Java type code ("B", "C", "Z", "S", "I", "J", "F", "D")
 367         if (isArray) {
 368             out.print('\t');
 369         } else {
 370             out.print("\t<attr name='");
 371             String name = field.getID().getName();
 372             out.print(identifierToXMLName(name));
 373             out.print("' kind='");
 374             out.print(kind);
 375             out.print("'>");
 376         }
 377         out.print('<');
 378         out.print(type);
 379         out.print('>');
 380         out.print(value);
 381         out.print("</");
 382         out.print(type);
 383         out.print('>');
 384         if (isArray) {
 385             out.println();
 386         } else {
 387             out.println("</attr>");
 388         }
 389     }
 390 
 391     private void writeVMInfo() throws IOException {
 392         VM vm = VM.getVM();
 393         writeAttribute("vm-version", "string", vm.getVMRelease());
 394         writeAttribute("vm-type", "string",
 395                        (vm.isClientCompiler())? "client" :
 396                        ((vm.isServerCompiler())? "server" : "core"));
 397         writeAttribute("os", "string", vm.getOS());
 398         writeAttribute("cpu", "string", vm.getCPU());
 399         writeAttribute("pointer-size", "string",
 400                        Integer.toString((int)vm.getOopSize() * 8));
 401     }
 402 
 403     // XML encoding that we'll use
 404     private static final String ENCODING = "UTF-8";
 405 
 406     // reference fields of currently visited object
 407     private List/*<OopField>*/ refFields;
 408     // are we writing an array now?
 409     private boolean isArray;
 410     private PrintWriter out;
 411 }