FileDocCategorySizeDatePackage
XMLEncoder.javaAPI DocJava SE 6 API23809Tue Jun 10 00:25:32 BST 2008java.beans

XMLEncoder

public class XMLEncoder extends Encoder
The XMLEncoder class is a complementary alternative to the ObjectOutputStream and can used to generate a textual representation of a JavaBean in the same way that the ObjectOutputStream can be used to create binary representation of Serializable objects. For example, the following fragment can be used to create a textual representation the supplied JavaBean and all its properties:
XMLEncoder e = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream("Test.xml")));
e.writeObject(new JButton("Hello, world"));
e.close();
Despite the similarity of their APIs, the XMLEncoder class is exclusively designed for the purpose of archiving graphs of JavaBeans as textual representations of their public properties. Like Java source files, documents written this way have a natural immunity to changes in the implementations of the classes involved. The ObjectOutputStream continues to be recommended for interprocess communication and general purpose serialization.

The XMLEncoder class provides a default denotation for JavaBeans in which they are represented as XML documents complying with version 1.0 of the XML specification and the UTF-8 character encoding of the Unicode/ISO 10646 character set. The XML documents produced by the XMLEncoder class are:

  • Portable and version resilient: they have no dependencies on the private implementation of any class and so, like Java source files, they may be exchanged between environments which may have different versions of some of the classes and between VMs from different vendors.
  • Structurally compact: The XMLEncoder class uses a redundancy elimination algorithm internally so that the default values of a Bean's properties are not written to the stream.
  • Fault tolerant: Non-structural errors in the file, caused either by damage to the file or by API changes made to classes in an archive remain localized so that a reader can report the error and continue to load the parts of the document which were not affected by the error.

Below is an example of an XML archive containing some user interface components from the swing toolkit:

<?xml version="1.0" encoding="UTF-8"?>
<java version="1.0" class="java.beans.XMLDecoder">
<object class="javax.swing.JFrame">
<void property="name">
<string>frame1</string>
</void>
<void property="bounds">
<object class="java.awt.Rectangle">
<int>0</int>
<int>0</int>
<int>200</int>
<int>200</int>
</object>
</void>
<void property="contentPane">
<void method="add">
<object class="javax.swing.JButton">
<void property="label">
<string>Hello</string>
</void>
</object>
</void>
</void>
<void property="visible">
<boolean>true</boolean>
</void>
</object>
</java>
The XML syntax uses the following conventions:
  • Each element represents a method call.
  • The "object" tag denotes an expression whose value is to be used as the argument to the enclosing element.
  • The "void" tag denotes a statement which will be executed, but whose result will not be used as an argument to the enclosing method.
  • Elements which contain elements use those elements as arguments, unless they have the tag: "void".
  • The name of the method is denoted by the "method" attribute.
  • XML's standard "id" and "idref" attributes are used to make references to previous expressions - so as to deal with circularities in the object graph.
  • The "class" attribute is used to specify the target of a static method or constructor explicitly; its value being the fully qualified name of the class.
  • Elements with the "void" tag are executed using the outer context as the target if no target is defined by a "class" attribute.
  • Java's String class is treated specially and is written <string>Hello, world</string> where the characters of the string are converted to bytes using the UTF-8 character encoding.

Although all object graphs may be written using just these three tags, the following definitions are included so that common data structures can be expressed more concisely:

  • The default method name is "new".
  • A reference to a java class is written in the form <class>javax.swing.JButton</class>.
  • Instances of the wrapper classes for Java's primitive types are written using the name of the primitive type as the tag. For example, an instance of the Integer class could be written: <int>123</int>. Note that the XMLEncoder class uses Java's reflection package in which the conversion between Java's primitive types and their associated "wrapper classes" is handled internally. The API for the XMLEncoder class itself deals only with Objects.
  • In an element representing a nullary method whose name starts with "get", the "method" attribute is replaced with a "property" attribute whose value is given by removing the "get" prefix and decapitalizing the result.
  • In an element representing a monadic method whose name starts with "set", the "method" attribute is replaced with a "property" attribute whose value is given by removing the "set" prefix and decapitalizing the result.
  • In an element representing a method named "get" taking one integer argument, the "method" attribute is replaced with an "index" attribute whose value the value of the first argument.
  • In an element representing a method named "set" taking two arguments, the first of which is an integer, the "method" attribute is replaced with an "index" attribute whose value the value of the first argument.
  • A reference to an array is written using the "array" tag. The "class" and "length" attributes specify the sub-type of the array and its length respectively.

