Package org.bifrost.xmlio.impl

Source Code of org.bifrost.xmlio.impl.ReaderContentHandler

package org.bifrost.xmlio.impl;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.bifrost.util.text.StringHelper;
import org.bifrost.xmlio.XmlException;
import org.bifrost.xmlio.config.NamespaceMap;
import org.bifrost.xmlio.config.ObjectMap;
import org.bifrost.xmlio.config.PropertyMap;
import org.bifrost.xmlio.config.XmlIOConfig;
import org.xml.sax.Attributes;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
* <p>Class used to parse the xml into objects.</p>
* <p>
* Created: Feb 23, 2003<br/>
* Copyright: Copyright (c) 2003<br/>
* Assumptions: none<br/>
* Requires: nothing<br/>
* Required by: XmlReader<br/>
* Revision History:<br/>
*   2003-04-21 Changed to use JAXP instead of Xerces.<br/>
* </p>
* @author Donald Kittle <donald@bifrost.org>
* @version 1.0
*/

public class ReaderContentHandler extends DefaultHandler {

  private Logger logger = Logger.getLogger(this.getClass().getName());

  public static final String EX_SETTER_NAME =
    "Cannot find appropriate setter name";
  public static final String EX_SETTER =
    "Cannot find appropriate setter";
  public static final String EX_CREATE_OBJECT =
    "Could not create object ";
  public static final String EX_CORRECT_SETTER =
    "Unable to find correct setter: ";
  public static final String EX_WRONG_NUM_PARAMS =
    "Wrong number of parameters in method.";
  public static final String EX_STACK_NOT_EMPTY =
    "Parser stack not empty at the end of the document.";
 
  /**
   * A stack of element and attribute names.  New names get added as the xml
   * is parsed. As attributes are set or elements are finshed with
   * (endElement()), the names are removed from the stack.
   */
  private Stack configStack;
  /**
   * A map of objects currently instantiated.  Setting attributes will look up
   * instantiated objects in this map.
   */
  private Map configObjects;
  /**
   * The root object described in the XML.
   */
  private Object root;
  /**
   * A flag indicating that an element has been processed already or not.
   * This is used when an endElement() is called to make sure that a setter
   * for the current element has been called. If startElement() or
   * characters() has not been called, call a setter that takes no
   * parameters.
   */
  private Map elementProcessed = new HashMap();
  /**
   * A flag indicating that an element is an object, not an attribute.
   */
  private Stack isObject = new Stack();
 
  /**
   * The packages that the objects described in the XML are defined in.
   */
  private List targetPackage;
  private String currentPackage;
  private String lastCharacters = "";
 
  /**
   * An instance of the XmlIOConfig object
   */
  XmlIOConfig config = XmlIOConfig.getInstance();
 
  /**
   * An instance of the XmlIOConfig object
   */
  PropertyHelper propertyHelper = PropertyHelper.getInstance();

  private Locator locator = null;
     
  /**
   * Constructor.
   * @param targetPackage The package where the classes for describing the xml
   * are located.
   */
  public ReaderContentHandler(String thePackage)
  {
    targetPackage = new LinkedList();
    if (thePackage == null)
      thePackage = "";
    StringTokenizer st = new StringTokenizer(thePackage, ":");
    while(st.hasMoreElements())
    {
      String path = (String)st.nextElement();
      path = correctPackageEnding(path);
      targetPackage.add(path);
    }
    if (logger.isLoggable(Level.FINE))
      logger.fine("Creating XML parser");
  }
 
