Package com.envoisolutions.sxc.jaxb

Source Code of com.envoisolutions.sxc.jaxb.ReaderIntrospector

/**
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements.  See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 com.envoisolutions.sxc.jaxb;

import java.awt.Image;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
import javax.activation.DataHandler;
import javax.xml.bind.JAXBElement;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;

import com.envoisolutions.sxc.builder.BuildException;
import com.envoisolutions.sxc.builder.impl.JBlankLine;
import com.envoisolutions.sxc.builder.impl.JIfElseBlock;
import com.envoisolutions.sxc.builder.impl.JLineComment;
import com.envoisolutions.sxc.builder.impl.JStaticImports;
import static com.envoisolutions.sxc.jaxb.JavaUtils.isPrivate;
import static com.envoisolutions.sxc.jaxb.JavaUtils.toClass;
import com.envoisolutions.sxc.jaxb.model.Bean;
import com.envoisolutions.sxc.jaxb.model.ElementMapping;
import com.envoisolutions.sxc.jaxb.model.EnumInfo;
import com.envoisolutions.sxc.jaxb.model.Model;
import com.envoisolutions.sxc.jaxb.model.Property;
import com.envoisolutions.sxc.util.ArrayUtil;
import com.envoisolutions.sxc.util.Base64;
import com.sun.codemodel.JArray;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JCatchBlock;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JExpression;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JInvocation;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JTryBlock;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;
import com.sun.codemodel.JForEach;
import org.w3c.dom.Element;

public class ReaderIntrospector {
    private static final Logger logger = Logger.getLogger(ReaderIntrospector.class.getName());

    private final BuilderContext context;
    private final Model model;
    private final Map<Bean, JAXBObjectBuilder> builders = new LinkedHashMap<Bean, JAXBObjectBuilder>();
    private final Map<Class, JAXBEnumBuilder> enumBuilders = new LinkedHashMap<Class, JAXBEnumBuilder>();

    public ReaderIntrospector(BuilderContext context, Model model) {
        this.context = context;
        this.model = model;

        // build all enum parsers so they are available for use by bean readers
        for (EnumInfo enumInfo : model.getEnums()) {
            addEnum(enumInfo);
        }

        // declare all parser methods first, so everything exists when we build
        for (Bean bean : this.model.getBeans()) {
            boolean mixed = false;
            for (Property property : bean.getProperties()) {
                if (property.isMixed() && property.getXmlName() == null) {
                    mixed = true;
                    break;
                }
            }
            JAXBObjectBuilder builder = context.createJAXBObjectBuilder(bean.getType(), bean.getRootElementName(), bean.getSchemaTypeName(), mixed);

            LinkedHashSet<Property> allProperties = new LinkedHashSet<Property>();
            for (Bean b = bean; b != null; b = b.getBaseClass()) {
                allProperties.addAll(b.getProperties());
            }

            // declare all private field accessors (so they are grouped)
            for (Property property : allProperties) {
                Field field = property.getField();
                if (field != null) {
                    if (isPrivate(field)) {
                        builder.getPrivateFieldAccessor(property.getField());
                    }
                } else {
                    if (isPrivate(property.getGetter()) || isPrivate(property.getSetter())) {
                        builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                    }
                }
            }

            // declare all adapter classes
            for (Property property : allProperties) {
                if (property.getAdapterType() != null) {
                    builder.getAdapter(property.getAdapterType());
                }
            }

            builders.put(bean, builder);
        }

        // build parsers
        for (Bean bean : model.getBeans()) {
            if (!bean.getType().isEnum()) {
                JAXBObjectBuilder builder = builders.get(bean);
                if (builder != null) {
                    add(builder, bean);
                }
            }
        }
    }

    private JInvocation invokeParser(JAXBObjectBuilder caller, JVar callerXsrVar, JAXBObjectBuilder parser) {
        // Declare dependency from caller to parser
        caller.addDependency(parser.getJAXBObjectClass());

        // Add a static import for the read method on the existing builder class
        String methodName = "read" + parser.getReadMethod().type().name();
        if (caller != parser) {
            JStaticImports staticImports = JStaticImports.getStaticImports(caller.getJAXBObjectClass());
            staticImports.addStaticImport(parser.getJAXBObjectClass().fullName() + "." + methodName);
        }

        // Call the static method
        JInvocation invocation = JExpr.invoke(methodName).arg(callerXsrVar).arg(caller.getReadContextVar());
        return invocation;
    }

    private JInvocation invokeEnumParser(JAXBObjectBuilder caller, JVar callerXsrVar, JAXBEnumBuilder parser, JExpression value) {
        // Declare dependency from caller to parser
        caller.addDependency(parser.getJAXBEnumClass());

        // Add a static import for the parse method on the existing builder class
        String methodName = "parse" + parser.getType().getSimpleName();
        JStaticImports staticImports = JStaticImports.getStaticImports(caller.getJAXBObjectClass());
        staticImports.addStaticImport(parser.getJAXBEnumClass().fullName() + "." + methodName);

        // Call the static method
        JInvocation invocation = JExpr.invoke(methodName).arg(callerXsrVar).arg(caller.getReadContextVar()).arg(value);
        return invocation;
    }

    private JAXBObjectBuilder add(JAXBObjectBuilder builder, Bean bean) {
        // read properties
        if (!Modifier.isAbstract(bean.getType().getModifiers())) {
            handleProperties(builder, bean, builder.getReadObject());
        }

        // This element may be replaced with another type using the xsi:type override
        // Add xsi type support for an other Bean that can be assigned to this property according to Java type assignment rules
        for (Bean xsiTypeBean : model.getBeans()) {
            Class xsiTypeClass = xsiTypeBean.getType();
            QName schemaTypeName = xsiTypeBean.getSchemaTypeName();
            if (bean.getType().isAssignableFrom(xsiTypeClass) && schemaTypeName != null && bean.getType() != xsiTypeClass && !Modifier.isAbstract(xsiTypeClass.getModifiers())) {
                JBlock block = builder.expectXsiType(schemaTypeName);

                JAXBObjectBuilder elementBuilder = builders.get(xsiTypeBean);
                if (elementBuilder == null) {
                    throw new BuildException("Unknown bean " + bean);
                }

                // invoke the reader method
                JInvocation method = invokeParser(builder, builder.getXSR(), elementBuilder);
                block._return(method);
            }
        }

        return builder;
    }

    private void addEnum(EnumInfo enumInfo) {
        JAXBEnumBuilder builder = context.createJAXBEnumBuilder(enumInfo.getType(), enumInfo.getRootElementName(), enumInfo.getSchemaTypeName());

        JMethod method = builder.getParseMethod();

        JIfElseBlock enumCond = new JIfElseBlock();
        method.body().add(enumCond);
        for (Map.Entry<Enum, String> entry : enumInfo.getEnumMap().entrySet()) {
            Enum enumValue = entry.getKey();
            String enumText = entry.getValue();

            JExpression textCompare = JExpr.lit(enumText).invoke("equals").arg(builder.getParseValue());
            JBlock block = enumCond.addCondition(textCompare);
            block._return(context.toJClass(enumInfo.getType()).staticRef(enumValue.name()));
        }

        JInvocation unexpectedInvoke = enumCond._else().invoke(builder.getParseContext(), "unexpectedEnumValue")
                .arg(builder.getParseXSR())
                .arg(context.toJClass(enumInfo.getType()).dotclass())
                .arg(builder.getParseValue());

        for (String expectedValue : enumInfo.getEnumMap().values()) {
            unexpectedInvoke.arg(expectedValue);
        }
        enumCond._else()._return(JExpr._null());

        enumBuilders.put(enumInfo.getType(), builder);
    }

    private void handleProperties(JAXBObjectBuilder builder, Bean bean, JVar beanVar) {
        for (Property property : bean.getProperties()) {
            // DS: the JaxB spec does not define the mapping for char or char[]
            // the RI reads the string as an int and then converts it to a char so 42 becomes '*'
            // I think this is lame so I didn't implement it. You can use a XmlAdapter to covert
            // if you really want to handle char.
            if (char.class.equals(property.getType()) || char[].class.equals(property.getType())) {
                logger.info("(JAXB Reader) JaxB specification does not support property " + property.getName()
                                   + " with type " + property.getType()
                                   + " on " + bean.getClass() + ": Use a XmlAdapter");
                continue;
            }

            switch (property.getXmlStyle()) {
                case ATTRIBUTE: {
                    if (!property.isXmlAny()) {
                        // create attribute block
                        JBlock block = builder.expectAttribute(property.getXmlName());

                        // add comment for readability
                        block.add(new JLineComment(property.getXmlStyle() + ": " + property.getName()));

                        // create collection var if necessary
                        JVar collectionVar = handleCollection(builder, property, beanVar);

                        // get the value to evaluate
                        JExpression value;
                        if (!property.isCollection()) {
                            value = builder.getAttributeVar().invoke("getValue");
                        } else {
                            JForEach forEach = block.forEach(context.toJClass(String.class), builder.getReadVariableManager().createId(property.getName() + "Item"), builder.getAttributeVar().invoke("getXmlListValue"));
                            block = forEach.body();
                            value = forEach.var();
                        }
                       
                        // read and set
                        JExpression toSet = handleAttribute(builder, block, property, value);
                        doSet(builder, block, property, beanVar, toSet, collectionVar);
                    } else {
                        handleAnyAttribute(builder, property, beanVar);
                    }

                }
                break;

                case ELEMENT:
                case ELEMENT_REF: {
                    JAXBObjectBuilder elementBuilder = builder;
                    JVar parentVar = beanVar;
                    if (property.getXmlName() != null && !property.isXmlList()) {
                        elementBuilder = builder.expectWrapperElement(property.getXmlName(), beanVar, property.getName(), property.isMixed());
                    }

                    // create collection var if necessary
                    JVar collectionVar = handleCollection(elementBuilder, property, parentVar);

                    for (ElementMapping mapping : property.getElementMappings()) {
                        // create element block
                        JBlock block = elementBuilder.expectElement(mapping.getXmlName());

                        // add comment for readability
                        block.add(new JLineComment(property.getXmlStyle() + ": " + property.getName()));

                        // get the value to evaluate
                        JVar xsrVar = builder.getChildElementVar();
                        if (!property.isXmlList()) {
                            // read and set
                            JExpression toSet = handleElement(builder, xsrVar, block, property, mapping.isNillable(), mapping.getComponentType());
                            doSet(builder, block, property, parentVar, toSet, collectionVar);
                        } else {
                            JForEach forEach = block.forEach(context.toJClass(String.class), builder.getReadVariableManager().createId(property.getName() + "Item"), xsrVar.invoke("getElementAsXmlList"));
                            block = forEach.body();
                            JExpression value = forEach.var();

                            // read and set
                            String propertyName = property.getName();
                            if (property.isCollection()) propertyName += "Item";
                            propertyName = builder.getReadVariableManager().createId(propertyName);

                            JExpression toSet;
                            if (property.isIdref() || property.getAdapterType() == null) {
                                toSet = coerce(builder, xsrVar, value, toClass(mapping.getComponentType()));
                            } else {
                                // adapted type
                                JVar adapterVar = builder.getAdapter(property.getAdapterType());
                   
                                block.add(new JBlankLine());

                                // convert raw value into bound type
                                Class targetType = toClass(mapping.getComponentType());
                                JVar valueVar = block.decl(context.toJClass(targetType), propertyName);
                                JTryBlock tryBlock = block._try();
                                tryBlock.body().assign(valueVar, adapterVar.invoke("unmarshal").arg(value));

                                JCatchBlock catchException = tryBlock._catch(context.toJClass(Exception.class));
                                JBlock catchBody = catchException.body();
                                catchBody.invoke(builder.getReadContextVar(), "xmlAdapterError")
                                        .arg(xsrVar)
                                        .arg(context.dotclass(property.getAdapterType()))
                                        .arg(context.dotclass(targetType))
                                        .arg(context.dotclass(targetType))
                                        .arg(catchException.param("e"));
                                catchBody._continue();

                                block.add(new JBlankLine());

                                toSet = valueVar;
                            }

                            // JaxB refs need to be wrapped with a JAXBElement
                            if (toClass(property.getComponentType()).equals(JAXBElement.class)) {
                                toSet = newJaxBElement(xsrVar, toClass(mapping.getComponentType()), toSet);
                            }

                            doSet(builder, block, property, parentVar, toSet, collectionVar);
                        }

                    }

                    if (property.isXmlAny()) {
                        // create element block
                        JBlock block = elementBuilder.expectAnyElement();

                        // add comment for readability
                        block.add(new JLineComment(property.getXmlStyle() + ": " + property.getName()));

                        // read and set
                        JInvocation toSet = builder.getReadContextVar().invoke("readXmlAny")
                                .arg(builder.getChildElementVar())
                                .arg(context.dotclass(property.getComponentType()))
                                .arg(property.isLax() ? JExpr.TRUE : JExpr.FALSE);
                        doSet(builder, block, property, parentVar, toSet, collectionVar);
                    }

                    if (property.isMixed()) {
                        // create element block
                        JBlock block = elementBuilder.expectMixedElement();

                        // add comment for readability
                        block.add(new JLineComment(property.getXmlStyle() + " (Mixed): " + property.getName()));

                        // read and set
                        JInvocation toSet = builder.getChildElementVar().invoke("getText");
                        doSet(builder, block, property, parentVar, toSet, collectionVar);
                    }
                }
                break;

                case VALUE: {
                    // value is read in class block
                    JBlock block = builder.expectValue();

                    // add comment for readability
                    block.add(new JBlankLine());
                    block.add(new JLineComment(property.getXmlStyle() + ": " + property.getName()));

                    // read and set value
                    handleValue(builder, builder.getXSR(), block, property, beanVar);
                }
                break;
            }
        }

        // handle properties of the base class
        if (bean.getBaseClass() != null) {
            handleProperties(builder, bean.getBaseClass(), beanVar);
        }
    }

    private JExpression handleAttribute(JAXBObjectBuilder builder, JBlock block, Property property, JExpression value) {
        JExpression toSet;
        if (property.isIdref()) {
            toSet = value;
        } else if (property.getAdapterType() != null) {
            JVar adapterVar = builder.getAdapter(property.getAdapterType());
            toSet = adapterVar.invoke("unmarshal").arg(value);
        } else {
            String propertyName = property.getName();
            if (property.isCollection() || property.isXmlAny()) propertyName += "Item";
            propertyName = builder.getReadVariableManager().createId(propertyName);

            Class clazz = toClass(property.getComponentType());
            if (isBuiltinType(clazz)) {
                toSet = block.decl(context.toJType(clazz), propertyName, coerce(builder, builder.getXSR(), value, clazz));
            } else if (clazz.equals(byte[].class)) {
                toSet = context.toJClass(Base64.class).staticInvoke("decode").arg(value);
            } else if (clazz.equals(QName.class)) {
                JVar var = as(builder, value, block, String.class, propertyName);
                toSet = builder.getXSR().invoke("getAsQName").arg(var);
            } else if (clazz.equals(DataHandler.class) || clazz.equals(Image.class)) {
                // todo support AttachmentMarshaller
                toSet = JExpr._null();
            } else {
                logger.severe("Could not map attribute " + propertyName + " of type " + clazz);
                toSet = JExpr._null();
            }
        }
        return toSet;
    }

    private void handleAnyAttribute(JAXBObjectBuilder builder, Property property, JVar beanVar) {
        // create element block
        JBlock block = builder.expectAnyAttribute();

        // add comment for readability
        block.add(new JLineComment(property.getXmlStyle() + ": " + property.getName()));

        //
        // declare any attribute map property at top of method
        String mapVarName = builder.getReadVariableManager().createId(property.getName());
        JVar mapVar = builder.getReadMethod().body().decl(context.getGenericType(property.getType()), mapVarName, JExpr._null());

        //
        // read attribute
        JExpression toSet = handleAttribute(builder, block, property, builder.getAttributeVar().invoke("getValue"));

        //
        // add attribute to map

        // if (map == null) {
        JBlock createMapBlock = block._if(mapVar.eq(JExpr._null()))._then();
        //     map = (Map) bean.getAnyTypeMap();
        if (property.getField() == null && property.getGetter() == null) {
            // write only maps are not allowed by the spec, but we can support it anyway
            JType mapType = getMapClass(property.getType(), property.getComponentType());
            if (mapType == null) {
                throw new BuildException("AnyAttribute map property does not have a getter and map does not have a default constructor: " +
                        property.getBean().getType().getName() + "." + property.getName());
            }
            createMapBlock.assign(mapVar, JExpr._new(mapType));
        } else {
            if (property.getField() != null) {
                Field field = property.getField();

                if (!isPrivate(field)) {
                    createMapBlock.assign(mapVar, beanVar.ref(field.getName()));
                } else {
                    JFieldVar fieldAccessorField = builder.getPrivateFieldAccessor(field);
                    createMapBlock.assign(mapVar, fieldAccessorField.invoke("getObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar));
                }
            } else {
                Method getter = property.getGetter() ;
                if (!isPrivate(getter)) {
                    JTryBlock tryGetter = createMapBlock._try();
                    tryGetter.body().assign(mapVar, beanVar.invoke(getter.getName()));

                    JCatchBlock catchException = tryGetter._catch(context.toJClass(Exception.class));
                    catchException.body().invoke(builder.getReadContextVar(), "getterError")
                            .arg(builder.getXSR())
                            .arg(context.dotclass(property.getBean().getType()))
                            .arg(getter.getName())
                            .arg(catchException.param("e"));
                    catchException.body()._continue();
                } else {
                    JFieldVar propertyAccessorField = builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                    createMapBlock.assign(mapVar, propertyAccessorField.invoke("getObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar));
                }
            }

            //     if (map != null) {
            //         map.clear();
            JConditional arrayFoundCondition = createMapBlock._if(mapVar.ne(JExpr._null()));
            arrayFoundCondition._then().invoke(mapVar, "clear");

            //     } else {
            //         map = new ArrayList();
            JType mapType = getMapClass(property.getType(), property.getComponentType());
            if (mapType != null) {
                arrayFoundCondition._else().assign(mapVar, JExpr._new(mapType));
            } else {
                arrayFoundCondition._else().invoke(builder.getReadContextVar(), "uncreatableMap")
                        .arg(builder.getXSR())
                        .arg(context.dotclass(property.getBean().getType()))
                        .arg(property.getName())
                        .arg(context.dotclass(property.getType()));
                arrayFoundCondition._else()._continue();
            }
        }
        //     }
        // }

        // collection.add(item);
        block.add(mapVar.invoke("put").arg(builder.getAttributeVar().invoke("getName")).arg(toSet));


        //
        // set the map into the bean at the bottom of the method
        //
        // if (map != null) {
        //     bean.setAnyAttribute(map);
        // }
        if (property.getField() != null) {
            Field field = property.getField();

            JBlock assignMapBlock = builder.getReadTailBlock()._if(mapVar.ne(JExpr._null()))._then();

            if (!isPrivate(field)) {
                assignMapBlock.assign(beanVar.ref(field.getName()), mapVar);
            } else {
                JFieldVar fieldAccessorField = builder.getPrivateFieldAccessor(field);
                assignMapBlock.add(fieldAccessorField.invoke("setObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar).arg(mapVar));
            }
        } else {
            // if there is no setter method, the map is not assigned into the class
            // this assumes that the getter returned the a map instance and held on to a reference
            Method setter = property.getSetter();
            if (setter != null) {
                JBlock assignMapBlock = builder.getReadTailBlock()._if(mapVar.ne(JExpr._null()))._then();
                if (!isPrivate(setter)) {

                    JTryBlock trySetter = assignMapBlock._try();
                    trySetter.body().add(beanVar.invoke(setter.getName()).arg(mapVar));

                    JCatchBlock catchException = trySetter._catch(context.toJClass(Exception.class));
                    catchException.body().invoke(builder.getReadContextVar(), "setterError")
                            .arg(builder.getXSR())
                            .arg(context.dotclass(property.getBean().getType()))
                            .arg(setter.getName())
                            .arg(context.dotclass(setter.getParameterTypes()[0]))
                            .arg(catchException.param("e"));
                } else {
                    JFieldVar propertyAccessorField = builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                    assignMapBlock.add(propertyAccessorField.invoke("setObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar).arg(mapVar));
                }
            }
        }
    }

    private JExpression handleElement(JAXBObjectBuilder builder, JVar xsrVar, JBlock block, Property property, boolean nillable, Type componentType) {

        String propertyName = property.getName();
        if (property.isCollection()) propertyName += "Item";
        propertyName = builder.getReadVariableManager().createId(propertyName);

        JExpression toSet;
        if (property.isIdref()) {
            // id ref... read string value which will later be turned into correct type
            toSet = xsrVar.invoke("getElementAsString");
        } else if (property.getAdapterType() != null) {
            // adapted type
            JVar adapterVar = builder.getAdapter(property.getAdapterType());

            // read the raw value
            JVar xmlValueVar = readElement(builder, xsrVar, block, nillable, builder.getReadVariableManager().createId(propertyName + "Raw"), property.getComponentAdaptedType());
            block.add(new JBlankLine());

            // convert raw value into bound type
            Class targetType = toClass(componentType);
            JVar valueVar = block.decl(context.toJClass(targetType), propertyName);
            JTryBlock tryBlock = block._try();
            tryBlock.body().assign(valueVar, adapterVar.invoke("unmarshal").arg(xmlValueVar));

            JCatchBlock catchException = tryBlock._catch(context.toJClass(Exception.class));
            JBlock catchBody = catchException.body();
            catchBody.invoke(builder.getReadContextVar(), "xmlAdapterError")
                    .arg(xsrVar)
                    .arg(context.dotclass(property.getAdapterType()))
                    .arg(context.dotclass(targetType))
                    .arg(context.dotclass(targetType))
                    .arg(catchException.param("e"));
            catchBody._continue();

            block.add(new JBlankLine());

            toSet = valueVar;
        } else {
            // plain old element type
            toSet = readElement(builder,  xsrVar, block, nillable, propertyName, toClass(componentType));
        }

        // JaxB refs need to be wrapped with a JAXBElement
        if (toClass(property.getComponentType()).equals(JAXBElement.class)) {
            toSet = newJaxBElement(xsrVar, toClass(componentType), toSet);
        }

        return toSet;
    }

    private JVar readElement(JAXBObjectBuilder builder, JVar xsrVar, JBlock block, boolean nillable, String propertyName, Class targetType) {
        JVar toSet;
        if (targetType.equals(Byte.class) || targetType.equals(byte.class)) {
            // todo why the special read method for byte?
            toSet = block.decl(context.toJType(byte.class), propertyName, JExpr.cast(context.toJType(byte.class), xsrVar.invoke("getElementAsInt")));
        } else if (isBuiltinType(targetType)) {
            toSet = as(builder, xsrVar, block, targetType, propertyName, nillable);
        } else if (targetType.equals(byte[].class)) {
            toSet = block.decl(context.toJClass(byte[].class), propertyName, context.toJClass(BinaryUtils.class).staticInvoke("decodeAsBytes").arg(xsrVar));
        } else if (targetType.equals(QName.class)) {
            toSet = block.decl(context.toJClass(QName.class), propertyName, xsrVar.invoke("getElementAsQName"));
        } else if (targetType.equals(DataHandler.class) || targetType.equals(Image.class)) {
            // todo support AttachmentMarshaller
            toSet = block.decl(context.toJClass(targetType), propertyName, JExpr._null());
        } else if (targetType.equals(Object.class) || targetType.equals(Element.class)) {
            toSet = block.decl(context.toJClass(Element.class), propertyName, xsrVar.invoke("getElementAsDomElement"));
        } else {
            // Complex type which will already have an element builder defined
            Bean targetBean = model.getBean(targetType);
            JAXBObjectBuilder elementBuilder = builders.get(targetBean);
            if (elementBuilder == null) {
                toSet = block.decl(context.toJClass(targetType), propertyName, builder.getReadContextVar().invoke("unexpectedXsiType").arg(builder.getXSR()).arg(JExpr.dotclass(context.toJClass(targetType))));
            } else {
                // invoke the reader method
                JInvocation invocation = invokeParser(builder, builder.getChildElementVar(), elementBuilder);
                toSet = block.decl(context.toJClass(targetType), propertyName, invocation);
            }
        }
        return toSet;
    }

    private void handleValue(JAXBObjectBuilder builder, JVar xsrVar, JBlock block, Property property, JVar beanVar) {
        // if this is a collection, create the collection variable
        JVar collectionVar = handleCollection(builder, property, beanVar);

        // get the value to evaluate
        JExpression value;
        if (!property.isCollection()) {
            value = xsrVar.invoke("getElementText");
        } else {
            JForEach forEach = block.forEach(context.toJClass(String.class), builder.getReadVariableManager().createId(property.getName() + "Item"), builder.getXSR().invoke("getElementAsXmlList"));
            block = forEach.body();
            value = forEach.var();
        }

        // read and set value
        Class targetType = toClass(property.getComponentType());

        String propertyName = property.getName();
        propertyName = builder.getReadVariableManager().createId(propertyName);

        JExpression toSet;
        if (property.getAdapterType() != null) {
            JVar adapterVar = builder.getAdapter(property.getAdapterType());

            JVar xmlValueVar = block.decl(context.toJClass(String.class), builder.getReadVariableManager().createId(propertyName + "Raw"), value);
            block.add(new JBlankLine());

            JVar valueVar = block.decl(context.toJClass(targetType), propertyName, JExpr._null());
            JVar isConvertedVar = block.decl(context.toJType(boolean.class), builder.getReadVariableManager().createId(propertyName + "Converted"));

            JTryBlock tryBlock = block._try();
            tryBlock.body().assign(valueVar, adapterVar.invoke("unmarshal").arg(xmlValueVar));
            tryBlock.body().assign(isConvertedVar, JExpr.TRUE);

            JCatchBlock catchException = tryBlock._catch(context.toJClass(Exception.class));
            JBlock catchBody = catchException.body();
            catchBody.invoke(builder.getReadContextVar(), "xmlAdapterError")
                    .arg(xsrVar)
                    .arg(context.dotclass(property.getAdapterType()))
                    .arg(context.dotclass(targetType)) // currently we only support conversion between same type
                    .arg(context.dotclass(targetType))
                    .arg(catchException.param("e"));
            catchBody.assign(isConvertedVar, JExpr.FALSE);

            block.add(new JBlankLine());

            toSet = valueVar;
            block = block._if(isConvertedVar)._then();
        } else if (!property.isCollection() && (targetType.equals(Byte.class) || targetType.equals(byte.class))) {
            // todo why the special read method for byte?
            toSet = JExpr.cast(context.toJType(byte.class), xsrVar.invoke("getElementAsInt"));
        } else if (isBuiltinType(targetType)) {
            toSet = coerce(builder, builder.getXSR(), value, targetType);
        } else if (!property.isCollection() && targetType.equals(byte[].class)) {
            toSet = context.toJClass(BinaryUtils.class).staticInvoke("decodeAsBytes").arg(xsrVar);
        } else if (!property.isCollection() && targetType.equals(QName.class)) {
            toSet = xsrVar.invoke("getElementAsQName");
        } else if (!property.isCollection() && (targetType.equals(DataHandler.class) || targetType.equals(Image.class))) {
            // todo support AttachmentMarshaller
            toSet = JExpr._null();
        } else {
            logger.severe("Could not map element value " + propertyName + " of type " + property.getType());
            toSet = JExpr._null();
        }
        doSet(builder, block, property, beanVar, toSet, collectionVar);
    }

    private JVar handleCollection(JAXBObjectBuilder builder, Property property, JVar beanVar) {
        if (!property.isCollection()) {
            return null;
        }

        Class propertyType = toClass(property.getType());

        JType collectionType;
        if (propertyType.isArray()) {
            Class componentType = propertyType.getComponentType();
            if (Boolean.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.BooleanArray.class);
            } else if (Character.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.CharArray.class);
            } else if (Short.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.ShortArray.class);
            } else if (Integer.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.IntArray.class);
            } else if (Long.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.LongArray.class);
            } else if (Float.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.FloatArray.class);
            } else if (Double.TYPE.equals(componentType)) {
                collectionType = context.toJClass(ArrayUtil.DoubleArray.class);
            } else {
                collectionType = context.toJClass(ArrayList.class).narrow(componentType);
            }
        } else {
            collectionType = context.getGenericType(property.getType());
        }

        String collectionVarName = builder.getReadVariableManager().createId(property.getName());
        JVar collectionVar = builder.getReadMethod().body().decl(collectionType, collectionVarName, JExpr._null());

        // inside of the tail block of the expect element method (at the bottom)...
        //
        // if (collection != null) {
        //     bean.setItemCollection(collection);
        // }
        JExpression collectionAssignment = collectionVar;
        if (propertyType.isArray()) {
            if (propertyType.getComponentType().isPrimitive()) {
                collectionAssignment = collectionVar.invoke("toArray");
            } else {
                JArray newArray = JExpr.newArray(context.toJClass(propertyType.getComponentType()), collectionVar.invoke("size"));
                collectionAssignment = collectionVar.invoke("toArray").arg(newArray);
            }
        }
        if (property.getField() != null) {
            Field field = property.getField();

            JBlock assignCollectionBlock = builder.getReadTailBlock()._if(collectionVar.ne(JExpr._null()))._then();

            if (!isPrivate(field)) {
                assignCollectionBlock.assign(beanVar.ref(field.getName()), collectionAssignment);
            } else {
                JFieldVar fieldAccessorField = builder.getPrivateFieldAccessor(field);
                assignCollectionBlock.add(fieldAccessorField.invoke("setObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar).arg(collectionAssignment));
            }
        } else {
            // if there is no setter method, the collection is not assigned into the class
            // this assumes that the getter returned the a collection instance and held on to a reference
            Method setter = property.getSetter();
            if (setter != null) {
                JBlock assignCollectionBlock = builder.getReadTailBlock()._if(collectionVar.ne(JExpr._null()))._then();
                if (!isPrivate(setter)) {

                    JTryBlock trySetter = assignCollectionBlock._try();
                    trySetter.body().add(beanVar.invoke(setter.getName()).arg(collectionAssignment));

                    JCatchBlock catchException = trySetter._catch(context.toJClass(Exception.class));
                    catchException.body().invoke(builder.getReadContextVar(), "setterError")
                            .arg(builder.getXSR())
                            .arg(context.dotclass(property.getBean().getType()))
                            .arg(setter.getName())
                            .arg(context.dotclass(setter.getParameterTypes()[0]))
                            .arg(catchException.param("e"));
                } else {
                    JFieldVar propertyAccessorField = builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                    assignCollectionBlock.add(propertyAccessorField.invoke("setObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar).arg(collectionAssignment));
                }
            }
        }
        return collectionVar;
    }

    private void doSet(JAXBObjectBuilder builder, JBlock block, Property property, JVar beanVar, JExpression toSet, JVar collectionVar) {
        if (toSet == null) {
            return;
        }

        if (property.isId()) {
            JVar id;
            if (toSet instanceof JVar) {
                id = (JVar) toSet;
            } else {
                id = block.decl(context.toJClass(String.class), builder.getReadVariableManager().createId(property.getName()), toSet);
                toSet = id;
            }
            block.invoke(builder.getReadContextVar(), "addXmlId").arg(builder.getXSR()).arg(id).arg(beanVar);
        }

        if (!property.isCollection()) {
            setSingleValue(builder, block, property, beanVar, toSet);
        } else {
            addCollectionItem(builder, block, property, beanVar, toSet, collectionVar);
        }
    }

    private void setSingleValue(JAXBObjectBuilder builder, JBlock block, Property property, JVar bean, JExpression value) {
        // If enum returned null, then the enum value was invalid, but ValidationEventHandler
        // chose to continue processing.  Since the enum is a bad value we don't wan't to set
        // null into the bean
        if (toClass(property.getComponentType()).isEnum()) {
            block = block._if(value.ne(JExpr._null()))._then();
        }

        if (property.getField() != null) {
            Field field = property.getField();

            if (property.isIdref()) {
                JFieldVar fieldAccessorField = builder.getPrivateFieldAccessor(field);
                JExpression target = JExpr._new(context.toJClass(FieldRefTarget.class)).arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(bean).arg(fieldAccessorField);
                block.add(builder.getReadContextVar().invoke("resolveXmlIdRef").arg(builder.getXSR()).arg(value).arg(target));
            } else if (!isPrivate(field)) {
                block.assign(bean.ref(field.getName()), value);
            } else {
                JFieldVar fieldAccessorField = builder.getPrivateFieldAccessor(field);

                String methodName;
                if (Boolean.TYPE.equals(field.getType())) {
                    methodName = "setBoolean";
                } else if (Byte.TYPE.equals(field.getType())) {
                    methodName = "setByte";
                } else if (Character.TYPE.equals(field.getType())) {
                    methodName = "setChar";
                } else if (Short.TYPE.equals(field.getType())) {
                    methodName = "setShort";
                } else if (Integer.TYPE.equals(field.getType())) {
                    methodName = "setInt";
                } else if (Long.TYPE.equals(field.getType())) {
                    methodName = "setLong";
                } else if (Float.TYPE.equals(field.getType())) {
                    methodName = "setFloat";
                } else if (Double.TYPE.equals(field.getType())) {
                    methodName = "setDouble";
                } else {
                    methodName = "setObject";
                }
                block.add(fieldAccessorField.invoke(methodName).arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(bean).arg(value));
            }
        } else if (property.getSetter() != null) {
            Method setter = property.getSetter();
            if (property.isIdref()) {
                JFieldVar propertyAccessorField = builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                JExpression target = JExpr._new(context.toJClass(FieldRefTarget.class)).arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(bean).arg(propertyAccessorField);
                block.add(builder.getReadContextVar().invoke("resolveXmlIdRef").arg(builder.getXSR()).arg(value).arg(target));
            } else if (!isPrivate(setter)) {
                JTryBlock trySetter = block._try();
                trySetter.body().add(bean.invoke(property.getSetter().getName()).arg(value));

                JCatchBlock catchException = trySetter._catch(context.toJClass(Exception.class));
                catchException.body().invoke(builder.getReadContextVar(), "setterError")
                        .arg(builder.getXSR())
                        .arg(context.dotclass(property.getBean().getType()))
                        .arg(setter.getName())
                        .arg(context.dotclass(setter.getParameterTypes()[0]))
                        .arg(catchException.param("e"));
            } else {
                JFieldVar propertyAccessorField = builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                block.add(propertyAccessorField.invoke("setObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(bean).arg(value));
            }
        } else {
            throw new BuildException("Property does not have a setter: " + property.getBean().getType().getName() + "." + property.getName());
        }
    }

    private void addCollectionItem(JAXBObjectBuilder builder, JBlock block, Property property, JVar beanVar, JExpression toSet, JVar collectionVar) {
        // if (collection == null) {
        JBlock createCollectionBlock = block._if(collectionVar.eq(JExpr._null()))._then();

        //     collection = (Collection) bean.getItemCollection();
        Class propertyType = toClass(property.getType());
        if (propertyType.isArray()) {
            createCollectionBlock.assign(collectionVar, JExpr._new(collectionVar.type()));
        } else if (property.getField() == null && property.getGetter() == null) {
            // write only collections are not allowed by the spec, but we can support it anyway
            JType collectionType = getCollectionClass(property.getType(), property.getComponentType());
            if (collectionType == null) {
                throw new BuildException("Collection property does not have a getter and collection does not have a default constructor: " +
                        property.getBean().getType().getName() + "." + property.getName());
            }
            createCollectionBlock.assign(collectionVar, JExpr._new(collectionType));
        } else {
            if (property.getField() != null) {
                Field field = property.getField();

                if (!isPrivate(field)) {
                    createCollectionBlock.assign(collectionVar, beanVar.ref(field.getName()));
                } else {
                    JFieldVar fieldAccessorField = builder.getPrivateFieldAccessor(field);
                    createCollectionBlock.assign(collectionVar, fieldAccessorField.invoke("getObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar));
                }
            } else {
                Method getter = property.getGetter() ;
                if (!isPrivate(getter)) {
                    JTryBlock tryGetter = createCollectionBlock._try();
                    tryGetter.body().assign(collectionVar, beanVar.invoke(getter.getName()));

                    JCatchBlock catchException = tryGetter._catch(context.toJClass(Exception.class));
                    catchException.body().invoke(builder.getReadContextVar(), "getterError")
                            .arg(builder.getXSR())
                            .arg(context.dotclass(property.getBean().getType()))
                            .arg(getter.getName())
                            .arg(catchException.param("e"));
                    catchException.body()._continue();
                } else {
                    JFieldVar propertyAccessorField = builder.getPrivatePropertyAccessor(property.getGetter(), property.getSetter(), property.getName());
                    createCollectionBlock.assign(collectionVar, propertyAccessorField.invoke("getObject").arg(builder.getXSR()).arg(builder.getReadContextVar()).arg(beanVar));
                }
            }

            //     if (collection != null) {
            //         collection.clear();
            JConditional arrayFoundCondition = createCollectionBlock._if(collectionVar.ne(JExpr._null()));
            arrayFoundCondition._then().invoke(collectionVar, "clear");

            //     } else {
            //         collection = new ArrayList();
            JType collectionType = getCollectionClass(property.getType(), property.getComponentType());
            if (collectionType != null) {
                arrayFoundCondition._else().assign(collectionVar, JExpr._new(collectionType));
            } else {
                arrayFoundCondition._else().invoke(builder.getReadContextVar(), "uncreatableCollection")
                        .arg(builder.getXSR())
                        .arg(context.dotclass(property.getBean().getType()))
                        .arg(property.getName())
                        .arg(context.dotclass(property.getType()));
                arrayFoundCondition._else()._continue();
            }
        }
        //     }
        // }

        // collection.add(item);
        if (property.isIdref()) {
            JExpression target = JExpr._new(context.toJClass(CollectionRefTarget.class)).arg(collectionVar);
            block.add(builder.getReadContextVar().invoke("resolveXmlIdRef").arg(builder.getXSR()).arg(toSet).arg(target));
        } else {
            block.add(collectionVar.invoke("add").arg(toSet));
        }
    }

    private JType getCollectionClass(Type collectionType, Type itemType) {
        Class collectionClass = toClass(collectionType);
        if (!collectionClass.isInterface()) {
            try {
                collectionClass.getConstructor();
                return context.getGenericType(collectionType);
            } catch (NoSuchMethodException e) {
            }
        } else if (SortedSet.class.equals(collectionClass)) {
            return context.toJClass(TreeSet.class).narrow(context.getGenericType(itemType));
        } else if (Set.class.equals(collectionClass)) {
            return context.toJClass(LinkedHashSet.class).narrow(context.getGenericType(itemType));
        } else if (Queue.class.equals(collectionClass)) {
            return context.toJClass(LinkedList.class).narrow(context.getGenericType(itemType));
        } else if (List.class.equals(collectionClass)) {
            return context.toJClass(ArrayList.class).narrow(context.getGenericType(itemType));
        } else if (Collection.class.equals(collectionClass)) {
            return context.toJClass(ArrayList.class).narrow(context.getGenericType(itemType));
        }
        return null;
    }

    private JType getMapClass(Type mapType, Type itemType) {
        Class mapClass = toClass(mapType);
        if (!mapClass.isInterface()) {
            try {
                mapClass.getConstructor();
                return context.getGenericType(mapType);
            } catch (NoSuchMethodException e) {
            }
        } else if (Map.class.equals(mapClass)) {
            return context.toJClass(LinkedHashMap.class).narrow(context.getGenericType(QName.class), context.getGenericType(itemType));
        }
        return null;
    }

    private JInvocation newJaxBElement(JVar xsrVar, Class type, JExpression expression) {
        if (JAXBElement.class.equals(type)) {
            throw new IllegalArgumentException("Can't wrap a JAXBElement with a JAXBElement");
        }
        JType jaxbElementType = context.toJClass(JAXBElement.class).narrow(type);
        JInvocation newJaxBElement = JExpr._new(jaxbElementType)
                .arg(xsrVar.invoke("getName"))
                .arg(JExpr.dotclass(context.toJClass(type)))
                .arg(expression);
        return newJaxBElement;
    }

    private JVar as(JAXBObjectBuilder builder, JExpression attributeVar, JBlock block, Class<?> cls, String name) {
        return block.decl(context.toJType(cls), name, coerce(builder, builder.getXSR(), attributeVar, cls));
    }

    private JVar as(JAXBObjectBuilder builder, JVar xsrVar, JBlock block, Class<?> cls, String name, boolean nillable) {
        JExpression value = coerce(builder, xsrVar, xsrVar.invoke("getElementAsString"), cls);

        JVar var;
        if (!cls.isPrimitive() && nillable) {
            var = block.decl(context.toJType(cls), name, JExpr._null());
            JConditional cond = block._if(xsrVar.invoke("isXsiNil").not());

            cond._then().assign(var, value);
        } else {
            var = block.decl(context.toJType(cls), name, value);
        }

        return var;
    }

    private boolean isBuiltinType(Class type) {
        return type.equals(boolean.class) ||
                type.equals(byte.class) ||
                type.equals(short.class) ||
                type.equals(int.class) ||
                type.equals(long.class) ||
                type.equals(float.class) ||
                type.equals(double.class) ||
                type.equals(String.class) ||
                type.equals(Boolean.class) ||
                type.equals(Byte.class) ||
                type.equals(Short.class) ||
                type.equals(Integer.class) ||
                type.equals(Long.class) ||
                type.equals(Float.class) ||
                type.equals(Double.class) ||
                type.equals(XMLGregorianCalendar.class) ||
                type.equals(Duration.class) ||
                type.equals(BigDecimal.class) ||
                type.equals(BigInteger.class) ||
                type.isEnum();
    }

    private JExpression coerce(JAXBObjectBuilder builder, JVar xsrVar, JExpression stringValue, Class<?> destType) {
        if (destType.isPrimitive()) {
            if (destType.equals(boolean.class)) {
                return JExpr.lit("1").invoke("equals").arg(stringValue).cor(JExpr.lit("true").invoke("equals").arg(stringValue));
            } else if (destType.equals(byte.class)) {
                return context.toJClass(Byte.class).staticInvoke("parseByte").arg(stringValue);
            } else if (destType.equals(short.class)) {
                return context.toJClass(Short.class).staticInvoke("parseShort").arg(stringValue);
            } else if (destType.equals(int.class)) {
                return context.toJClass(Integer.class).staticInvoke("parseInt").arg(stringValue);
            } else if (destType.equals(long.class)) {
                return context.toJClass(Long.class).staticInvoke("parseLong").arg(stringValue);
            } else if (destType.equals(float.class)) {
                return context.toJClass(Float.class).staticInvoke("parseFloat").arg(stringValue);
            } else if (destType.equals(double.class)) {
                return context.toJClass(Double.class).staticInvoke("parseDouble").arg(stringValue);
            }
        } else {
            if (destType.equals(String.class)) {
                return stringValue;
            } else if (destType.equals(Boolean.class)) {
                return JExpr.lit("1").invoke("equals").arg(stringValue).cor(JExpr.lit("true").invoke("equals").arg(stringValue));
            } else if (destType.equals(Byte.class)) {
                return context.toJClass(Byte.class).staticInvoke("valueOf").arg(stringValue);
            } else if (destType.equals(Short.class)) {
                return context.toJClass(Short.class).staticInvoke("valueOf").arg(stringValue);
            } else if (destType.equals(Integer.class)) {
                return context.toJClass(Integer.class).staticInvoke("valueOf").arg(stringValue);
            } else if (destType.equals(Long.class)) {
                return context.toJClass(Long.class).staticInvoke("valueOf").arg(stringValue);
            } else if (destType.equals(Float.class)) {
                return context.toJClass(Float.class).staticInvoke("valueOf").arg(stringValue);
            } else if (destType.equals(Double.class)) {
                return context.toJClass(Double.class).staticInvoke("valueOf").arg(stringValue);
            } else if (destType.equals(XMLGregorianCalendar.class)) {
                return builder.getDatatypeFactory().invoke("newXMLGregorianCalendar").arg(stringValue);
            } else if (destType.equals(Duration.class)) {
                return builder.getDatatypeFactory().invoke("newDuration").arg(stringValue);
            } else if (destType.equals(BigDecimal.class)) {
                return JExpr._new(context.toJClass(BigDecimal.class)).arg(stringValue);
            } else if (destType.equals(BigInteger.class)) {
                return JExpr._new(context.toJClass(BigInteger.class)).arg(stringValue);
            } else if (destType.isEnum()) {
                JAXBEnumBuilder enumBuilder = enumBuilders.get(destType);
                if (enumBuilder == null) {
                    throw new BuildException("Unknown enum type " + destType);
                }
                return invokeEnumParser(builder, xsrVar, enumBuilder, stringValue);
            }
        }
        throw new UnsupportedOperationException("Invalid type " + destType);
    }
}
TOP

Related Classes of com.envoisolutions.sxc.jaxb.ReaderIntrospector

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.