Unmarshaller.java :  » XML » zeus » org » enhydra » zeus » Java Open Source

Java Open Source » XML » zeus 
zeus » org » enhydra » zeus » Unmarshaller.java
/*
 * Enhydra Java Application Server Project
 * 
 * The contents of this file are subject to the Enhydra Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License on
 * the Enhydra web site ( http://www.enhydra.org/ ).
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See 
 * the License for the specific terms governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Enhydra Application Server is Lutris
 * Technologies, Inc. The Enhydra Application Server and portions created
 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
 * All Rights Reserved.
 */
package org.enhydra.zeus;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

// Zeus imports
import org.enhydra.zeus.util.ClassUtils;
import org.enhydra.zeus.util.NamingUtils;

// JDOM imports
import org.jdom.Attribute;
import org.jdom.Document;
import org.jdom.DocType;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;

/**
 * <p>
 *  <code>Unmarshaller</code> takes an XML instance document, which should
 *    conform to some set of XML constraints, and creates a Java object from 
 *    the XML document. This object is an instance of the implementation class, 
 *    which in turn implements the interface, created using an implementation of
 *    <code>{@link Generator}</code>.
 * </p>
 * <p>
 *  It also assumes that the generated interface and implementation classes
 *    are in the classpath, as it will perform a <code>Class.forName()</code> on
 *    the classes.
 * </p>
 *
 * @author Brett McLaughlin
 * @author Maciej Zawadzki
 */
public class Unmarshaller {
    
    /** Constant used to signal whether debug messages should be printed out */
    protected boolean debug = false;
    
    /** Store interface/implementation class mappings */
    private Map implClasses;

    /** The package name of the interface/implementation classes */
    private String javaPackage;

    /**
     * Prefix to be prepended to the name of every generated 
     *   interface and class.
     */
    protected String namePrefix;

    /**
     * <p>
     *  Simple constructor.
     * </p>
     */
    public Unmarshaller() {
        implClasses = new HashMap();
        namePrefix = "";
    }
    
    /**
     * <p>
     *  For the supplied interface name, this will allow a customized 
     *    implementation class to be set. This effectively allows the user to 
     *    specify their own implementation class to use instead of the default, 
     *    Zeus-generated class.
     * </p>
     *
     * @param interfaceName <code>String</code> name of interface being 
     *        deal with.
     * @param implClassName <code>String</code> name of class to load when 
     *        implementations of <code>interfaceName</code> are needed.
     */
    public void setImplClass(String interfaceName, String implClassName) {
        implClasses.put(interfaceName, implClassName);
    }
    
    /**
     * <p>
     *  This method allows the developer to set a prefix that 
     *    will be prepended to the name of every generated 
     *    interface and class.  
     * </p>
     *
     * @param String prefix that will be prepended to the 
     *        name of every generated interface and class
     */
    public void setNamePrefix(String namePrefix) {
        if (namePrefix != null) {
            this.namePrefix = namePrefix;
        }
    }

    /**
     * <p>
     *  This method allows the developer to get the prefix that 
     *    will be prepended to the name of every generated
     *    interface and class.  Returns an empty string in the 
     *    case that a prefix is not prepended.
     * </p>
     *
     * @return <code>String</code> prefix that will be prepended 
     *         to the name of every generated interface and class
     */
    public String getNamePrefix() {
        return namePrefix;
    }
    
    /**
     * <p>
     *  For the supplied interface name, this will return the current 
     *    implementation class associated with the interface. If no class has 
     *    been specified, this will return the default, Zeus-generated class 
     *    name for the interface.
     * </p>
     *
     * @param interfaceName <code>String</code> name of interface being 
     *        dealt with.
     * @return <code>String</code> - implementation class for supplied 
     *         <code>interfaceName</code>.
     */
    public String getImplClass(String interfaceName) {
        String implClass = (String)implClasses.get(interfaceName);
        if (implClass == null) {
            // Construct class name for Zeus binding class
            implClass = new StringBuffer()
                .append(namePrefix)
                .append(interfaceName)
                .append("Impl")
                .toString();
        }
        return implClass;
    }

    /**
     * <p>
     *  Returns the Java package to unmarshall classes to.
     * </p>
     * 
     * @return <code>String</code> - the Java package to unmarshall to.
     */
    public String getJavaPackage() {
        if (javaPackage == null) {
            javaPackage = "";
        }
        return javaPackage;
    }

