FileDocCategorySizeDatePackage
SimpleDeserializer.javaAPI DocApache Axis 1.415356Sat Apr 22 18:57:26 BST 2006org.apache.axis.encoding.ser

SimpleDeserializer.java

/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis.encoding.ser;

import org.apache.axis.description.TypeDesc;
import org.apache.axis.encoding.DeserializationContext;
import org.apache.axis.encoding.Deserializer;
import org.apache.axis.encoding.DeserializerImpl;
import org.apache.axis.encoding.SimpleType;
import org.apache.axis.encoding.TypeMapping;
import org.apache.axis.message.SOAPHandler;
import org.apache.axis.utils.BeanPropertyDescriptor;
import org.apache.axis.utils.BeanUtils;
import org.apache.axis.utils.Messages;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import java.io.CharArrayWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * A deserializer for any simple type with a (String) constructor.  Note:
 * this class is designed so that subclasses need only override the makeValue 
 * method in order to construct objects of their own type.
 *
 * @author Glen Daniels (gdaniels@apache.org)
 * @author Sam Ruby (rubys@us.ibm.com)
 * Modified for JAX-RPC @author Rich Scheuerle (scheu@us.ibm.com)
 */
public class SimpleDeserializer extends DeserializerImpl {
    
    private static final Class[] STRING_STRING_CLASS = 
        new Class [] {String.class, String.class};

    public static final Class[] STRING_CLASS = 
        new Class [] {String.class};

    private final CharArrayWriter val = new CharArrayWriter();
    private Constructor constructor = null;
    private Map propertyMap = null;
    private HashMap attributeMap = null;
    
    public QName xmlType;
    public Class javaType;
    
    private TypeDesc typeDesc = null;

    protected DeserializationContext context = null;
    protected SimpleDeserializer cacheStringDSer = null;
    protected QName cacheXMLType = null;
    /**
     * The Deserializer is constructed with the xmlType and 
     * javaType (which could be a java primitive like int.class)
     */
    public SimpleDeserializer(Class javaType, QName xmlType) {
         this.xmlType = xmlType;
        this.javaType = javaType;
        
        init();
    }

    public SimpleDeserializer(Class javaType, QName xmlType, TypeDesc typeDesc) {
        this.xmlType = xmlType;
        this.javaType = javaType;
        this.typeDesc = typeDesc;
        
        init();
    }    

    /**
     * Initialize the typeDesc, property descriptors and propertyMap.
     */
    private void init() {
        // The typeDesc and map array are only necessary
        // if this class extends SimpleType.
        if (SimpleType.class.isAssignableFrom(javaType)) {
            // Set the typeDesc if not already set
            if (typeDesc == null) {
                typeDesc = TypeDesc.getTypeDescForClass(javaType);
            }
        }

        // Get the cached propertyDescriptor from the type or
        // generate a fresh one.
        if (typeDesc != null) {
            propertyMap = typeDesc.getPropertyDescriptorMap();
        } else {
            BeanPropertyDescriptor[] pd = BeanUtils.getPd(javaType, null);
            propertyMap = new HashMap();
            for (int i = 0; i < pd.length; i++) {
                BeanPropertyDescriptor descriptor = pd[i];
                propertyMap.put(descriptor.getName(), descriptor);
            }
        }
    }
    
    /**
     * Reset deserializer for re-use
     */
    public void reset() {
        val.reset();
        attributeMap = null; // Remove attribute map
        isNil = false; // Don't know if nil
        isEnded = false; // Indicate the end of element not yet called
    }
    
    /** 
     * The Factory calls setConstructor.
     */
    public void setConstructor(Constructor c) 
    {
        constructor = c;
    }
    
    /**
     * There should not be nested elements, so thow and exception if this occurs.
     */
    public SOAPHandler onStartChild(String namespace,
                                    String localName,
                                    String prefix,
                                    Attributes attributes,
                                    DeserializationContext context)
            throws SAXException
    {
        throw new SAXException(
                Messages.getMessage("cantHandle00", "SimpleDeserializer"));
    }
    