For more information you might also want to check out Using XMLEncoder, an article in The Swing Connection.

see
XMLDecoder
see
java.io.ObjectOutputStream
since
1.4
version
1.35 03/09/06
author
Philip Milne

Fields Summary
private static String
encoding
private OutputStream
out
private Object
owner
private int
indentation
private boolean
internal
private Map
valueToExpression
private Map
targetToStatementList
private boolean
preambleWritten
private NameGenerator
nameGenerator
Constructors Summary
public XMLEncoder(OutputStream out)
Creates a new output stream for sending JavaBeans to the stream out using an XML encoding.

param
out The stream to which the XML representation of the objects will be sent.
see
XMLDecoder#XMLDecoder(InputStream)

    

                                          
       
        this.out = out;
        valueToExpression = new IdentityHashMap();
        targetToStatementList = new IdentityHashMap();
	nameGenerator = new NameGenerator();
    
Methods Summary
voidclear()

	super.clear();
	nameGenerator.clear();
	valueToExpression.clear();
	targetToStatementList.clear();
    
public voidclose()
This method calls flush, writes the closing postamble and then closes the output stream associated with this stream.

        flush();
        writeln("</java>");
        try {
            out.close();
        }
        catch (IOException e) {
            getExceptionListener().exceptionThrown(e);
        }
    
private static java.lang.StringcreateString(int code)

        return "<char code=\"#" + Integer.toString(code, 16) + "\"/>";
    
private java.lang.StringcreateString(java.lang.String string)

        CharsetEncoder encoder = Charset.forName(encoding).newEncoder();

        StringBuilder sb = new StringBuilder();
        sb.append("<string>");
        int index = 0;
        while (index < string.length()) {
            int point = string.codePointAt(index);
            int count = Character.charCount(point);

            if (isValidCharCode(point) && encoder.canEncode(string.substring(index, index + count))) {
                String value = quoteCharCode(point);
                if (value != null) {
                    sb.append(value);
                } else {
                    sb.appendCodePoint(point);
                }
                index += count;
            } else {
                sb.append(createString(string.charAt(index)));
                index++;
            }
/*
            String value = isValidCharCode(point) && encoder.canEncode(string.substring(index, index + count))
                    ? quoteCharCode(point)
                    : createString(point);

            if (value != null) {
                sb.append(value);
            } else {
                sb.appendCodePoint(point);
            }
            index += count;
*/
        }
        sb.append("</string>");
        return sb.toString();
    
public voidflush()
This method writes out the preamble associated with the XML encoding if it has not been written already and then writes out all of the values that been written to the stream since the last time flush was called. After flushing, all internal references to the values that were written to this stream are cleared.

	if (!preambleWritten) { // Don't do this in constructor - it throws ... pending.
	    writeln("<?xml version=" + quote("1.0") +
                        " encoding=" + quote(encoding) + "?>");
	    writeln("<java version=" + quote(System.getProperty("java.version")) +
	                   " class=" + quote(XMLDecoder.class.getName()) + ">");
	    preambleWritten = true;
	}
	indentation++;
	Vector roots = statementList(this);
	for(int i = 0; i < roots.size(); i++) {
	    Statement s = (Statement)roots.get(i);
	    if ("writeObject".equals(s.getMethodName())) {
	        outputValue(s.getArguments()[0], this, true);
            }
	    else {
	        outputStatement(s, this, false);
	    }
	}
	indentation--;

	try {
	    out.flush();
	}
        catch (IOException e) {
	    getExceptionListener().exceptionThrown(e);
	}
	clear();
    
public java.lang.ObjectgetOwner()
Gets the owner of this encoder.

return
The owner of this encoder.
see
#setOwner

	return owner;
    
private java.beans.XMLEncoder$ValueDatagetValueData(java.lang.Object o)

        ValueData d = (ValueData)valueToExpression.get(o);
        if (d == null) {
            d = new ValueData();
            valueToExpression.put(o, d);
        }
        return d;
    
private static booleanisValidCharCode(int code)
Returns true if the argument, a Unicode code point, is valid in XML documents. Unicode characters fit into the low sixteen bits of a Unicode code point, and pairs of Unicode surrogate characters can be combined to encode Unicode code point in documents containing only Unicode. (The char datatype in the Java Programming Language represents Unicode characters, including unpaired surrogates.) [2] Char ::= #x0009 | #x000A | #x000D | [#x0020-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10ffff]