    /**
     * <p>
     *  Sets the Java package to unmarshall classes to.
     * </p>
     * 
     * @param javaPackage <code>String</code> the Java package to unmarshall
     *        to.
     */
    public void setJavaPackage(String javaPackage) {
        this.javaPackage = javaPackage;
    }

    /**
     * <p>
     *  This method is the public entry point for unmarshalling an object from
     *    an XML instance document. Note that the raw converted object is not
     *    directly returned, but is available through the 
     *    <code>{@link UnmarshalledObject#getObject}</code> method on
     *    <code>{@link UnmarshalledObject}</code>.
     * </p><p>
     *  XXX: We need to eventually put in Zeus exceptions here, 
     *    not IOExceptions.
     * </p>
     *
     * @param instanceURL <code>URL</code> for the instance document.
     * @return <code>UnmarshalledObject</code> - the created Java object, or 
     *         <code>null</code> if problems occur in a way that does not
     *         generate an <code>Exception</code>.
     * @throws <code>IOException</code> when errors in binding occur.
     */
    public UnmarshalledObject unmarshal(Source sourceXML) throws IOException {
        // Obtain the JDOM Document from the supplied source
        Document doc = sourceXML.getDocument(); 
        Element rootElement = doc.getRootElement();
               
        // Convert to Java
        Object obj = getJavaRepresentation(rootElement);
 
        // Wrap in UnmarshalledObject
        DocType docType = doc.getDocType();
        if (docType != null) {
            return new UnmarshalledObject(obj, 
                                          docType.getPublicID(),
                                          docType.getSystemID());
        } else {
            return new UnmarshalledObject(obj);
        }
    }

