com.sun.xml.io.XmlInStream Maven / Gradle / Ivy
/*
* @(#)XmlInStream.java 1.20 99/02/05
*
* Copyright (c) 1998 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*/
package com.sun.xml.io;
import java.beans.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.URL;
import java.net.URLConnection;
// import java.net.URLClassLoader; // JDK 1.2 specific!
import java.util.Enumeration;
import java.util.Hashtable;
import org.w3c.dom.*;
import org.xml.sax.*;
import org.xml.sax.helpers.ParserFactory;
import com.sun.xml.parser.Resolver;
import com.sun.xml.parser.ValidatingParser;
import com.sun.xml.tree.*;
/**
* This parses XML documents following an experimental DTD for
* externalized JavaBeans and related data. Note that the reading
* and writing of the data must be closely synchronized; it is not
* permissible to perform any kind of "type punning" through this
* interface. What's written as an integer must be read as such,
* not as four bytes that are then recombined; and vice versa.
* (This and other reasons may make it undesirable to use the
* java.io.Object{In,Out}put
interfaces, which seem to
* assume such things will be done. Notice the deprecated methods.)
*
* The initial version makes no claims to efficiency, since it
* reads the whole thing into a parse tree, then dissects it. A
* more efficient implementation would coroutine this with the parser.
*
*
The implementation's use of a new JDK 1.2 feature, the class
* java.net.URLClassLoader
, is currently disabled. When
* enabled, the <STREAM ARCHIVE=...> feature permits the
* bean classes in this stream to be loaded from the specified archive.
*
* @see XmlOutStream
*
* @author David Brownell
* @version 1.20
*/
public class XmlInStream
implements ObjectInput
{
/**
* This is the name of the Java resource that holds the DTD
* used for these "published" object streams.
*/
private final static String resourceName = "com/sun/xml/io/stream.dtd";
private InputStream in;
private Base64Decoder decoder;
private ClassLoader loader;
private ElementNode serial;
private NodeList next;
private int index;
private Hashtable mapping = new Hashtable ();
/**
* Constructs a stream that may be used to read data in the format
* written by XmlOutStream. At this time it requires the
* input stream to be a valid XML document, and reports errors if
* that document doesn't match the structure which it supports.
*/
public XmlInStream (InputStream in) throws IOException
{
try {
Parser parser = new ValidatingParser ();
// Parser parser = ParserFactory.makeParser ();
XmlDocumentBuilder builder = new XmlDocumentBuilder ();
Resolver resolver = new Resolver ();
XmlDocument document;
builder.setIgnoringLexicalInfo (true);
parser.setDocumentHandler (builder);
resolver.registerCatalogEntry (XmlOutStream.publicId,
resourceName, this.getClass ().getClassLoader ());
parser.setEntityResolver (resolver);
parser.parse (new InputSource (in));
document = builder.getDocument ();
serial = (ElementNode) document.getDocumentElement ();
if (!"STREAM".equals (serial.getTagName ()))
throw new XmlStreamException ("wrong document tag");
loader = getClassLoader (serial.getAttribute ("ARCHIVE"));
next = serial.getChildNodes ();
index = 0;
this.in = in;
} catch (IOException e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (SAXException e) {
Exception x = e.getException ();
if (x == null)
x = e;
if (e instanceof SAXParseException) {
SAXParseException sex = (SAXParseException) e;
System.out.println ("uri: " + sex.getSystemId ());
System.out.println ("line: " + sex.getLineNumber ());
}
x.printStackTrace ();
throw new XmlStreamException (x.getMessage ());
} catch (Exception e) {
e.printStackTrace ();
throw new XmlStreamException (e.getMessage ());
}
}
private ClassLoader getClassLoader (String name)
throws IOException
{
// System.err.println ("ARCHIVE: " + name);
// XXX JDK 1.2 only!
// return new URLClassLoader (new URL [] { new URL (name) } );
return null;
}
private Node advance () throws IOException
{
if (decoder != null)
throw new XmlStreamException ("opaque data pending");
return next.item (index++);
}
private ElementNode element () throws IOException
{
Node n;
//
// Skip to the next element.
//
do {
n = advance ();
if (n == null)
return null;
} while (!(n instanceof ElementNode));
return (ElementNode) n;
}
private ElementNode advance (String type) throws IOException
{
ElementNode e = element ();
if (e != null && !type.equals (e.getTagName ()))
throw new XmlStreamException ("expected tag = " + type
+ " not " + e.getTagName ());
return e;
}
private String value (ElementNode e) throws IOException
{
// This knows that all the values in this DTD are "V" attributes
return e.getAttribute ("V");
}
private String value (String type) throws IOException
{
ElementNode e = advance (type);
if (e == null)
throw new XmlStreamException ("expected '" + type + "' element");
return value (e);
}
/**
* ObjectInput method.
* @deprecated can't meaningfully be used on structured data
*/
public long skip (long l) throws IOException
{
// XXX could skip opaque data ...
throw new XmlStreamException ("Can't skip XmlInStream data");
}
/**
* DataInput method.
* @deprecated can't meaningfully be used on structured data
*/
public int skipBytes (int n) throws IOException
{
return (int) skip (n);
}
/**
* ObjectInput method.
* @deprecated can't meaningfully be used on structured data
*/
public int available () throws IOException
{
// XXX could say how much opaque data was available
return 0;
}
/**
* Closes the stream, reclaiming resources.
*/
public void close () throws IOException
{
serial = null;
next = null;
index = 0;
mapping = null;
in.close ();
}
// BOOLEAN
/**
* Returns a boolean value read from the input stream.
*/
public boolean readBoolean () throws IOException
{
return "1".equals (value ("boolean"));
}
// INTEGRAL TYPES
/**
* Returns a byte value read from the input stream.
*/
public int read () throws IOException
{
return Byte.parseByte (value ("i1"));
}
/**
* Returns a byte value read from the input stream.
*/
public byte readByte () throws IOException
{
return (byte) read ();
}
/**
* Returns a byte value read from the input stream, with
* no sign extension performed.
*/
public int readUnsignedByte () throws IOException
{
return read ();
}
/**
* Returns a short value read from the input stream.
*/
public short readShort () throws IOException
{
return Short.parseShort (value ("i2"));
}
/**
* Returns a short value read from the input stream, with
* no sign extension performed.
*/
public int readUnsignedShort () throws IOException
{
return 0x0ffff & readShort ();
}
/**
* Returns an integer value read from the input stream.
*/
public int readInt () throws IOException
{
return Integer.parseInt (value ("i4"));
}
/**
* Returns a long value read from the input stream.
*/
public long readLong () throws IOException
{
return Long.parseLong (value ("i8"));
}
// FLOATING POINT TYPES
/**
* Returns a floating point value read from the input stream.
*/
public float readFloat () throws IOException
{
return Float.valueOf (value ("r4")).floatValue ();
}
/**
* Returns a double width floating point value read from the
* input stream.
*/
public double readDouble () throws IOException
{
return Double.valueOf (value ("r8")).doubleValue ();
}
// CHARACTER/STRING TYPES
/**
* Returns a character value read from the input stream. This is a
* Java character (and so could be a single UNICODE surrogate), not
* an XML character (which might need to be expressed in a pair of
* UNICODE characters).
*/
public char readChar () throws IOException
{
//
// NOTE: This needs to be a numeric value at least part of the
// time, since there are chunks of UNICODE characters which are
// disallowed (like most control characters) and single surrogate
// characters are mishandled by at least UTF-8 readers. The XML
// notion of character isn't Java's notion!!
//
return (char) Short.parseShort (value ("c"));
}
/**
* DataInput method.
* @deprecated can't meaningfully be used on structured data,
* when DataOutput has no corresponding writeLine method
*/
public String readLine () throws IOException
{
throw new XmlStreamException ("Can't read lines from XmlInStream");
}
// So, no analogue of writeBytes, writeChars ...
/**
* Returns a String value read from the input stream. NOTE:
* This can return strings much longer than the approximately
* 64Kbyte limit imposed by the java.io.DataInputStream
* implementation in wide use. As the only primitive for parsing
* arbitrary text data, such a restriction is unreasonable.
*
*
Another current bug: Since Java's text model isn't the
* same as XML's, we need to be able to escape some characters from
* processing by XML (control characters) and UTF input streams
* (unpaired surrogates, etc).
*/
public String readUTF () throws IOException
{
ElementNode e = element ();
Node n;
if ("NULL".equals (e.getTagName ()))
return null;
else if (!"STRING".equals (e.getTagName ()))
throw new XmlStreamException ("expecting a string value");
//
// Construct the result string from #PCDATA and "c" nodes
//
String retval = "";
for (n = e.getFirstChild ();
n != null;
n = n.getNextSibling ()) {
switch (n.getNodeType ()) {
case Element.TEXT_NODE:
retval += n.getNodeValue ();
continue;
case Element.ELEMENT_NODE:
retval += (char) Short.parseShort (value ((ElementNode)n));
continue;
default:
throw new XmlStreamException ("expecting text or ");
}
}
return retval;
}
// OTHER
/**
* Reads Base64 encoded opaque binary data. It is the caller's
* responsibility to know how much data was written, and not to
* attempt to read more than that amount of data.
*/
public int read (byte buf [], int off, int len) throws IOException
{
int retval = -1;
while (retval == -1) {
if (decoder == null) {
Node n = advance ("OPAQUE");
// n.b. we actually have the byte count available ...
if (n == null)
throw new XmlStreamException ("empty opaque element");
// base64 text
((Element)n).normalize ();
n = n.getFirstChild ();
if (!(n instanceof Text))
throw new XmlStreamException ("expected text");
decoder = new Base64Decoder (new StringReader (n.toString ()));
}
retval = decoder.read (buf, off, len);
if (decoder.isEOF ()) {
decoder = null;
}
}
return retval;
}
/**
* Fills as much of the buffer as possible; shorthand
* for read (buf, 0, buf.length)
.
*/
public int read (byte buf []) throws IOException
{ return read (buf, 0, buf.length); }
/**
* Fills the buffer.
*/
public void readFully (byte buf []) throws IOException
{
readFully (buf, 0, buf.length);
}
/**
* Fills the specified parts of the buffer.
*/
public void readFully (byte buffer [], int offset, int length)
throws IOException
{
int count = 0, delta;
while (length > 0) {
delta = decoder.read (buffer, offset, length);
if (delta <= 0)
throw new XmlStreamException ("?? internal EOF ??");
count += delta;
offset -= delta;
length += delta;
}
}
/**
* Reads an object or array.
*/
public Object readObject () throws IOException
{
ElementNode e = element ();
if (e != null) {
if ("BEAN".equals (e.getTagName ()))
return getBean (e);
else if ("OBJECT".equals (e.getTagName ()))
return mapping.get (e.getAttribute ("IDREF"));
else if ("NULL".equals (e.getTagName ()))
return null;
else if ("ARRAY".equals (e.getTagName ()))
return getArray (e);
// XXX accept counted OPAQUE byte array here too
}
throw new XmlStreamException ("needed BEAN, OBJECT, or NULL tag");
}
private Object getBean (ElementNode el) throws IOException
{
String id = el.getAttribute ("ID");
String className = el.getAttribute ("CLASS");
Class objClass;
Object retval = null;
try {
// Instantiate object ...
if (loader != null)
objClass = loader.loadClass (className);
else
objClass = Class.forName (className);
retval = objClass.newInstance ();
// Store it away, in case its contents refer back...
mapping.put (id, retval);
// Fetch type's property info
BeanInfo info = Introspector.getBeanInfo (objClass);
PropertyDescriptor props [] = info.getPropertyDescriptors ();
// set all reported properties
NodeList saved = next;
int lastIndex = index;
next = el.getChildNodes ();
index = 0;
eachProperty:
for (;;) {
String propName;
NodeList propValue;
if ((el = advance ("PROPERTY")) == null)
break;
propName = el.getAttribute ("NAME");
propValue = el.getChildNodes ();
if (propValue == null)
throw new XmlStreamException ("No value for property "
+ propName);
for (int i = 0; i < props.length; i++) {
if (props [i].getName ().equals (propName)) {
setProperty (retval, propValue,
props [i].getWriteMethod ());
continue eachProperty;
}
}
throw new XmlStreamException ("Bean type " + className
+ "has no property named " + propName);
}
next = saved;
index = lastIndex;
} catch (ClassNotFoundException e) {
throw new XmlStreamException ("Class not found: " + className);
} catch (InstantiationException e) {
throw new XmlStreamException ("Can't instantiate: " + className);
} catch (IllegalAccessException e) {
throw new XmlStreamException ("Can't access: " + className);
} catch (IntrospectionException e) {
throw new XmlStreamException ("Not a bean class: " + className);
} catch (InvocationTargetException e) {
throw new XmlStreamException ("Can't set all properties: "
+ className);
}
return retval;
}
private void setProperty (
Object bean,
NodeList value,
Method setter
) throws IOException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
Object args [] = new Object [1];
args [0] = readValue (value);
setter.invoke (bean, args);
}
private Object readValue (NodeList valueEnum)
throws IOException
{
NodeList saved = next;
int lastIndex = index;
ElementNode el;
Object retval;
try {
next = valueEnum;
index = 0;
el = element ();
if ("boolean".equals (el.getTagName ())) {
if ("1".equals (value (el)))
retval = Boolean.TRUE;
else
retval = Boolean.FALSE;
}
else if ("i1".equals (el.getTagName ()))
retval = new Byte (value (el));
else if ("i2".equals (el.getTagName ()))
retval = new Short (value (el));
else if ("i4".equals (el.getTagName ()))
retval = new Integer (value (el));
else if ("i8".equals (el.getTagName ()))
retval = new Long (value (el));
else if ("r4".equals (el.getTagName ()))
retval = new Float (value (el));
else if ("r8".equals (el.getTagName ()))
retval = new Double (value (el));
else if ("c".equals (el.getTagName ()))
retval = new Character ((char)Integer.parseInt (value (el)));
else if ("STRING".equals (el.getTagName ())) {
Node n;
String value = "";
// (#PCDATA|c)*
for (n = el.getFirstChild ();
n != null;
n = n.getNextSibling ()) {
switch (n.getNodeType ()) {
case Element.TEXT_NODE:
value += n.getNodeValue ();
continue;
case Element.ELEMENT_NODE:
value += (char) Short.parseShort (
value ((ElementNode)n));
continue;
default:
throw new XmlStreamException ("expecting text or ");
}
}
retval = value;
}
else if ("OBJECT".equals (el.getTagName ()))
retval = mapping.get (el.getAttribute ("IDREF"));
else if ("NULL".equals (el.getTagName ()))
retval = null;
else if ("BEAN".equals (el.getTagName ()))
retval = getBean (el);
else if ("ARRAY".equals (el.getTagName ()))
retval = getArray (el);
// XXX OPAQUE
else
throw new XmlStreamException ("unrecognized tag: "
+ el.getTagName ());
return retval;
} finally {
next = saved;
index = lastIndex;
}
}
private Object getArray (ElementNode el) throws IOException
{
String id = el.getAttribute ("ID");
String className = el.getAttribute ("CLASS");
int length = Integer.parseInt (el.getAttribute ("LENGTH"));
Class elementClass;
Object retval = null;
try {
// Instantiate array ...
if (loader != null)
elementClass = loader.loadClass (className);
else
elementClass = Class.forName (className);
retval = Array.newInstance (elementClass, length);
// Store it away, in case its contents refer back...
mapping.put (id, retval);
NodeList saved = next;
int lastIndex = index;
next = el.getChildNodes ();
eachProperty:
for (;;) {
int index;
NodeList elementValue;
if ((el = advance ("ELEMENT")) == null)
break;
index = Integer.parseInt (el.getAttribute ("INDEX"));
elementValue = el.getChildNodes ();
if (elementValue == null)
throw new XmlStreamException (
"No value for array element " + index);
Array.set (retval, index, readValue (elementValue));
}
next = saved;
index = lastIndex;
} catch (ArrayIndexOutOfBoundsException e) {
throw new XmlStreamException ("Array index out of bounds");
} catch (NegativeArraySizeException e) {
throw new XmlStreamException ("Negative array size");
} catch (ClassNotFoundException e) {
throw new XmlStreamException ("Class not found: " + className);
}
return retval;
}
}