/*
* @(#)XMLEncoder.java 1.33 03/12/19
*
* Copyright 2004 Sun Microsystems, Inc. All rights reserved.
* SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/
package java.beans;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
/**
* 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:
*
XMLEncoder
class
* uses a redundancy elimination algorithm internally so that the
* default values of a Bean's properties are not written to the stream.
* * 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: *
* 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: *
*
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 Object
s.
*
* 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.33 12/19/03
* @author Philip Milne
*/
public class XMLEncoder extends Encoder {
private static String encoding = "UTF-8";
private OutputStream out;
private Object owner;
private int indentation = 0;
private boolean internal = false;
private Map valueToExpression;
private Map targetToStatementList;
private boolean preambleWritten = false;
private NameGenerator nameGenerator;
private class ValueData {
public int refs = 0;
public boolean marked = false; // Marked -> refs > 0 unless ref was a target.
public String name = null;
public Expression exp = null;
}
/**
* 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)
*/
public XMLEncoder(OutputStream out) {
this.out = out;
valueToExpression = new IdentityHashMap();
targetToStatementList = new IdentityHashMap();
nameGenerator = new NameGenerator();
}
/**
* Sets the owner of this encoder to owner
.
*
* @param owner The owner of this encoder.
*
* @see #getOwner
*/
public void setOwner(Object owner) {
this.owner = owner;
writeExpression(new Expression(this, "getOwner", new Object[0]));
}
/**
* Gets the owner of this encoder.
*
* @return The owner of this encoder.
*
* @see #setOwner
*/
public Object getOwner() {
return owner;
}
/**
* Write an XML representation of the specified object to the output.
*
* @param o The object to be written to the stream.
*
* @see XMLDecoder#readObject
*/
public void writeObject(Object o) {
if (internal) {
super.writeObject(o);
}
else {
writeStatement(new Statement(this, "writeObject", new Object[]{o}));
}
}
private Vector statementList(Object target) {
Vector list = (Vector)targetToStatementList.get(target);
if (list != null) {
return list;
}
list = new Vector();
targetToStatementList.put(target, list);
return list;
}
private void mark(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 void mark(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);
}
/**
* 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 */ public void writeStatement(Statement oldStm) { // 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; } /** * 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
*/
public void writeExpression(Expression oldExp) {
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;
}
/**
* 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.
*/
public void flush() {
if (!preambleWritten) { // Don't do this in constructor - it throws ... pending.
writeln("");
writeln("flush
, writes the closing
* postamble and then closes the output stream associated
* with this stream.
*/
public void close() {
flush();
writeln("