    /**
     * <p>
     *  This will take an XML document fragment, starting with the provided
     *    <code>Element</code>, and convert it to a Java representation.
     * </p>
     *
     * @param rootElement <code>Element</code> to begin processing on.
     * @return <code>Object</code> - unmarshalled Java object.
     * @throws <code>IOException</code> - when XML cannot be converted to Java.
     */
    private Object getJavaRepresentation(Element element) 
        throws IOException {           

        // If debugging, print out the element being processed
        if (debug) {
            System.out.println("Unmarshaller.getJavaRepresentation: " + 
                element.getName());
        }

        Object obj = null;
        String className = null;
        try {
            Namespace ns = element.getNamespace();

            // Class name is root element name, with initial caps, plus "Impl"
            className = getImplClass(NamingUtils.getJavaClassName(
                element.getName()));

            Class objectClass;
            
            // Get the class
            String pkg = getJavaPackage();
            if (pkg.equals("")) {
                objectClass = getClass().getClassLoader().loadClass(className);
            } else {
                objectClass = 
                    getClass().getClassLoader().loadClass(new StringBuffer()
                        .append(pkg)
                        .append(".")
                        .append(className)
                        .toString());
            }

            // Get a new instance of this class
            obj = objectClass.newInstance();

            // For each attribute, get its name and call mutator
            List attributes = element.getAttributes();
            Method[] methods = objectClass.getMethods(); 

            for (Iterator i = attributes.iterator(); i.hasNext(); ) {
                Attribute att = (Attribute)i.next();
                
                // If debugging, print out the attribute being processed
                if (debug) {
                    System.out.println(
                        "  Processing attribute : " + att.getName());
                }

                // We only want attributes for this namespace
                if ((!att.getNamespace().equals(ns)) &&
                    (!att.getNamespace().equals(Namespace.NO_NAMESPACE))) {
                    continue;
                }

                // Determine method to call - no Lists to worry about 
                //   with attributes
                String methodName = new StringBuffer()
                    .append("set")
                    .append(NamingUtils.getJavaClassName(att.getName()))
                    .toString();

                // Find the method to call, and its parameter type
                for (int j=0; j<methods.length; j++) {
                    if (methods[j].getName().equals(methodName)) {
                        // Since all mutators have one param, get the first one
                        Class[] paramTypes = methods[j].getParameterTypes();
                        Class paramType = paramTypes[0];

                        // Convert the type we have to the correct type
                        Object param = 
                            ClassUtils.getParameter(att.getValue(), paramType);

                        // Invoke the method
                        methods[j].invoke(obj, new Object[] { param });
                    }
                }
            }

            // Now do complex objects
            List children = element.getChildren();

            for (Iterator i = children.iterator(); i.hasNext(); ) {
                Element child = (Element)i.next();
                // If debugging, print out the element being processed
                if (debug) {
                    System.out.println("  Processing Child Element: " + 
                        child.getName());
                }

                // We only want elements for this namespace
                if ((!child.getNamespace().equals(ns)) &&
                    (!child.getNamespace().equals(Namespace.NO_NAMESPACE))) {
                    continue;
                }

                // Determine method to call - start with default name
                String singularMethodName = new StringBuffer()
                    .append("set")
                    .append(NamingUtils.getJavaClassName(child.getName()))
                    .toString();
                String listMethodName = new StringBuffer()
                    .append("add")
                    .append(NamingUtils.getJavaClassName(child.getName()))
                    .toString();              

                // Find the method to call, and its parameter type
                for (int j=0; j<methods.length; j++) {
                    if (methods[j].getName().equals(singularMethodName)) {                                             
                        // Since all mutators have one param, get the first one
                        Class[] paramTypes = methods[j].getParameterTypes();
                        Class paramType = paramTypes[0];

                        // Convert the type we have to the correct type
                        Object param = null;

                        if (isSimpleElement(child)) {
                            param = child.getTextTrim();
                        } else {
                            param = getJavaRepresentation(child);
                        }

                        // Invoke the method:
                        //   In case the arguments are wrong, the exception
                        //   is caught and a recursive call is made
                        try {
                            methods[j].invoke(obj, new Object[] { param });
                        } catch (IllegalArgumentException ilex) {
                            param = getJavaRepresentation(child);
                            methods[j].invoke(obj, new Object[] { param });
                        }

                        break;
                    } else if (methods[j].getName().equals(listMethodName)) {
                        Class[] paramTypes = methods[j].getParameterTypes();
                        Class paramType = paramTypes[0];
                        
                        // Convert the type we have to the correct type
                        Object param = null;
                        if (isSimpleElement(child)) {
                            param = child.getTextTrim();
                        } else {
                            param = getJavaRepresentation(child);
                        }
                        
                        // Invoke the method:
                        //   In case the arguments are wrong, the exception
                        //   is caught and a recursive call is made
                        try {
                            methods[j].invoke(obj, new Object[] { param });
                        } catch (IllegalArgumentException ilex) {
                            param = getJavaRepresentation(child);
                        }

                        break;
                    }
                }                
            }
            
            // Finally, deal with element content            
            String content = element.getTextTrim();
            if ((content != null) && (content.length() > 0)) {
                String methodName = "setValue";
                Method method
                    = objectClass.getMethod(methodName,
                                            new Class[] { String.class });
                method.invoke(obj, new Object[] { content });               
            }
            
        } catch (ClassNotFoundException e) {
            throw new IOException("The class that the XML instance document " +
                                  "should be unmarshalled as an instance of, " +
                                  className +
                                  ", must be generated and on the classpath. " +
                                  "The current classpath is '" +
                                  System.getProperty("java.class.path") + "'.");
        } catch (InstantiationException e) {
            throw new IOException("Could not create a new instance of the " +
                                  "class " + className);
        } catch (IllegalAccessException e) {
            throw new IOException("Could not access the class " + className);
        } catch (IllegalArgumentException e) {
            throw new IOException("Could not pass arguments to mutator " +
                                  "method on new instance.");
        } catch (InvocationTargetException e) {
            throw new IOException("Error in accessing mutator on new " +
                                  "instance of class.");
        } catch (NoSuchMethodException e) {
            throw new IOException(
                "Error in setting data method on new instance of class.");
        }
        return obj;
    } 

    /**
     * <p>
     *  This will determine if an element is "simple", having neither
     *    attributes nor child elements.
     * </p>
     * 
     * @param element <code>Element</code> to inspect
     * @return <code>boolean</code> - whether the element is simple.
     */
    private boolean isSimpleElement(Element element) {
        boolean simple = true;
        if (element.getChildren().isEmpty()) {
            List attributes = element.getAttributes();
            int size = attributes.size();
            Attribute attr = null;
            String typeStr = "dt_";
            
            // FIXME: If there is an implied attribute, then the element may
            //        look like a simple element even though it is not.
            for (int i = 0; i < size; i++) {
                attr = (Attribute)attributes.get(i);
                if (!attr.getName().startsWith(typeStr)) {
                    simple = false;
                    break;
                }
            }
        } else {
            simple = false;
        }
        return simple;
    }
}
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.