param
code the 32-bit Unicode code point being tested
return
true if the Unicode code point is valid, false otherwise

        return (0x0020 <= code && code <= 0xD7FF)
            || (0x000A == code)
            || (0x0009 == code)
            || (0x000D == code)
            || (0xE000 <= code && code <= 0xFFFD)
            || (0x10000 <= code && code <= 0x10ffff);
    
private voidmark(java.lang.Object o, boolean isArgument)

        if (o == null || o == this) {
            return;
        }
        ValueData d = getValueData(o);
        Expression exp = d.exp; 
        // Do not mark liternal strings. Other strings, which might,  
        // for example, come from resource bundles should still be marked. 
        if (o.getClass() == String.class && exp == null) { 
            return; 
        } 
        
        // Bump the reference counts of all arguments
        if (isArgument) {
            d.refs++;
        }
        if (d.marked) {
            return;
        }
        d.marked = true;
        Object target = exp.getTarget();
        if (!(target instanceof Class)) {
            statementList(target).add(exp);
	    // Pending: Why does the reference count need to
	    // be incremented here?
            d.refs++;
        }
        mark(exp);
    
private voidmark(java.beans.Statement stm)

        Object[] args = stm.getArguments();
        for (int i = 0; i < args.length; i++) {
            Object arg = args[i];
            mark(arg, true);
        }
        mark(stm.getTarget(), false);
    
private voidoutputStatement(java.beans.Statement exp, java.lang.Object outer, boolean isArgument)

        Object target = exp.getTarget();
        String methodName = exp.getMethodName();

	if (target == null || methodName == null) {
	    throw new NullPointerException((target == null ? "target" : 
					    "methodName") + " should not be null");
	}

        Object[] args = exp.getArguments();
        boolean expression = exp.getClass() == Expression.class;
        Object value = (expression) ? getValue((Expression)exp) : null;

        String tag = (expression && isArgument) ? "object" : "void";
        String attributes = "";
        ValueData d = getValueData(value);
        if (expression) {
            if (d.refs > 1) {
                String instanceName = nameGenerator.instanceName(value);
                d.name = instanceName;
                attributes = attributes + " id=" + quote(instanceName);
            }
        }

        // Special cases for targets.
        if (target == outer) {
        }
        else if (target == Array.class && methodName.equals("newInstance")) {
            tag = "array";
            attributes = attributes + " class=" + quote(((Class)args[0]).getName());
            attributes = attributes + " length=" + quote(args[1].toString());
            args = new Object[]{};
        }
        else if (target.getClass() == Class.class) {
            attributes = attributes + " class=" + quote(((Class)target).getName());
        }
        else {
            d.refs = 2;
            outputValue(target, outer, false);
            outputValue(value, outer, false);
            return;
        }


        // Special cases for methods.
        if ((!expression && methodName.equals("set") && args.length == 2 && 
	     args[0] instanceof Integer) ||
             (expression && methodName.equals("get") && args.length == 1 && 
	      args[0] instanceof Integer)) {
            attributes = attributes + " index=" + quote(args[0].toString());
            args = (args.length == 1) ? new Object[]{} : new Object[]{args[1]};
        }
        else if ((!expression && methodName.startsWith("set") && args.length == 1) ||
		 (expression && methodName.startsWith("get") && args.length == 0)) {
	    attributes = attributes + " property=" + 
		quote(Introspector.decapitalize(methodName.substring(3)));
        }
        else if (!methodName.equals("new") && !methodName.equals("newInstance")) {
            attributes = attributes + " method=" + quote(methodName);
        }

        Vector statements = statementList(value);
        // Use XML's short form when there is no body.
        if (args.length == 0 && statements.size() == 0) {
            writeln("<" + tag + attributes + "/>");
            return;
        }

        writeln("<" + tag + attributes + ">");
        indentation++;

        for(int i = 0; i < args.length; i++) {
            outputValue(args[i], null, true);
        }

        for(int i = 0; i < statements.size(); i++) {
            Statement s = (Statement)statements.get(i);
            outputStatement(s, value, false);
        }

        indentation--;
        writeln("</" + tag + ">");
    