  private String correctPackageEnding(String thePackage)
  {
    if (thePackage == null || "".equals(thePackage))
      return thePackage;
    if (!thePackage.endsWith("."))
    {
      StringBuffer test = new StringBuffer(thePackage);
      test.append(".");
      thePackage = test.toString();
      test = null;
    }
    return thePackage;
  }
  /**
   * Process characters - presumably this is to set an attribute. Call the
   * setter on the top objects in the stack.
   */
  public void characters(char[] ch, int start, int length)
    throws SAXException
  {
    if (attributeOnStack() == false)
      return;
    String attributeName = (String)configStack.peek();
    attributeName = getJavaName(attributeName);
    Boolean called = (Boolean)elementProcessed.get(attributeName);
    if (called != null && called == Boolean.TRUE)
      return;
    String value = (new String(ch, start, length));
    StringBuffer temp = new StringBuffer();
    if (lastCharacters != null)
      temp.append(lastCharacters);
    temp.append(value);
    lastCharacters = temp.toString();
    logger.fine("Processing characters: '" + value + "'");
  } // end characters()
 
  /**
   * Given the xml name of an element or attribute, translate into it's java
   * object or property name, as appropriate.
   * @param xmlName the name of the xml tag or attribute
   * @return String the java name corresponding to the xml name, or the xml
   * name that was passed to the method if there was no mapping.
   */
  private String getJavaName(String xmlName)
  {
    if (xmlName == null)
      return null;
   
    String result = null;

    String className = null;
    // Pull previous names from the stack
    if (configStack.size() > 0)
      className = (String)configStack.pop();
    // Restore stack
    if (className != null)
      configStack.push(className);
   
    // Check global property mappings
    if (config.getPropertyMapByAlias(xmlName) != null)
      result = config.getPropertyMapByAlias(xmlName).getPropertyName();

    if (result == null && className != null)
    {
      // See if this object name is mapped
      ObjectMap oMap = config.getObjectMapByName(className);
      if (oMap == null)
      {
        className = StringHelper.capitalizeFirst(className);
        oMap = config.getObjectMapByName(className);
      }
      if (oMap != null && oMap.getPropertyMapFromAlias(xmlName) != null)
        result = oMap.getPropertyMapFromAlias(xmlName).getPropertyName();
    }

    if (result == null)
      result = xmlName;
   
    return result;
  } // end getJavaName()
 
  private boolean attributeOnStack()
  {
    String attributeName = null;
    if (configStack.size() > 0)
      attributeName = (String)configStack.peek();
    // If the first letter of the attribute name is upper case then we are
    // currently processing an object
    if (Character.isUpperCase(attributeName.charAt(0)))
      return false;

    return true
  }
 