    /**
     * Append any characters received to the value.  This method is defined 
     * by Deserializer.
     */
    public void characters(char [] chars, int start, int end)
            throws SAXException
    {
        val.write(chars,start,end);
    }
    
    /**
     * Append any characters to the value.  This method is defined by 
     * Deserializer.
     */
    public void onEndElement(String namespace, String localName,
                             DeserializationContext context)
            throws SAXException
    {
        if (isNil) {
            value = null;
            return;
        }
        try {
            value = makeValue(val.toString());
        } catch (InvocationTargetException ite) {
            Throwable realException = ite.getTargetException();
            if (realException instanceof Exception)
                throw new SAXException((Exception)realException);
            else
                throw new SAXException(ite.getMessage());
        } catch (Exception e) {
            throw new SAXException(e);
        }
        
        // If this is a SimpleType, set attributes we have stashed away
        setSimpleTypeAttributes();
    }
    
    /**
     * Convert the string that has been accumulated into an Object.  Subclasses
     * may override this.  Note that if the javaType is a primitive, the returned
     * object is a wrapper class.
     * @param source the serialized value to be deserialized
     * @throws Exception any exception thrown by this method will be wrapped
     */
    public Object makeValue(String source) throws Exception
    {
        if (javaType == java.lang.String.class) {
            return source;
        }

        // Trim whitespace if non-String
        source = source.trim();

        if (source.length() == 0 && typeDesc == null) {
            return null;
        }
        
        // if constructor is set skip all basic java type checks
        if (this.constructor == null) {
            Object value = makeBasicValue(source);
            if (value != null) {
                return value;
            }
        }
            
        Object [] args = null;
        
        boolean isQNameSubclass = QName.class.isAssignableFrom(javaType);

        if (isQNameSubclass) {
            int colon = source.lastIndexOf(":");
            String namespace = colon < 0 ? "" :
                context.getNamespaceURI(source.substring(0, colon));
            String localPart = colon < 0 ? source : source.substring(colon + 1);
            args = new Object [] {namespace, localPart};
        } 

        if (constructor == null) {
            try {
                if (isQNameSubclass) {
                    constructor = 
                        javaType.getDeclaredConstructor(STRING_STRING_CLASS);
                } else {
                    constructor = 
                        javaType.getDeclaredConstructor(STRING_CLASS);
                }
            } catch (Exception e) {
                return null;
            }
        }
        
        if(constructor.getParameterTypes().length==0){
            try {
                Object obj = constructor.newInstance(new Object[]{});
                obj.getClass().getMethod("set_value", new Class[]{String.class})
                        .invoke(obj, new Object[]{source});
                return obj;
            } catch (Exception e){
                //Ignore exception
            }
        }
        if (args == null) {
            args = new Object[]{source};
        }
        return constructor.newInstance(args);
    }

    private Object makeBasicValue(String source) throws Exception {
        // If the javaType is a boolean, except a number of different sources
        if (javaType == boolean.class || 
            javaType == Boolean.class) {
            // This is a pretty lame test, but it is what the previous code did.
            switch (source.charAt(0)) {
                case '0': case 'f': case 'F':
                    return Boolean.FALSE;
                    
                case '1': case 't': case 'T': 
                    return Boolean.TRUE; 
                    
                default:
                    throw new NumberFormatException(
                            Messages.getMessage("badBool00"));
                }
            
        }
        
        // If expecting a Float or a Double, need to accept some special cases.
        if (javaType == float.class ||
            javaType == java.lang.Float.class) {
            if (source.equals("NaN")) {
                return new Float(Float.NaN);
            } else if (source.equals("INF")) {
                return new Float(Float.POSITIVE_INFINITY);
            } else if (source.equals("-INF")) {
                return new Float(Float.NEGATIVE_INFINITY);
            } else {
                return new Float(source);
            }
        }
        
        if (javaType == double.class ||
            javaType == java.lang.Double.class) {
            if (source.equals("NaN")) {
                return new Double(Double.NaN);
            } else if (source.equals("INF")) {
                return new Double(Double.POSITIVE_INFINITY);
            } else if (source.equals("-INF")) {
                return new Double(Double.NEGATIVE_INFINITY);
            } else {
                return new Double(source);
            }
        }    
        
        if (javaType == int.class ||
            javaType == java.lang.Integer.class) {
            return new Integer(source);
        }
        
        if (javaType == short.class ||
            javaType == java.lang.Short.class) {
            return new Short(source);
        }
        
        if (javaType == long.class ||
            javaType == java.lang.Long.class) {
            return new Long(source);
        }
        
        if (javaType == byte.class ||
            javaType == java.lang.Byte.class) {
            return new Byte(source);
        }
        
        if (javaType == org.apache.axis.types.URI.class) {
            return new org.apache.axis.types.URI(source);
        }

        return null;
    }
        