private voidoutputValue(java.lang.Object value, java.lang.Object outer, boolean isArgument)

        if (value == null) {
            writeln("<null/>");
            return;
        }

        if (value instanceof Class) {
            writeln("<class>" + ((Class)value).getName() + "</class>");
            return;
        }

        ValueData d = getValueData(value);         
        if (d.exp != null) {
	    Object target = d.exp.getTarget();
	    String methodName = d.exp.getMethodName();

	    if (target == null || methodName == null) {
		throw new NullPointerException((target == null ? "target" : 
						"methodName") + " should not be null");
	    }

	    if (target instanceof Field && methodName.equals("get")) {
		Field f = (Field)target; 
		writeln("<object class=" + quote(f.getDeclaringClass().getName()) + 
			" field=" + quote(f.getName()) + "/>");
		return;
	    }
        
	    Class primitiveType = ReflectionUtils.primitiveTypeFor(value.getClass());
	    if (primitiveType != null && target == value.getClass() && 
		methodName.equals("new")) {
		String primitiveTypeName = primitiveType.getName();
		// Make sure that character types are quoted correctly.
		if (primitiveType == Character.TYPE) {
                    char code = ((Character) value).charValue();
                    if (!isValidCharCode(code)) {
                        writeln(createString(code));
                        return;
                    }
                    value = quoteCharCode(code);
                    if (value == null) {
                        value = Character.valueOf(code);
                    }
		}
		writeln("<" + primitiveTypeName + ">" + value + "</" + 
			primitiveTypeName + ">");
		return;
	    }

	} else if (value instanceof String) {
            writeln(createString((String) value));
            return;
        }

        if (d.name != null) {
            writeln("<object idref=" + quote(d.name) + "/>");
            return;
        }

        outputStatement(d.exp, outer, isArgument);
    
private java.lang.Stringquote(java.lang.String s)

        return "\"" + s + "\"";
    
private static java.lang.StringquoteCharCode(int code)

        switch(code) {
          case '&":  return "&";
          case '<":  return "<";
          case '>":  return ">";
          case '"":  return """;
          case '\'": return "'";
          case '\r": return "
";
          default:   return null;
        }
    
public voidsetOwner(java.lang.Object owner)
Sets the owner of this encoder to owner.

param
owner The owner of this encoder.
see
#getOwner

        this.owner = owner;
        writeExpression(new Expression(this, "getOwner", new Object[0]));
    
private java.util.VectorstatementList(java.lang.Object target)

        Vector list = (Vector)targetToStatementList.get(target);
        if (list != null) {
            return list;
        }
        list = new Vector();
        targetToStatementList.put(target, list);
        return list;
    
public voidwriteExpression(java.beans.Expression oldExp)
Records the Expression so that the Encoder will produce the actual output when the stream is flushed.

This method should only be invoked within the context of initializing a persistence delegate or setting up an encoder to read from a resource bundle.

For more information about using resource bundles with the XMLEncoder, see http://java.sun.com/products/jfc/tsc/articles/persistence4/#i18n

param
oldExp The expression that will be written to the stream.
see
java.beans.PersistenceDelegate#initialize

        boolean internal = this.internal;
        this.internal = true;
        Object oldValue = getValue(oldExp);
        if (get(oldValue) == null || (oldValue instanceof String && !internal)) {
            getValueData(oldValue).exp = oldExp;
            super.writeExpression(oldExp);
        }
        this.internal = internal;
    
public voidwriteObject(java.lang.Object o)
Write an XML representation of the specified object to the output.

param
o The object to be written to the stream.
see
XMLDecoder#readObject

        if (internal) {
            super.writeObject(o);
        }
        else {
            writeStatement(new Statement(this, "writeObject", new Object[]{o}));
        }
    
public voidwriteStatement(java.beans.Statement oldStm)
Records the Statement so that the Encoder will produce the actual output when the stream is flushed.

This method should only be invoked within the context of initializing a persistence delegate.

param
oldStm The statement that will be written to the stream.
see
java.beans.PersistenceDelegate#initialize

    	// System.out.println("XMLEncoder::writeStatement: " + oldStm);
        boolean internal = this.internal;
        this.internal = true;
        try {
            super.writeStatement(oldStm);
	    /*
	       Note we must do the mark first as we may
	       require the results of previous values in
	       this context for this statement.
	       Test case is:
	           os.setOwner(this);
	           os.writeObject(this);
            */
	    mark(oldStm);
            statementList(oldStm.getTarget()).add(oldStm);
        }
        catch (Exception e) {
            getExceptionListener().exceptionThrown(new Exception("XMLEncoder: discarding statement " + oldStm, e));
        }
        this.internal = internal;
    
private voidwriteln(java.lang.String exp)

        try {
            for(int i = 0; i < indentation; i++) {
                out.write(' ");
            }
            out.write(exp.getBytes(encoding));
            out.write(" \n".getBytes(encoding));
        }
        catch (IOException e) {
            getExceptionListener().exceptionThrown(e);
        }