  /**
   * Sets the 'current' attribute to the specified value. The top object on
   * the stack is the attribute to be set and the second last object on the
   * stack is the object.  An exception will not be thrown if there are too
   * few objects on the stack as it simply means we are processing some
   * whitespace or something similar.
   * @param value the value to be set
   * @throws SAXException if there is a problem
   */
  private void setCurrentAttribute(Object value) throws SAXException
  {
    String attributeName = null;
    String objectName = null;
    if (configStack.size() > 0)
      attributeName = (String)configStack.pop();
    if (configStack.size() > 0)
      objectName = (String)configStack.pop();
    if (attributeName == null)
      return;
    if (objectName == null)
    {
      // Put attribute name back on stack
      configStack.push(attributeName);
      return;
    }
    // Push both object name and attribute name back onto stack
    configStack.push(objectName);
    configStack.push(attributeName);
     
    if (attributeName.equals("") || objectName.equals(""))
      return;
   
    // Get an instance of the object to set the attribute in
    Object object = getObject(objectName);
    if (object == null)
    {
      object = createObject(objectName);
      if (object == null)
        throw new SAXException(EX_CREATE_OBJECT + objectName + getLocation());
    }

    String methodName = attributeName;

    ObjectMap omap = config.getObjectMapByName(objectName);
    if (omap == null)
      omap = config.getObjectMapByName(nameWithoutPackage(objectName));
    if (omap == null)
    {
      omap = ObjectMap.createFromClass(object.getClass());
      config.addObjectMap(omap);
    }
   
    Method method = null;
    boolean global = false;
   
    PropertyMap pmap = omap.getPropertyMapFromName(methodName);
    if (pmap == null)
      pmap = omap.getPropertyMapFromName(lowerCaseFirst(methodName));
    if (pmap == null)
      pmap = omap.getPropertyMapFromName(capitalizeFirst(methodName));

    // Check to see if it's globally defined
    if (pmap == null)
    {
      pmap = config.getPropertyMapByName(methodName);
      if (pmap == null)
        pmap = config.getPropertyMapByName(lowerCaseFirst(methodName));
      if (pmap == null)
        pmap = config.getPropertyMapByName(capitalizeFirst(methodName));
      if (pmap != null)
        global = true;
    }
   
    StringBuffer trace = new StringBuffer(methodName);
   
    if (pmap == null)
    {
      Object thisObject = instantiateObject(methodName);
      Class superClass = null;
      if (thisObject != null)
        superClass = thisObject.getClass().getSuperclass();

      while (superClass != null && pmap == null)
      {
        methodName = nameWithoutPackage(superClass.getName());
        trace.append(" or ");
        trace.append(methodName);
        pmap = omap.getPropertyMapFromName(methodName);
        if (pmap == null)
          pmap = omap.getPropertyMapFromName(lowerCaseFirst(methodName));

        // Check to see if it's globally defined
        if (pmap == null)
        {
          pmap = config.getPropertyMapByName(methodName);
          if (pmap == null)
            pmap = config.getPropertyMapByName(capitalizeFirst(methodName));
          if (pmap != null)
            global = true;
        }

        superClass = superClass.getSuperclass();
      }
    }

    if (pmap == null)
    {
      String error = EX_SETTER_NAME +" for " + trace.toString() + " in " + objectName;
      logger.fine(error);
      throw new SAXException(error);
    }
    if (pmap != null)
      method = pmap.getSetter();

    // If the setter method is undefined for this property map, then the
    // property map was probably added to config programmatically or through
    // a config file, rather than being generated from a class.
    if (method == null && pmap != null)
    {
      // Guess what the setter might be
      method = pmap.guessSetter(object);
      // And cache the method if it's not a global property map
      if (method != null && global == false)
        pmap.setSetter(method);
    }
   
    try
    {
      propertyHelper.setAttribute(pmap, object, method, value);
    } catch (XmlException e)
    {
      throw new SAXException(e.toString() + getLocation());
    }

  } // end setCurrentAttribute()
 
  private String lowerCaseFirst(String source)
  {
    if (source == null)
      return null;
    if (source.equals(""))
      return "";
    StringBuffer result = new StringBuffer();
      result.append(Character.toLowerCase(source.charAt(0)));
    if (source.length() > 1)
      result.append(source.substring(1));
    return result.toString();
  }

  private String capitalizeFirst(String source)
  {
    if (source == null)
      return null;
    if (source.equals(""))
      return "";
    StringBuffer result = new StringBuffer();
      result.append(Character.toUpperCase(source.charAt(0)));
    if (source.length() > 1)
      result.append(source.substring(1));
    return result.toString();
  }

  /**
   * Create an instance of the named object, trying all paths defined in the
   * context.
   * @param objectName the fully qualified name of the object to create
   */
  private Object instantiateObject(String objectName)
  {
    if (objectName == null)
      return null;
    // Try creating an instance of the object without any changes to the name.
    Object object = config.getObjectFactory().getInstance(objectName);
    if (object != null)
      return object;
    // Try adding the various context paths to the object name
    for (Iterator i = targetPackage.iterator(); i.hasNext(); )
    {
      currentPackage = (String)i.next();
      String className = currentPackage + objectName;
      object = config.getObjectFactory().getInstance(className);
      if (object != null)
        return object;
    }
    return null;
  } // end instantiateObject()
 
  /**
   * Create an instance of the named object and put a reference to that object
   * in the map.
   * @param objectName the fully qualified name of the object to create
   */
  private Object createObject(String objectName)
  {
    Object object = instantiateObject(objectName);
    if (object != null)
    {
      putObject(objectName, object);
      return object;
    }
    // Finally, see if there is a mapping from the name found in the xml to
    // a different java name
    return object;
  } // end createObject()

