/*
* Copyright (c) 2000, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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 java.beans;
import java.io.*;
import java.util.*;
import java.lang.reflect.*;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
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 Object
s.
-
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.
Author: Philip Milne See Also: - XMLDecoder
- ObjectOutputStream
Since: 1.4
/**
* The <code>XMLEncoder</code> class is a complementary alternative to
* the <code>ObjectOutputStream</code> and can used to generate
* a textual representation of a <em>JavaBean</em> in the same
* way that the <code>ObjectOutputStream</code> can
* be used to create binary representation of <code>Serializable</code>
* objects. For example, the following fragment can be used to create
* a textual representation the supplied <em>JavaBean</em>
* and all its properties:
* <pre>
* XMLEncoder e = new XMLEncoder(
* new BufferedOutputStream(
* new FileOutputStream("Test.xml")));
* e.writeObject(new JButton("Hello, world"));
* e.close();
* </pre>
* Despite the similarity of their APIs, the <code>XMLEncoder</code>
* class is exclusively designed for the purpose of archiving graphs
* of <em>JavaBean</em>s 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 <code>ObjectOutputStream</code> continues to be recommended
* for interprocess communication and general purpose serialization.
* <p>
* The <code>XMLEncoder</code> class provides a default denotation for
* <em>JavaBean</em>s 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 <code>XMLEncoder</code> class are:
* <ul>
* <li>
* <em>Portable and version resilient</em>: 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.
* <li>
* <em>Structurally compact</em>: The <code>XMLEncoder</code> class
* uses a <em>redundancy elimination</em> algorithm internally so that the
* default values of a Bean's properties are not written to the stream.
* <li>
* <em>Fault tolerant</em>: 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.
* </ul>
* <p>
* Below is an example of an XML archive containing
* some user interface components from the <em>swing</em> toolkit:
* <pre>
* <?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>
* </pre>
* The XML syntax uses the following conventions:
* <ul>
* <li>
* Each element represents a method call.
* <li>
* The "object" tag denotes an <em>expression</em> whose value is
* to be used as the argument to the enclosing element.
* <li>
* The "void" tag denotes a <em>statement</em> which will
* be executed, but whose result will not be used as an
* argument to the enclosing method.
* <li>
* Elements which contain elements use those elements as arguments,
* unless they have the tag: "void".
* <li>
* The name of the method is denoted by the "method" attribute.
* <li>
* 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.
* <li>
* 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.
* <li>
* Elements with the "void" tag are executed using
* the outer context as the target if no target is defined
* by a "class" attribute.
* <li>
* 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.
* </ul>
* <p>
* 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:
* <p>
* <ul>
* <li>
* The default method name is "new".
* <li>
* A reference to a java class is written in the form
* <class>javax.swing.JButton</class>.
* <li>
* 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 <code>Integer</code> class could be written:
* <int>123</int>. Note that the <code>XMLEncoder</code> 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 <code>XMLEncoder</code> class
* itself deals only with <code>Object</code>s.
* <li>
* 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.
* <li>
* 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.
* <li>
* 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.
* <li>
* 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.
* <li>
* 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.
* </ul>
*
*<p>
* For more information you might also want to check out
* <a
href="http://java.sun.com/products/jfc/tsc/articles/persistence4">Using XMLEncoder</a>,
* an article in <em>The Swing Connection.</em>
* @see XMLDecoder
* @see java.io.ObjectOutputStream
*
* @since 1.4
*
* @author Philip Milne
*/
public class XMLEncoder extends Encoder implements AutoCloseable {
private final CharsetEncoder encoder;
private final String charset;
private final boolean declaration;
private OutputStreamWriter out;
private Object owner;
private int indentation = 0;
private boolean internal = false;
private Map<Object, ValueData> valueToExpression;
private Map<Object, List<Statement>> 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 XML encoder to write out JavaBeans
to the stream out
using an XML encoding.
Params: - out – the stream to which the XML representation of
the objects will be written
Throws: - IllegalArgumentException –
if
out
is null
See Also:
/**
* Creates a new XML encoder to write out <em>JavaBeans</em>
* to the stream <code>out</code> using an XML encoding.
*
* @param out the stream to which the XML representation of
* the objects will be written
*
* @throws IllegalArgumentException
* if <code>out</code> is <code>null</code>
*
* @see XMLDecoder#XMLDecoder(InputStream)
*/
public XMLEncoder(OutputStream out) {
this(out, "UTF-8", true, 0);
}
Creates a new XML encoder to write out JavaBeans
to the stream out
using the given charset
starting from the given indentation
.
Params: - out – the stream to which the XML representation of
the objects will be written
- charset – the name of the requested charset;
may be either a canonical name or an alias
- declaration – whether the XML declaration should be generated;
set this to
false
when embedding the contents in another XML document - indentation – the number of space characters to indent the entire XML document by
Throws: - IllegalArgumentException –
if
out
or charset
is null
,
or if indentation
is less than 0 - IllegalCharsetNameException –
if
charset
name is illegal - UnsupportedCharsetException –
if no support for the named charset is available
in this instance of the Java virtual machine
- UnsupportedOperationException –
if loaded charset does not support encoding
See Also: Since: 1.7
/**
* Creates a new XML encoder to write out <em>JavaBeans</em>
* to the stream <code>out</code> using the given <code>charset</code>
* starting from the given <code>indentation</code>.
*
* @param out the stream to which the XML representation of
* the objects will be written
* @param charset the name of the requested charset;
* may be either a canonical name or an alias
* @param declaration whether the XML declaration should be generated;
* set this to <code>false</code>
* when embedding the contents in another XML document
* @param indentation the number of space characters to indent the entire XML document by
*
* @throws IllegalArgumentException
* if <code>out</code> or <code>charset</code> is <code>null</code>,
* or if <code>indentation</code> is less than 0
*
* @throws IllegalCharsetNameException
* if <code>charset</code> name is illegal
*
* @throws UnsupportedCharsetException
* if no support for the named charset is available
* in this instance of the Java virtual machine
*
* @throws UnsupportedOperationException
* if loaded charset does not support encoding
*
* @see Charset#forName(String)
*
* @since 1.7
*/
public XMLEncoder(OutputStream out, String charset, boolean declaration, int indentation) {
if (out == null) {
throw new IllegalArgumentException("the output stream cannot be null");
}
if (indentation < 0) {
throw new IllegalArgumentException("the indentation must be >= 0");
}
Charset cs = Charset.forName(charset);
this.encoder = cs.newEncoder();
this.charset = charset;
this.declaration = declaration;
this.indentation = indentation;
this.out = new OutputStreamWriter(out, cs.newEncoder());
valueToExpression = new IdentityHashMap<>();
targetToStatementList = new IdentityHashMap<>();
nameGenerator = new NameGenerator();
}
Sets the owner of this encoder to owner
.
Params: - owner – The owner of this encoder.
See Also:
/**
* Sets the owner of this encoder to <code>owner</code>.
*
* @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.
See Also: Returns: The owner of this encoder.
/**
* 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.
Params: - o – The object to be written to the stream.
See Also:
/**
* 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 List<Statement> statementList(Object target) {
List<Statement> list = targetToStatementList.get(target);
if (list == null) {
list = new ArrayList<>();
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();
mark(exp);
if (!(target instanceof Class)) {
statementList(target).add(exp);
// Pending: Why does the reference count need to
// be incremented here?
d.refs++;
}
}
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(), stm instanceof Expression);
}
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.
Params: - oldStm – The statement that will be written
to the stream.
See Also:
/**
* Records the Statement so that the Encoder will
* produce the actual output when the stream is flushed.
* <P>
* 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);
Object target = oldStm.getTarget();
if (target instanceof Field) {
String method = oldStm.getMethodName();
Object[] args = oldStm.getArguments();
if ((method == null) || (args == null)) {
}
else if (method.equals("get") && (args.length == 1)) {
target = args[0];
}
else if (method.equals("set") && (args.length == 2)) {
target = args[0];
}
}
statementList(target).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
Params: - oldExp – The expression that will be written
to the stream.
See Also:
/**
* Records the Expression so that the Encoder will
* produce the actual output when the stream is flushed.
* <P>
* 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.
* <P>
* 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.
/**
* 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 <code>flush</code>
* 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.
if (this.declaration) {
writeln("<?xml version=" + quote("1.0") +
" encoding=" + quote(this.charset) + "?>");
}
writeln("<java version=" + quote(System.getProperty("java.version")) +
" class=" + quote(XMLDecoder.class.getName()) + ">");
preambleWritten = true;
}
indentation++;
List<Statement> statements = statementList(this);
while (!statements.isEmpty()) {
Statement s = statements.remove(0);
if ("writeObject".equals(s.getMethodName())) {
outputValue(s.getArguments()[0], this, true);
}
else {
outputStatement(s, this, false);
}
}
indentation--;
Statement statement = getMissedStatement();
while (statement != null) {
outputStatement(statement, this, false);
statement = getMissedStatement();
}
try {
out.flush();
}
catch (IOException e) {
getExceptionListener().exceptionThrown(e);
}
clear();
}
void clear() {
super.clear();
nameGenerator.clear();
valueToExpression.clear();
targetToStatementList.clear();
}
Statement getMissedStatement() {
for (List<Statement> statements : this.targetToStatementList.values()) {
for (int i = 0; i < statements.size(); i++) {
if (Statement.class == statements.get(i).getClass()) {
return statements.remove(i);
}
}
}
return null;
}
This method calls flush
, writes the closing
postamble and then closes the output stream associated
with this stream.
/**
* This method calls <code>flush</code>, writes the closing
* postamble and then closes the output stream associated
* with this stream.
*/
public void close() {
flush();
writeln("</java>");
try {
out.close();
}
catch (IOException e) {
getExceptionListener().exceptionThrown(e);
}
}
private String quote(String s) {
return "\"" + s + "\"";
}
private ValueData getValueData(Object o) {
ValueData d = valueToExpression.get(o);
if (d == null) {
d = new ValueData();
valueToExpression.put(o, d);
}
return d;
}
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]
Params: - code – the 32-bit Unicode code point being tested
Returns: true
if the Unicode code point is valid,
false
otherwise
/**
* Returns <code>true</code> 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 <em>surrogate characters</em> can be combined
* to encode Unicode code point in documents containing only Unicode.
* (The <code>char</code> datatype in the Java Programming Language
* represents Unicode characters, including unpaired surrogates.)
* <par>
* [2] Char ::= #x0009 | #x000A | #x000D
* | [#x0020-#xD7FF]
* | [#xE000-#xFFFD]
* | [#x10000-#x10ffff]
* </par>
*
* @param code the 32-bit Unicode code point being tested
* @return <code>true</code> if the Unicode code point is valid,
* <code>false</code> otherwise
*/
private static boolean isValidCharCode(int code) {
return (0x0020 <= code && code <= 0xD7FF)
|| (0x000A == code)
|| (0x0009 == code)
|| (0x000D == code)
|| (0xE000 <= code && code <= 0xFFFD)
|| (0x10000 <= code && code <= 0x10ffff);
}
private void writeln(String exp) {
try {
StringBuilder sb = new StringBuilder();
for(int i = 0; i < indentation; i++) {
sb.append(' ');
}
sb.append(exp);
sb.append('\n');
this.out.write(sb.toString());
}
catch (IOException e) {
getExceptionListener().exceptionThrown(e);
}
}
private void outputValue(Object value, 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 (isArgument && target instanceof Field && methodName.equals("get")) {
Field f = (Field)target;
writeln("<object class=" + quote(f.getDeclaringClass().getName()) +
" field=" + quote(f.getName()) + "/>");
return;
}
Class<?> primitiveType = 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) {
if (isArgument) {
writeln("<object idref=" + quote(d.name) + "/>");
}
else {
outputXML("void", " idref=" + quote(d.name), value);
}
}
else if (d.exp != null) {
outputStatement(d.exp, outer, isArgument);
}
}
private static String quoteCharCode(int code) {
switch(code) {
case '&': return "&";
case '<': return "<";
case '>': return ">";
case '"': return """;
case '\'': return "'";
case '\r': return " ";
default: return null;
}
}
private static String createString(int code) {
return "<char code=\"#" + Integer.toString(code, 16) + "\"/>";
}
private String createString(String string) {
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) && this.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++;
}
}
sb.append("</string>");
return sb.toString();
}
private void outputStatement(Statement exp, 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);
// 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;
if (d.name == null) {
getValueData(target).refs++;
List<Statement> statements = statementList(target);
if (!statements.contains(exp)) {
statements.add(exp);
}
outputValue(target, outer, false);
}
if (expression) {
outputValue(value, outer, isArgument);
}
return;
}
if (expression && (d.refs > 1)) {
String instanceName = nameGenerator.instanceName(value);
d.name = instanceName;
attributes = attributes + " id=" + quote(instanceName);
}
// 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)) {
if (3 < methodName.length()) {
attributes = attributes + " property=" +
quote(Introspector.decapitalize(methodName.substring(3)));
}
}
else if (!methodName.equals("new") && !methodName.equals("newInstance")) {
attributes = attributes + " method=" + quote(methodName);
}
outputXML(tag, attributes, value, args);
}
private void outputXML(String tag, String attributes, Object value, Object... args) {
List<Statement> 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);
}
while (!statements.isEmpty()) {
Statement s = statements.remove(0);
outputStatement(s, value, false);
}
indentation--;
writeln("</" + tag + ">");
}
@SuppressWarnings("rawtypes")
static Class primitiveTypeFor(Class wrapper) {
if (wrapper == Boolean.class) return Boolean.TYPE;
if (wrapper == Byte.class) return Byte.TYPE;
if (wrapper == Character.class) return Character.TYPE;
if (wrapper == Short.class) return Short.TYPE;
if (wrapper == Integer.class) return Integer.TYPE;
if (wrapper == Long.class) return Long.TYPE;
if (wrapper == Float.class) return Float.TYPE;
if (wrapper == Double.class) return Double.TYPE;
if (wrapper == Void.class) return Void.TYPE;
return null;
}
}