Package com.envoisolutions.sxc.jaxb.model

Source Code of com.envoisolutions.sxc.jaxb.model.RiModelBuilder

/**
*
* 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.model;

import java.beans.Introspector;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Iterator;
import java.util.concurrent.Callable;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.annotation.XmlElementDecl;
import javax.xml.bind.annotation.XmlRegistry;
import javax.xml.bind.annotation.XmlSchema;
import javax.xml.namespace.QName;

import com.envoisolutions.sxc.builder.BuildException;
import com.envoisolutions.sxc.jaxb.JAXBModelFactory;
import com.envoisolutions.sxc.jaxb.JavaUtils;
import com.sun.xml.bind.api.AccessorException;
import com.sun.xml.bind.v2.ContextFactory;
import com.sun.xml.bind.v2.model.core.ID;
import com.sun.xml.bind.v2.model.core.WildcardMode;
import com.sun.xml.bind.v2.model.runtime.RuntimeAttributePropertyInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeClassInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeElement;
import com.sun.xml.bind.v2.model.runtime.RuntimeElementInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeElementPropertyInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeEnumLeafInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeLeafInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimePropertyInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeReferencePropertyInfo;
import com.sun.xml.bind.v2.model.runtime.RuntimeTypeInfoSet;
import com.sun.xml.bind.v2.model.runtime.RuntimeTypeRef;
import com.sun.xml.bind.v2.model.runtime.RuntimeValuePropertyInfo;
import com.sun.xml.bind.v2.runtime.JAXBContextImpl;
import com.sun.xml.bind.v2.runtime.Transducer;
import com.sun.xml.bind.v2.runtime.reflect.Accessor;

public class RiModelBuilder {
    private final JAXBContextImpl context;
    private final Model model;

    public RiModelBuilder(Map<String, ?> properties, Class... classes) throws JAXBException {
        Map<String, Object> riProperties = new LinkedHashMap<String, Object>(properties);
        for (Iterator<String> iterator = riProperties.keySet().iterator(); iterator.hasNext();) {
            String key =  iterator.next();
            if (key.startsWith("com.envoisolutions")) {
                iterator.remove();
            }

        }
        context = (JAXBContextImpl) ContextFactory.createContext(classes, riProperties);

        RuntimeTypeInfoSet runtimeTypeInfoSet = JAXBModelFactory.create(context, classes);

        model = new Model();

        for (RuntimeEnumLeafInfo runtimeEnumLeafInfo : runtimeTypeInfoSet.enums().values()) {
            addEnum(model, runtimeEnumLeafInfo);
        }

        for (RuntimeClassInfo runtimeClassInfo : runtimeTypeInfoSet.beans().values()) {
            addBean(model, runtimeClassInfo);
        }

        for (Class clazz : classes) {
            if (clazz.isAnnotationPresent(XmlRegistry.class)) {
                addXmlRegistry(clazz);
            }
        }
    }

    public Model getModel() {
        return model;
    }

    public Callable<JAXBContext> getContext() {
        return new Callable<JAXBContext>() {
            public JAXBContext call() throws Exception {
                return context;
            }
        };
    }

    @SuppressWarnings({"unchecked"})
    private EnumInfo addEnum(Model model, RuntimeEnumLeafInfo runtimeEnumLeafInfo) {
        // compiler can't handle the wacky over loaded methods in the jaxb ri
        // so we convert it to a simpler type
        RuntimeLeafInfo leafInfo = runtimeEnumLeafInfo;

        EnumInfo enumInfo = model.getEnum(leafInfo.getClazz());

        if (enumInfo == null) {
            if (Modifier.isAbstract(leafInfo.getClazz().getModifiers())) {
                return null;
            }

            enumInfo = new EnumInfo(model, leafInfo.getClazz());
            enumInfo.setRootElementName(runtimeEnumLeafInfo.getElementName());
            enumInfo.setSchemaTypeName(runtimeEnumLeafInfo.getTypeName());

            Class<?> type = enumInfo.getType();
            Enum[] enumValues;
            try {
                Method method = type.getMethod("values");
                enumValues = (Enum[]) method.invoke(null);
            } catch (Exception e) {
                throw new BuildException("Class is not an enumeration " + type.getName());
            }

            if (enumValues.length == 0) {
                throw new BuildException("Enum contains no values " + type.getName());
            }

            Transducer transducer = (Transducer) runtimeEnumLeafInfo;
            for (Enum enumValue : enumValues) {
                String enumText;
                try {
                    enumText = transducer.print(enumValue).toString();
                } catch (AccessorException e) {
                    throw new BuildException(e);
                }
                enumInfo.getEnumMap().put(enumValue, enumText);
            }

            model.addEnum(enumInfo);
        }

        return enumInfo;
    }

    private Bean addBean(Model model, RuntimeClassInfo runtimeClassInfo) {
        Bean bean = model.getBean(runtimeClassInfo.getClazz());

        if (bean == null) {
            bean = new Bean(model, runtimeClassInfo.getClazz());

            // bean must be added to model before initializing
            model.addBean(bean);

            initBean(model, bean, runtimeClassInfo);
        }

        return bean;
    }

    private void initBean(Model model, Bean bean, RuntimeClassInfo runtimeClassInfo) {
        bean.setRootElementName(runtimeClassInfo.getElementName());
        bean.setSchemaTypeName(runtimeClassInfo.getTypeName());

        for (RuntimePropertyInfo runtimePropertyInfo : runtimeClassInfo.getProperties()) {
            Property property = createProperty(bean, runtimePropertyInfo);
            bean.addProperty(property);
        }

        if (runtimeClassInfo.declaresAttributeWildcard()) {
            Accessor anyAttributeAccessor = runtimeClassInfo.getAttributeWildcard();
            Property anyAttributeProperty;

            // if we have an AdaptedAccessor wrapper, strip off wrapper but preserve the name of the adapter class
            if ("com.sun.xml.bind.v2.runtime.reflect.AdaptedAccessor".equals(anyAttributeAccessor.getClass().getName())) {
                try {
                    Field coreField = anyAttributeAccessor.getClass().getDeclaredField("core");
                    coreField.setAccessible(true);
                    anyAttributeAccessor = (Accessor) coreField.get(anyAttributeAccessor);
                } catch (Throwable e) {
                    throw new BuildException("Unable to access private fields of AdaptedAccessor class", e);
                }
            }

            if (anyAttributeAccessor instanceof Accessor.FieldReflection) {
                Accessor.FieldReflection fieldReflection = (Accessor.FieldReflection) anyAttributeAccessor;
                anyAttributeProperty = new Property(bean, fieldReflection.f.getName());
                anyAttributeProperty.setField(fieldReflection.f);
                anyAttributeProperty.setType(fieldReflection.f.getGenericType());
            } else if (anyAttributeAccessor instanceof Accessor.GetterSetterReflection) {
                Accessor.GetterSetterReflection getterSetterReflection = (Accessor.GetterSetterReflection) anyAttributeAccessor;

                if (getterSetterReflection.getter != null) {
                    String propertyName = getterSetterReflection.getter.getName();
                    if (propertyName.startsWith("get")) {
                        propertyName = Introspector.decapitalize(propertyName.substring(3));
                    } else if (propertyName.startsWith("is")) {
                        propertyName = Introspector.decapitalize(propertyName.substring(2));
                    }
                    anyAttributeProperty = new Property(bean, propertyName);
                    anyAttributeProperty.setType(getterSetterReflection.getter.getGenericReturnType());
                } else if (getterSetterReflection.setter != null) {
                    String propertyName = getterSetterReflection.setter.getName();
                    if (propertyName.startsWith("set")) {
                        propertyName = Introspector.decapitalize(propertyName.substring(3));
                    }
                    anyAttributeProperty = new Property(bean, propertyName);
                    anyAttributeProperty.setType(getterSetterReflection.setter.getGenericParameterTypes()[0]);
                } else {
                    throw new BuildException("Any attribute property for class " + bean + " does not have a getter or setter method");
                }

                anyAttributeProperty.setGetter(getterSetterReflection.getter);
                anyAttributeProperty.setSetter(getterSetterReflection.setter);
            } else {
                throw new BuildException("Unknown property accessor type '" + anyAttributeAccessor.getClass().getName() + "' for class " + bean);
            }           

            anyAttributeProperty.setXmlStyle(Property.XmlStyle.ATTRIBUTE);
            anyAttributeProperty.setComponentType(String.class);
            anyAttributeProperty.setXmlAny(true);
            bean.addProperty(anyAttributeProperty);
        }


        Bean baseClass = null;
        RuntimeClassInfo baseClassInfo = runtimeClassInfo.getBaseClass();
        if (baseClassInfo != null) {
            baseClass = model.getBean(baseClassInfo.getClazz());
            if (baseClass == null) {
                baseClass = addBean(model, baseClassInfo);
            }
        }
        bean.setBaseClass(baseClass);
    }

    private Property createProperty(Bean bean, RuntimePropertyInfo runtimePropertyInfo) {
        Property property = new Property(bean, runtimePropertyInfo.getName());

        property.setType(runtimePropertyInfo.getRawType());
        property.setComponentType(runtimePropertyInfo.getIndividualType());

        property.setId(runtimePropertyInfo.id() == ID.ID);
        property.setIdref(runtimePropertyInfo.id() == ID.IDREF);
        property.setCollection(runtimePropertyInfo.isCollection());

        Accessor accessor = runtimePropertyInfo.getAccessor();

        if (runtimePropertyInfo.getAdapter() != null) {
            property.setAdapterType(runtimePropertyInfo.getAdapter().adapterType);
            property.setComponentType(runtimePropertyInfo.getAdapter().customType);
            property.setComponentAdaptedType(JavaUtils.toClass(runtimePropertyInfo.getAdapter().defaultType));
        }

        // if we have an AdaptedAccessor wrapper, strip off wrapper but preserve the name of the adapter class
        if ("com.sun.xml.bind.v2.runtime.reflect.AdaptedAccessor".equals(accessor.getClass().getName())) {
            try {

                Field coreField = accessor.getClass().getDeclaredField("core");
                coreField.setAccessible(true);
                accessor = (Accessor) coreField.get(accessor);

            } catch (Throwable e) {
                throw new BuildException("Unable to access private fields of AdaptedAccessor class", e);
            }
        }

        if (accessor instanceof Accessor.FieldReflection) {
            Accessor.FieldReflection fieldReflection = (Accessor.FieldReflection) accessor;
            property.setField(fieldReflection.f);
        } else if (accessor instanceof Accessor.GetterSetterReflection) {
            Accessor.GetterSetterReflection getterSetterReflection = (Accessor.GetterSetterReflection) accessor;
            property.setGetter(getterSetterReflection.getter);
            property.setSetter(getterSetterReflection.setter);
        } else {
            throw new BuildException("Unknown property accessor type '" + accessor.getClass().getName() + "' for property " + property);
        }

        if (runtimePropertyInfo instanceof RuntimeAttributePropertyInfo) {
            RuntimeAttributePropertyInfo attributeProperty = (RuntimeAttributePropertyInfo) runtimePropertyInfo;
            property.setXmlStyle(Property.XmlStyle.ATTRIBUTE);
            property.setXmlName(attributeProperty.getXmlName());
            property.setRequired(attributeProperty.isRequired());
            if (property.isCollection()) property.setXmlList(true);
        } else if (runtimePropertyInfo instanceof RuntimeElementPropertyInfo) {
            RuntimeElementPropertyInfo elementProperty = (RuntimeElementPropertyInfo) runtimePropertyInfo;
            property.setXmlStyle(Property.XmlStyle.ELEMENT);
            if (!elementProperty.isValueList()) {
                property.setXmlName(elementProperty.getXmlName());
                for (RuntimeTypeRef typeRef : elementProperty.getTypes()) {
                    ElementMapping elementMapping = createXmlMapping(property, typeRef);
                    property.getElementMappings().add(elementMapping);
                }
                property.setRequired(elementProperty.isRequired());
                property.setNillable(elementProperty.isCollectionNillable());
            } else {
                property.setXmlList(true);

                if (elementProperty.getTypes().size() != 1) throw new BuildException("Expected 1 element mapped to property " + property + " but there are " + elementProperty.getTypes().size() + " mappings");
                RuntimeTypeRef elementType = elementProperty.getTypes().get(0);
                ElementMapping elementMapping = createXmlMapping(property, elementType);
                elementMapping.setNillable(false);
                property.getElementMappings().add(elementMapping);

                property.setXmlName(elementType.getTagName());
                property.setRequired(false);
                property.setNillable(false);
            }
        } else  if (runtimePropertyInfo instanceof RuntimeReferencePropertyInfo) {
            RuntimeReferencePropertyInfo referenceProperty = (RuntimeReferencePropertyInfo) runtimePropertyInfo;
            property.setXmlStyle(Property.XmlStyle.ELEMENT_REF);
            for (RuntimeElement re : referenceProperty.getElements()) {
                ElementMapping elementMapping;
                if (re instanceof RuntimeElementInfo) {
                    RuntimeElementInfo runtimeElement = (RuntimeElementInfo) re;
                    elementMapping = createXmlMapping(property, runtimeElement);
                } else {
                    RuntimeClassInfo runtimeClassInfo = (RuntimeClassInfo) re;
                    elementMapping = createXmlMapping(property, runtimeClassInfo);
                }
                property.getElementMappings().add(elementMapping);
            }
            property.setNillable(referenceProperty.isCollectionNillable());
            property.setXmlAny(referenceProperty.getWildcard() != null);
            property.setLax(referenceProperty.getWildcard() == WildcardMode.LAX);
            property.setMixed(referenceProperty.isMixed());
        } else if (runtimePropertyInfo instanceof RuntimeValuePropertyInfo) {
            property.setXmlStyle(Property.XmlStyle.VALUE);
            if (property.isCollection()) property.setXmlList(true);
        } else {
            throw new BuildException("Unknown property type " + runtimePropertyInfo.getClass().getName());
        }

        return property;
    }

    private ElementMapping createXmlMapping(Property property, RuntimeTypeRef runtimeTypeRef) {
        ElementMapping mapping = new ElementMapping(property, runtimeTypeRef.getTagName());

        mapping.setNillable(runtimeTypeRef.isNillable());

        if (property.getAdapterType() == null) {
            mapping.setComponentType(runtimeTypeRef.getTarget().getType());
        } else {
            mapping.setComponentType(property.getComponentType());
        }

        return mapping;
    }

    private ElementMapping createXmlMapping(Property property, RuntimeElementInfo runtimeElement) {
        ElementMapping mapping = new ElementMapping(property, runtimeElement.getElementName());

        if (property.getAdapterType() == null) {
            mapping.setComponentType(runtimeElement.getContentType().getType());
        } else {
            mapping.setComponentType(property.getComponentType());
        }

        return mapping;
    }

    private ElementMapping createXmlMapping(Property property, RuntimeClassInfo runtimeClassInfo) {
        ElementMapping mapping = new ElementMapping(property, runtimeClassInfo.getElementName());

        if (property.getAdapterType() == null) {
            mapping.setComponentType(runtimeClassInfo.getClazz());
        } else {
            mapping.setComponentType(property.getComponentType());           
        }

        return mapping;
    }

    private void addXmlRegistry(Class clazz) {
        ObjectFactory objectFactory = new ObjectFactory(model, clazz);
        model.getObjectFactories().add(objectFactory);
        String pkgNamespace = null;
        for (Method method : clazz.getMethods()) {
            if (method.isAnnotationPresent(XmlElementDecl.class)) {
                Class<?> type = null;
                if (method.getParameterTypes().length > 0) {
                    // according to the JAXB team it is more reliable to determine referenced class type
                    // from the method parameter type instead of the return type
                    type = method.getParameterTypes()[0];
                } else if (method.getReturnType().equals(JAXBElement.class)) {
                    // return type should be a parameterized JAXBElement
                    Type returnType = method.getGenericReturnType();
                    if (returnType instanceof ParameterizedType) {
                        ParameterizedType parameterizedType = (ParameterizedType) returnType;
                        if (parameterizedType.getActualTypeArguments().length > 0) {
                            Type typeArg = parameterizedType.getActualTypeArguments()[0];
                            type = JavaUtils.toClass(typeArg);
                        }
                    }
                }

                if (type != null) {
                    XmlElementDecl xmlElementDecl = method.getAnnotation(XmlElementDecl.class);
                    String name = xmlElementDecl.name();
                    String namespace = xmlElementDecl.namespace();
                    if ("##default".equals(namespace)) {
                        if (pkgNamespace == null) {
                            Package pkg = clazz.getPackage();
                            if (pkg != null) {
                                XmlSchema annotation = pkg.getAnnotation(XmlSchema.class);
                                if (annotation != null) {
                                    pkgNamespace = annotation.namespace();
                                }
                            }
                            if (pkgNamespace == null || "##default".equals(pkgNamespace)) {
                                pkgNamespace = "";
                            }
                        }
                        namespace = pkgNamespace;
                    }

                    objectFactory.getRootElements().put(new QName(namespace, name), type);

                    Bean bean = model.getBean(type);
                    if (bean != null) {
                        objectFactory.getDependencies().add(bean);
                    }
                }
            } else if(method.getName().startsWith("create")) {
                Bean bean = model.getBean(method.getReturnType());
                if (bean != null) {
                    objectFactory.getDependencies().add(bean);
                }
            }
        }
    }
}
TOP

Related Classes of com.envoisolutions.sxc.jaxb.model.RiModelBuilder

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.