  private void putObject(String objectName, Object object)
  {
    LinkedList coll = (LinkedList)configObjects.get(objectName);
    if (coll == null)
      coll = new LinkedList();
    coll.add(object);
    configObjects.put(objectName, coll);
  } // end putObject()
 
  private Object getObject(String objectName)
  {
    Object result = null;
    LinkedList coll = (LinkedList)configObjects.get(objectName);
    if (coll != null && coll.size() > 0)
      result = coll.getLast();
    return result;
  } // getObject()
 
  private void removeObject(String objectName)
  {
    LinkedList coll = (LinkedList)configObjects.get(objectName);
    if (coll != null)
    {
      Object last = coll.getLast();
      coll.remove(last);
    }
  } // getObject()

  /**
   * Implementation of abstract method from ContentHandler.
   */
  public void endDocument() throws SAXException {
    if (!configStack.empty())
      throw new SAXException(EX_STACK_NOT_EMPTY + getLocation() + getRemaining());
  } // end endDocument()
 
  public String getRemaining()
  {
    StringBuffer result = new StringBuffer();
    String sep = "";
    while (!configStack.isEmpty())
    {
      String item = (String)configStack.pop();
      result.append(sep);
      result.append(item);
      sep = ",";
    }
    return result.toString();
  } // end getRemaining()
 
  /**
   * Process end element. If first character in the name of the element is
   * lower case, the element is an attribute so simply remove it from the
   * stack.  If the first character in the name of the element is upper case,
   * it is an object so try to treat this current object as an attribute of
   * a parent object (if a parent object exists).  For instance, if the
   * current element name is Value and the previous element name was AnObject,
   * we would try to call a AnObject.setValue() method.
   */
   public void endElement(String namespaceURI, String localName, String qName)
    throws SAXException
  {
    if (logger.isLoggable(Level.FINE))
      logger.fine("Processing end tag: " + qName);
    qName = StringHelper.dashesToCapitals(qName);
    String name = (String)configStack.pop();
    if (name == null || "".equals(name))
      return;
    // Check to see if the current element is 'null' and the previous
    // element is an attribute.  If both are true, simply return.
    if (name.equals("null"))
    {
      if (attributeOnStack() == true)
      {
        // Get the name of the property off of the stack (it was the previous
        // tag), set the property to null and mark it as having been
        // processed
        String attributeName = (String)configStack.peek();
        setCurrentAttribute(null);
        elementProcessed.put(attributeName, Boolean.TRUE);
        logger.fine("Null found and previous element is an attribute.");
        return;
      }
    }
    // We are not dealing with a null, push the name back onto stack and
    // process it normally
    if (name != null)
    {
      name = getJavaName(name);
      configStack.push(name);
    }

    // If first letter of element is lower case, it's an attribute
    // if (attributeOnStack() == true)
    Boolean objectFlag = (Boolean)isObject.pop();
    if (objectFlag == Boolean.FALSE)
    { 
      logger.fine("End element is an attribute");
      // Check to make sure that characters() was called on this element,
      // otherwise check for and call a setAttribute() method with the
      // lastCharacters variable as the parameter
      Boolean called = (Boolean)elementProcessed.get(name);
      if (called == null || called == Boolean.FALSE)
        setCurrentAttribute(lastCharacters);
      configStack.pop();
      elementProcessed.remove(name);
      return;
    }
    // The element is an object...
    Object object = getObject(name);
    // Create objects if it had not sub-elements or attributes.
    if (object == null)
      object = createObject(name);
    if (object == null)
      return;
   
    // And remove that instance of the object out of the object map (we are
    // done with it)
    removeObject(name);

    // Try to call a parent object's setter for this object as an attribute
    setCurrentAttribute(object);
    // Pull object name back off of stack (we are done with it)
    configStack.pop();
   } // end endElement()
 
