KBMLDeserializer.java :  » XML » KBML » fr » dyade » koala » xml » kbml » Java Open Source

Java Open Source » XML » KBML 
KBML » fr » dyade » koala » xml » kbml » KBMLDeserializer.java
/*
 * 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);
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.