    /**
     * Set the bean properties that correspond to element attributes.
     * 
     * This method is invoked after startElement when the element requires
     * deserialization (i.e. the element is not an href and the value is not nil.)
     * @param namespace is the namespace of the element
     * @param localName is the name of the element
     * @param prefix is the prefix of the element
     * @param attributes are the attributes on the element...used to get the type
     * @param context is the DeserializationContext
     */
    public void onStartElement(String namespace, String localName,
                               String prefix, Attributes attributes,
                               DeserializationContext context)
            throws SAXException 
    {
        
        this.context = context;

        // loop through the attributes and set bean properties that
        // correspond to attributes
        for (int i=0; i < attributes.getLength(); i++) {
            QName attrQName = new QName(attributes.getURI(i),
                                        attributes.getLocalName(i));
            
            String fieldName = attributes.getLocalName(i);
            
            if(typeDesc != null) {
                fieldName = typeDesc.getFieldNameForAttribute(attrQName);
                if (fieldName == null)
                    continue;
            }

            if (propertyMap == null)
                continue;
            
            // look for the attribute property
            BeanPropertyDescriptor bpd =
                    (BeanPropertyDescriptor) propertyMap.get(fieldName);
            if (bpd != null) {
                if (!bpd.isWriteable() || bpd.isIndexed() ) continue ;
                
                // determine the QName for this child element
                TypeMapping tm = context.getTypeMapping();
                Class type = bpd.getType();
                QName qn = tm.getTypeQName(type);
                if (qn == null)
                    throw new SAXException(
                            Messages.getMessage("unregistered00", type.toString()));
                
                // get the deserializer
                Deserializer dSer = context.getDeserializerForType(qn);
                if (dSer == null)
                    throw new SAXException(
                            Messages.getMessage("noDeser00", type.toString()));
                if (! (dSer instanceof SimpleDeserializer))
                    throw new SAXException(
                            Messages.getMessage("AttrNotSimpleType00",
                                                bpd.getName(),
                                                type.toString()));
                
                // Success!  Create an object from the string and save
                // it in our attribute map for later.
                if (attributeMap == null) {
                    attributeMap = new HashMap();
                }
                try {
                    Object val = ((SimpleDeserializer)dSer).
                            makeValue(attributes.getValue(i));
                    attributeMap.put(fieldName, val);
                } catch (Exception e) {
                    throw new SAXException(e);
                }
            } // if
        } // attribute loop
    } // onStartElement
    
    /**
     * Process any attributes we may have encountered (in onStartElement)
     */ 
    private void setSimpleTypeAttributes() throws SAXException {
        if (attributeMap == null)
            return;
        
        // loop through map
        Set entries = attributeMap.entrySet();
        for (Iterator iterator = entries.iterator(); iterator.hasNext();) {
            Map.Entry entry = (Map.Entry) iterator.next();
            String name = (String) entry.getKey();
            Object val = entry.getValue();
            
            BeanPropertyDescriptor bpd =
                    (BeanPropertyDescriptor) propertyMap.get(name);
            if (!bpd.isWriteable() || bpd.isIndexed()) continue;
            try {
                bpd.set(value, val );
            } catch (Exception e) {
                throw new SAXException(e);
            }
        }
    }
    
}