/*
* Koala Bean Markup Language - Copyright (C) 1999 Dyade
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL Dyade BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*
* Except as contained in this notice, the name of Dyade shall not be
* used in advertising or otherwise to promote the sale, use or other
* dealings in this Software without prior written authorization from
* Dyade.
*
* $Id: KBMLDeserializer.java,v 1.13 2000/08/01 13:34:23 tkormann Exp $
* Author: Thierry.Kormann@sophia.inria.fr
*/
package fr.dyade.koala.xml.kbml;
import java.io.*;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Stack;
import java.beans.*;
import java.lang.reflect.*;
import org.xml.sax.*;
/**
* The class enables to create JavaBeans from an XML document. Example of use:
*
* <pre><code>
* FileInputStream istream = new FileInputStream("test.kbml");
* KBMLDeserializer bxi = new KBMLDeserializer(istream);
* bean1 = bxi.readBean();
* bean2 = bxi.readBean();
* bxi.close();
* </code></pre>
*
* @author Thierry.Kormann@sophia.inria.fr
*/
public class KBMLDeserializer {
/** The KBML version. */
public static final String VERSION = KBMLSerializer.VERSION;
/** The Hashtable where keys are Object and values are ID. */
protected Hashtable beansCache = new Hashtable();
/** The underlying input stream where to get the beans. */
protected InputStream istream;
/** The underlying reader where to get the beans. */
protected Reader reader;
/** The children of the kbml tag. */
private Object [] beans;
/** The current child index. */
private int currentBeanIndex = 0;
/** A SAX parser. */
private Parser parser;
/** The error handler used to notify no-fatal errors. */
protected ErrorHandler handler;
/**
* Constructs a new deserializer with the specified input
* stream. The InputStream must contains the XML document.
*
* @param istream the input stream
*
* @exception ClassNotFoundException if the SAX parser can't be
* found (check your CLASSPATH)
*
* @exception InstantiationException if the SAX parser can't be
* instanciated (it's an interface or abstract class)
*
* @exception IllegalAccessException The SAX parser class was
* found, but you do not have permission to load it.
*
* @exception java.lang.ClassCastException The SAX parser class
* was found and instantiated, but does not implement
* org.xml.sax.Parser.
*/
public KBMLDeserializer(InputStream istream)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, ClassCastException {
this(istream, new KBMLDeserializerDefaultErrorHandler());
}
/**
* Constructs a new deserializer with the specified input stream
* and error handler.
*
* @param istream the input stream
* @param handler the handler to track the no-fatal error
*
* @exception ClassNotFoundException if the SAX parser can't be
* found (check your CLASSPATH)
*
* @exception InstantiationException if the SAX parser can't be
* instanciated (it's an interface or abstract class)
*
* @exception IllegalAccessException The SAX parser class was
* found, but you do not have permission to load it.
*
* @exception java.lang.ClassCastException The SAX parser class
* was found and instantiated, but does not implement
* org.xml.sax.Parser.
*/
public KBMLDeserializer(InputStream istream, ErrorHandler handler)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, ClassCastException {
this.istream = istream;
this.handler = handler;
parser = fr.dyade.koala.xml.sax.ParserFactory.makeParser();
parser.setDocumentHandler(new SAXHandler());
parser.setErrorHandler(new SAXErrorHandler());
initializePropertyEditorManager();
}
/**
* Constructs a new deserializer with the specified reader. The
* Reader must contains the XML document.
*
* @param reader the reader
*
* @exception ClassNotFoundException if the SAX parser can't be
* found (check your CLASSPATH)
*
* @exception InstantiationException if the SAX parser can't be
* instanciated (it's an interface or abstract class)
*
* @exception IllegalAccessException The SAX parser class was
* found, but you do not have permission to load it.
*
* @exception java.lang.ClassCastException The SAX parser class
* was found and instantiated, but does not implement
* org.xml.sax.Parser.
*/
public KBMLDeserializer(Reader reader)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, ClassCastException {
this(reader, new KBMLDeserializerDefaultErrorHandler());
}
/**
* Constructs a new deserializer with the specified reader
* and error handler.
*
* @param reader the reader
* @param handler the handler to track the no-fatal error
*t
* @exception ClassNotFoundException if the SAX parser can't be
* found (check your CLASSPATH)
*
* @exception InstantiationException if the SAX parser can't be
* instanciated (it's an interface or abstract class)
*
* @exception IllegalAccessException The SAX parser class was
* found, but you do not have permission to load it.
*
* @exception java.lang.ClassCastException The SAX parser class
* was found and instantiated, but does not implement
* org.xml.sax.Parser.
*/
public KBMLDeserializer(Reader reader, ErrorHandler handler)
throws ClassNotFoundException, InstantiationException,
IllegalAccessException, ClassCastException {
this.reader = reader;
this.handler = handler;
parser = fr.dyade.koala.xml.sax.ParserFactory.makeParser();
parser.setDocumentHandler(new SAXHandler());
parser.setErrorHandler(new SAXErrorHandler());
initializePropertyEditorManager();
}
/**
* Reads next bean object from the XML input stream or reader.
* @exception IOException if an I/O error occurs
* @exception SAXException if an error occrus while parsing the xml document
*/
public Object readBean() throws IOException, SAXException {
if (beans == null) {
if (istream != null) {
parser.parse(new InputSource(istream));
} else {
parser.parse(new InputSource(reader));
}
}
if (currentBeanIndex < beans.length) {
return beans[currentBeanIndex++];
} else {
return null;
}
}
/**
* Closes the input stream.
* @exception IOException if an I/O error occurs.
*/
public void close() throws IOException {
if (istream != null) {
istream.close();
} else {
reader.close();
}
}
/**
* Returns the internal cache used by the deserializer. Use the ID of
* an element as a key to get its associated bean.
*/
public Hashtable getBeansCache() {
return beansCache;
}
/**
* Registers useful PropertyEditors to the java.beans.PropertyEditorManager.
* @see Util#initializePropertyEditorManager
*/
protected void initializePropertyEditorManager() {
Util.initializePropertyEditorManager();
}
/**
* Returns an instance that corresponds to the specified
* classname. This method is invoked each time the class attribute
* is specified while parsing a bean element from the XML
* document. Default behavior use the default class loader to
* instanciate the bean.
*
* <p>Override to have the opportunity to use custom class
* loaders. For example, the class name can be an URL that points
* to a class file. This method parses the class name and use an
* URL class loader to create the right instance.
*
* @see KBMLSerializer#getBeanClassName(java.lang.Object)
*
* @exception IOException if the bean can't be instantiate by the
* <code>Beans.instanciate(ClassLoader, String)</code> method
*
* @exception ClassNotFoundException if the bean can't be
* found (check your CLASSPATH)
*
* @exception InstantiationException if the bean can't be
* instanciated (it's an interface or abstract class)
*
* @exception IllegalAccessException The bean class was
* found, but you do not have permission to load it.
*
* @exception NoSuchMethodError The zero-argument constructor of
* the bean was not found.
*
* @return a new bean instance corresponding to the specified class name.
* @since KBML 2.2
*/
protected Object instanciateBean(String className)
throws ClassNotFoundException,
IllegalAccessException,
InstantiationException,
NoSuchMethodError,
IOException {
return Beans.instantiate(this.getClass().getClassLoader(), className);
}
/**
* A SAX handler that creates JavaBeans.
*/
private class SAXHandler extends HandlerBase {
Stack stack = new Stack();
StringBuffer content = new StringBuffer();
public void startElement(String name, AttributeList atts) {
// dispatch start-tag
if (name.equals("property")) {
handlePropertyStartElement(atts);
} else if (name.equals("value")) {
handleValueStartElement(atts);
} else if (name.equals("bean")) {
handleBeanStartElement(atts);
} else if (name.equals("null")) {
handleNullStartElement();
} else if (name.equals("valueArray")) {
handleValueArrayStartElement(atts);
} else if (name.equals("kbml")) {
String version = getAttributeValue(atts, "version");
if (version == null) {
throw new IllegalArgumentException(
"Bad KBML file, version attribute not found.");
}
int i = version.indexOf('.');
if (i < 0) {
throw new IllegalArgumentException(
"Bad version format : "+version+"\n"+
"Correct version format is : <major>.<minor>");
}
String vmajor = version.substring(0, i);
String Vmajor = VERSION.substring(0, version.indexOf('.'));
if (!vmajor.equals(Vmajor)) {
throw new IllegalArgumentException(
"Bad KBML version, current version is "+
VERSION+" and document version is "+version);
}
}
}
public void endElement(String name) {
// dispatch end-tag
if (name.equals("property")) {
handlePropertyEndElement();
} else if (name.equals("value")) {
handleValueEndElement();
} else if (name.equals("valueArray")) {
handleValueArrayEndElement();
}
}
public void endDocument() {
beans = new Object[stack.size()];
stack.copyInto(beans);
stack.removeAllElements();
content = null;
}
public void characters(char ch[], int start, int length)
throws SAXException {
content.append(ch, start, length);
}
// handle the bean start-tag
void handleBeanStartElement(AttributeList atts) {
String src = getAttributeValue(atts, "source");
Object bean;
if (src != null) {
bean = beansCache.get(src);
stack.push(bean);
} else {
String className = getAttributeValue(atts, "class");
try {
bean = instanciateBean(className);
stack.push(bean);
String id = getAttributeValue(atts, "id");
if (id != null) {
beansCache.put(id, bean);
}
} catch (IOException ex) {
handler.instanciateBean(className, ex);
stack.push(ERROR);
} catch (ClassNotFoundException ex) {
handler.instanciateBean(className, ex);
stack.push(ERROR);
} catch (IllegalAccessException ex) {
handler.instanciateBean(className, ex);
stack.push(ERROR);
} catch (InstantiationException ex) {
handler.instanciateBean(className, ex);
stack.push(ERROR);
} catch (NoSuchMethodError err) {
handler.instanciateBean(className, err);
stack.push(ERROR);
}
}
}
// handle property start-tag
void handlePropertyStartElement(AttributeList atts) {
Object bean = stack.peek();
try {
BeanInfo info = Introspector.getBeanInfo(bean.getClass());
String name = getAttributeValue(atts, "name");
PropertyDescriptor [] pds = info.getPropertyDescriptors();
PropertyDescriptor pd = Util.getPropertyDescriptor(pds, name);
if (pd == null) {
handler.propertyDescriptor(bean, name);
stack.push(ERROR);
} else {
stack.push(pd);
}
} catch (IntrospectionException ex) {
handler.introspector(bean, ex);
stack.push(ERROR);
}
}
// handle the property end-tag
void handlePropertyEndElement() {
Object v1 = stack.pop();
Object v2 = stack.pop();
Object bean = stack.peek();
if (v1 != ERROR && v2 != ERROR) {
Object value = v1;
PropertyDescriptor pd = (PropertyDescriptor) v2;
Method writeMethod = pd.getWriteMethod();
if (writeMethod != null) {
// use the write method
Object [] args = new Object[] { value };
try {
writeMethod.invoke(bean, args);
} catch (IllegalAccessException ex) {
handler.writeMethod(bean, pd, ex);
} catch (InvocationTargetException ex) {
handler.writeMethod(bean, pd, ex);
} catch (IllegalArgumentException ex) {
handler.writeMethod(bean, pd, ex);
}
} else if (pd instanceof IndexedPropertyDescriptor) {
// use the indexed write method
IndexedPropertyDescriptor ipd =
(IndexedPropertyDescriptor) pd;
Method iwriteMethod = ipd.getIndexedWriteMethod();
if (iwriteMethod != null) {
// set values using the indexed write method
Object [] valueArray = (Object []) value;
Object [] args = new Object[2];
try {
for (int i=0; i < valueArray.length; ++i) {
args[0] = new Integer(i);
args[1] = valueArray[i];
iwriteMethod.invoke(bean, args);
}
} catch (IllegalAccessException ex) {
handler.writeMethod(bean, pd, ex);
} catch (InvocationTargetException ex) {
handler.writeMethod(bean, pd, ex);
} catch (IllegalArgumentException ex) {
handler.writeMethod(bean, pd, ex);
}
} else {
// indexed write method not found
handler.writeMethod(bean, pd);
}
} else {
// write method and indexed write method not found
handler.writeMethod(bean, pd);
}
}
}
// handle the valueArray start-tag
void handleValueArrayStartElement(AttributeList atts) {
String src = getAttributeValue(atts, "source");
if (src != null) {
stack.push(new SourceRef(src));
} else {
String id = getAttributeValue(atts, "id");
stack.push(new ValueArrayDecl(id));
}
}
// handle the valueArray end-tag
void handleValueArrayEndElement() {
if (stack.peek() instanceof SourceRef) {
SourceRef ref = (SourceRef) stack.pop();
stack.push(beansCache.get(ref.source));
} else {
Vector subValues = new Vector();
while (!(stack.peek() instanceof ValueArrayDecl)) {
subValues.addElement(stack.pop());
}
ValueArrayDecl decl = (ValueArrayDecl) stack.pop();
PropertyDescriptor pd = currentPropertyDescriptor();
Class valueClass = pd.getPropertyType().getComponentType();
Object array = Array.newInstance(valueClass, subValues.size());
int length = subValues.size();
for (int i=0; i < length; ++i) {
Array.set(array, length-i-1, subValues.elementAt(i));
}
stack.push(array);
if (decl.id != null) {
beansCache.put(decl.id, array);
}
}
}
// handle the value start-tag, push a value if reference or class
void handleValueStartElement(AttributeList atts) {
// reset content
content.setLength(0);
// push a value declaration object or source ref
String src = getAttributeValue(atts, "source");
if (src != null) {
stack.push(new SourceRef(src));
} else {
String id = getAttributeValue(atts, "id");
String className = getAttributeValue(atts, "class");
Class valueClass;
if (className == null) {
PropertyDescriptor pd = currentPropertyDescriptor();
if (pd.getPropertyType().isArray()) {
valueClass = pd.getPropertyType().getComponentType();
} else {
valueClass = pd.getPropertyType();
}
} else {
try {
valueClass = Class.forName(className);
} catch (ClassNotFoundException ex) {
handler.instanciateValue(className, ex);
stack.push(ERROR);
return;
}
}
stack.push(new ValueDecl(id, valueClass));
}
}
// handle the value end-tag, push a value if content not empty
void handleValueEndElement() {
if (stack.peek() instanceof SourceRef) {
SourceRef ref = (SourceRef) stack.pop();
stack.push(beansCache.get(ref.source));
} else {
ValueDecl decl = (ValueDecl) stack.pop();
PropertyDescriptor pd = currentPropertyDescriptor();
Object value = getContentValue(pd, decl.valueClass);
stack.push(value);
if (decl.id != null) {
beansCache.put(decl.id, value);
}
}
}
// handle the null start-tag, push null
void handleNullStartElement() {
stack.push(null);
}
// returns the property value object from the content string
Object getContentValue(PropertyDescriptor pd, Class valueClass) {
Class editorClass = pd.getPropertyEditorClass();
PropertyEditor editor = null;
if (editorClass == null) {
editor = PropertyEditorManager.findEditor(valueClass);
} else {
try {
editor = (PropertyEditor) editorClass.newInstance();
} catch (IllegalAccessException ex) {
handler.propertyEditor(pd, ex);
return null;
} catch (InstantiationException ex) {
handler.propertyEditor(pd, ex);
return null;
} catch (NoSuchMethodError err) {
handler.propertyEditor(pd, err);
return null;
}
}
if (editor != null) {
editor.setAsText(new String(content));
return editor.getValue();
} else {
handler.propertyEditor(pd);
return null;
}
}
// search the current property descriptor on the stack
PropertyDescriptor currentPropertyDescriptor() {
for (int i=stack.size()-1; i >= 0; --i) {
Object obj = stack.elementAt(i);
if (obj instanceof PropertyDescriptor) {
return (PropertyDescriptor) obj;
}
}
return null;
}
// returns the value of an attribute in the list, or null if any
String getAttributeValue(AttributeList list, String name) {
for (int i=0; i < list.getLength(); ++i) {
if (list.getName(i).equals(name)) {
return list.getValue(i);
}
}
return null;
}
}
/** An object that is pushed on the stack when an error occurs. */
private static final Object ERROR = new Object(){};
/**
* A SAX error handler for the SAX parser.
*/
private class SAXErrorHandler implements org.xml.sax.ErrorHandler {
public void error(SAXParseException e) throws SAXException {
// Nothing to do
}
public void warning(SAXParseException e) throws SAXException {
showMessage("\tWarning: ", e);
}
public void fatalError(SAXParseException e) throws SAXException {
showMessage("\tFatal Error: ", e);
}
// Display a message, used by ErrorHandler
private void showMessage(String kind, SAXParseException e) {
System.err.println(" Line " + e.getLineNumber()
+ ":" + e.getColumnNumber());
System.err.println("\t" + kind + ": " + e.getMessage());
if (e.getException() != null) {
e.getException().printStackTrace();
} else {
e.printStackTrace();
}
}
}
/**
* A class used by tags that have a source attribute. Getting the
* value from bean cache using the source attribute is done in the
* end-tag method.
*/
private static class SourceRef {
String source;
SourceRef(String source) {
this.source = source;
}
}
/**
* A class used by the valueArray start-tag to store the id of the
* array. The array will be added to the bean cache in the end-tag method.
*/
private static class ValueArrayDecl {
String id;
ValueArrayDecl(String id) {
this.id = id;
}
}
/**
* A class used by the value start-tag to store the id and the class of a
* value. The value will be added to the bean cache in the end-tag method.
*/
private static class ValueDecl {
String id;
Class valueClass;
ValueDecl(String id, Class valueClass) {
this.id = id;
this.valueClass = valueClass;
}
}
/**
* The handler interface for receiving no-fatal errors that may occur
* during the deserialization process. If an application needs to
* customize error handling, it must implement this interface and
* then instanciate the deserializer with correct arguments.
*
* <p>KBML deserializer calls this handler instead of throwing an
* exception, it is up to the application to throw an exception
* when needed.
*
* @author Thierry.Kormann@sophia.inria.fr
*/
public interface ErrorHandler {
// called by handleBeanStartElement
/**
* Invoked when the bean can not be instanciated.
* @param className the name of the class
* @param ex the exception thrown
*/
void instanciateBean(String className, IOException ex);
/**
* Invoked when the bean can not be instanciated.
* @param className the name of the class
* @param ex the exception thrown
*/
void instanciateBean(String className, ClassNotFoundException ex);
/**
* Invoked when the bean can not be instanciated.
* @param className the name of the class
* @param ex the exception thrown
*/
void instanciateBean(String className, IllegalAccessException ex);
/**
* Invoked when the bean can not be instanciated.
* @param className the name of the class
* @param ex the exception thrown
*/
void instanciateBean(String className, InstantiationException ex);
/**
* Invoked when the bean can not be instanciated.
* @param className the name of the class
* @param err the error thrown
*/
void instanciateBean(String className, NoSuchMethodError err);
// called by handlePropertyStartElement
/**
* Invoked when no property descriptor is available a specific property.
* @param bean the bean
* @param propertyName the name of the property
*/
void propertyDescriptor(Object bean, String propertyName);
/**
* Invoked when the introspector can not get the BeanInfo.
* @param bean the bean
* @param ex the exception thrown
*/
void introspector(Object bean, IntrospectionException ex);
// called by handlePropertyEndElement
/**
* Invoked when no write method is found for a specific property.
* @param bean the bean
* @param pd the property descriptor
*/
void writeMethod(Object bean, PropertyDescriptor pd);
/**
* Invoked when a property value can not be set.
* @param bean the bean
* @param pd the property descriptor
* @param ex the exception thrown
*/
void writeMethod(Object bean, PropertyDescriptor pd,
IllegalAccessException ex);
/**
* Invoked when a property value can not be set.
* @param bean the bean
* @param pd the property descriptor
* @param ex the exception thrown
*/
void writeMethod(Object bean, PropertyDescriptor pd,
InvocationTargetException ex);
/**
* Invoked when the write method has been called with bad arguments.
* @param bean the bean
* @param pd the property descriptor
* @param ex the exception thrown
*/
void writeMethod(Object bean, PropertyDescriptor pd,
IllegalArgumentException ex);
// called by handleValueStartElement
/**
* Invoked when the value can not be instanciated.
* @param className the name of the class
* @param ex the exception thrown
*/
void instanciateValue(String className, ClassNotFoundException ex);
// called by getContentValue
/**
* Invoked when a property editor can not be instanciated.
* @param pd the property descriptor
* @param ex the exception thrown
*/
void propertyEditor(PropertyDescriptor pd, IllegalAccessException ex);
/**
* Invoked when a property editor can not be instanciated.
* @param pd the property descriptor
* @param ex the exception thrown
*/
void propertyEditor(PropertyDescriptor pd, InstantiationException ex);
/**
* Invoked when a property editor can not be instanciated.
* @param pd the property descriptor
* @param err the error thrown
*/
void propertyEditor(PropertyDescriptor pd, NoSuchMethodError err);
/**
* Invoked when no property editor is found for a specific property.
* @param pd the property descriptor
*/
void propertyEditor(PropertyDescriptor pd);
}
}
|