  /**
   * Clears the root element and creates a new processing stack and object map
   * for caching objects.
   */
   public void startDocument() throws SAXException {
     configStack = new Stack();
    configObjects = new HashMap();
    root = null;
  } // end startDocument()
 
  /**
   * Start element implementation which simply pushes the element name onto the
   * stack of elements and attributes to deal with.  If the root object has not
   * been created, create it using the name of this element.  If this element
   * has attributes, process them as if they were sub elements of this element.
   */
  public void startElement(String namespaceUri, String localName, String qName,
    Attributes atttributes) throws SAXException
  {
    // Convert the xml name to a java name
    qName = StringHelper.dashesToCapitals(qName);
    qName = getJavaName(qName);
   
    String name = "";
    if (configStack.size() > 0)
      name = StringHelper.capitalizeFirst((String)configStack.peek());

    lastCharacters = "";
    String element = null;
    if (configStack.size() > 0)
      element = (String)configStack.peek();
    if (element != null)
      elementProcessed.put(element, Boolean.FALSE);

    if (logger.isLoggable(Level.FINE))
      logger.fine("Processing start tag: " + qName);

    if (config.getObjectMapByAlias(qName) != null)
      qName = config.getObjectMapByAlias(qName).getName();
   
//    qName = StringHelper.capitalizeFirst(qName);
   
    // Expand namespace if the element is an object (first letter is uppercase)
    if (namespaceUri != null && !namespaceUri.equals("") && qName != null &&
      !"".equals(qName))
    {
      if (Character.isUpperCase(qName.charAt(0)) == true)
      {
        NamespaceMap map =
          (NamespaceMap)config.getNamespaceMapByUri(namespaceUri);
        String packageName = null;
        if (map != null)
          packageName = (String)map.getPackageName();
        if (packageName != null)
        {
          StringBuffer temp = new StringBuffer(packageName);
          if (!packageName.endsWith("."))
            temp.append(".");
          temp.append(qName);
          qName = temp.toString();
        }
        else
          logger.info("Found no mappings for " + namespaceUri);
      }

    } // end namespace processing

    Object obj = createObject(qName);
    if (obj != null)
      isObject.push(Boolean.TRUE);
    else
      isObject.push(Boolean.FALSE);
   
    if (root == null)
      root = obj;
    if (obj != null && config.getObjectMapByName(qName) == null)
      config.addClassAsMappedObject(obj.getClass());
   
    configStack.push(qName);

    // If there are no attributes, we are done.
    if (atttributes == null || atttributes.getLength() < 1)
      return;

    // Process attributes as if they were elements.
    for (int i = 0; i < atttributes.getLength(); i++)
    {
      startElement(namespaceUri, atttributes.getLocalName(i), atttributes.getQName(i), null);
      characters(atttributes.getValue(i).toCharArray(), 0, atttributes.getValue(i).length());
      endElement(namespaceUri, atttributes.getLocalName(i), atttributes.getQName(i));
    }
  } // end startElement()

  /**
   * Returns the result of the parsing (the collection of objects).
   * @return Object the root object that describes the xml
   */
   public Object getResult(){
     return root;
  } // end getResult()

  /**
   * Take a fully qualified name of a class and return the class name without
   * any package information.
   * @param source the fully qualified name of the class
   * @return the name of the class without any package information
   */
  private String nameWithoutPackage(String source)
  {
    int index = source.lastIndexOf(".");
    if (index < 0 || index == source.length())
      return source;
    return source.substring(index + 1);
  } // end nameWithoutPackage()

  private String getLocation()
  {
    if (locator == null)
      return "";
    StringBuffer result = new StringBuffer();
    result.append(" - line = ");
    result.append(locator.getLineNumber());
    result.append(", column = ");
    result.append(locator.getColumnNumber());
    return result.toString();
  }
 
  public void setDocumentLocator(Locator locator)
  {
    if (locator == null)
      return;
    this.locator = locator;
  }
 
} // end ReaderContentHandler Class
TOP

Related Classes of org.bifrost.xmlio.impl.ReaderContentHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.