/** * Copyright © 2006-2016 Web Cohesion (info@webcohesion.com) * * 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 com.webcohesion.enunciate.modules.jaxb.model; import com.webcohesion.enunciate.EnunciateException; import com.webcohesion.enunciate.javac.decorations.Annotations; import com.webcohesion.enunciate.javac.decorations.TypeMirrorDecorator; import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror; import com.webcohesion.enunciate.javac.decorations.type.TypeMirrorUtils; import com.webcohesion.enunciate.modules.jaxb.EnunciateJaxbContext; import com.webcohesion.enunciate.modules.jaxb.model.types.XmlClassType; import com.webcohesion.enunciate.modules.jaxb.model.types.XmlTypeFactory; import com.webcohesion.enunciate.modules.jaxb.model.util.JAXBUtil; import com.webcohesion.enunciate.util.BeanValidationUtils; import javax.lang.model.element.TypeElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.xml.bind.annotation.*; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.Callable; /** * An accessor that is marshalled in xml to an xml element. * * @author Ryan Heaton */ @SuppressWarnings ( "unchecked" ) public class Element extends Accessor { private final XmlElement xmlElement; private final Collection<Element> choices; private boolean isChoice = false; public Element(javax.lang.model.element.Element delegate, TypeDefinition typedef, EnunciateJaxbContext context) { super(delegate, typedef, context); XmlElement xmlElement = getAnnotation(XmlElement.class); XmlElements xmlElements = getAnnotation(XmlElements.class); if (xmlElements != null) { XmlElement[] elementChoices = xmlElements.value(); if (elementChoices.length == 0) { xmlElements = null; } else if ((xmlElement == null) && (elementChoices.length == 1)) { xmlElement = elementChoices[0]; xmlElements = null; } } this.xmlElement = xmlElement; this.choices = new ArrayList<Element>(); if (xmlElements != null) { for (final XmlElement element : xmlElements.value()) { DecoratedTypeMirror typeMirror = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return element.type(); } }, this.env, XmlElement.DEFAULT.class); if ((typeMirror instanceof ArrayType && ((ArrayType)typeMirror).getComponentType().getKind() != TypeKind.BYTE) || (typeMirror.isCollection())) { throw new EnunciateException("Member " + getName() + " of " + typedef.getQualifiedName() + ": an element choice must not be a collection or an array."); } this.choices.add(new Element(getDelegate(), getTypeDefinition(), element, context)); } } else { this.choices.add(this); } } /** * Construct an element accessor with a specific element annotation. * * @param delegate The delegate. * @param typedef The type definition. * @param xmlElement The specific element annotation. */ protected Element(javax.lang.model.element.Element delegate, TypeDefinition typedef, XmlElement xmlElement, EnunciateJaxbContext context) { super(delegate, typedef, context); this.xmlElement = xmlElement; this.choices = new ArrayList<Element>(); this.choices.add(this); this.isChoice = true; } // Inherited. public String getName() { String propertyName = getSimpleName().toString(); if ((xmlElement != null) && (!"##default".equals(xmlElement.name()))) { propertyName = xmlElement.name(); } return propertyName; } // Inherited. public String getNamespace() { String namespace = null; if (getForm() == XmlNsForm.QUALIFIED) { namespace = getTypeDefinition().getNamespace(); } if ((xmlElement != null) && (!"##default".equals(xmlElement.namespace()))) { namespace = xmlElement.namespace(); } return namespace; } /** * The form of this element. * * @return The form of this element. */ public XmlNsForm getForm() { XmlNsForm elementForm = getTypeDefinition().getSchema().getElementFormDefault(); if (elementForm == null || elementForm == XmlNsForm.UNSET) { elementForm = XmlNsForm.UNQUALIFIED; } return elementForm; } /** * The qname for the referenced element, if this element is a reference to a global element, or null if * this element is not a reference element. * * @return The qname for the referenced element, if exists. */ public QName getRef() { QName ref = null; boolean qualified = getForm() == XmlNsForm.QUALIFIED; String typeNamespace = getTypeDefinition().getNamespace(); typeNamespace = typeNamespace == null ? "" : typeNamespace; String elementNamespace = isWrapped() ? getWrapperNamespace() : getNamespace(); elementNamespace = elementNamespace == null ? "" : elementNamespace; if ((!elementNamespace.equals(typeNamespace)) && (qualified || !"".equals(elementNamespace))) { //the namespace is different; must be a ref... return new QName(elementNamespace, isWrapped() ? getWrapperName() : getName()); } else { //check to see if this is an implied ref as per the jaxb spec, section 8.9.1.2 com.webcohesion.enunciate.modules.jaxb.model.types.XmlType baseType = getBaseType(); if ((baseType.isAnonymous()) && (baseType instanceof XmlClassType)) { TypeDefinition baseTypeDef = ((XmlClassType) baseType).getTypeDefinition(); if (baseTypeDef.getAnnotation(XmlRootElement.class) != null) { RootElementDeclaration rootElement = new RootElementDeclaration(baseTypeDef.getDelegate(), baseTypeDef, this.context); ref = new QName(rootElement.getNamespace(), rootElement.getName()); } } } return ref; } /** * The type of an element accessor can be specified by an annotation. * * @return The accessor type. */ @Override public DecoratedTypeMirror getAccessorType() { DecoratedTypeMirror specifiedType = null; if (xmlElement != null) { specifiedType = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return xmlElement.type(); } }, this.env, XmlElement.DEFAULT.class); } if (specifiedType != null) { if (!isChoice) { DecoratedTypeMirror accessorType = super.getAccessorType(); if (accessorType.isCollection()) { TypeElement collectionElement = (TypeElement) (accessorType.isList() ? TypeMirrorUtils.listType(this.env).asElement() : TypeMirrorUtils.collectionType(this.env).asElement()); specifiedType = (DecoratedTypeMirror) TypeMirrorDecorator.decorate(this.env.getTypeUtils().getDeclaredType(collectionElement, specifiedType), this.env); } else if (accessorType.isArray() && !(specifiedType.isArray())) { specifiedType = (DecoratedTypeMirror) TypeMirrorDecorator.decorate(this.env.getTypeUtils().getArrayType(specifiedType), this.env); } } return specifiedType; } return super.getAccessorType(); } /** * The base type of an element accessor can be specified by an annotation. * * @return The base type. */ @Override public com.webcohesion.enunciate.modules.jaxb.model.types.XmlType getBaseType() { if (xmlElement != null) { TypeMirror typeMirror = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return xmlElement.type(); } }, this.env, XmlElement.DEFAULT.class); if (typeMirror != null) { return XmlTypeFactory.getXmlType(typeMirror, this.context); } } return super.getBaseType(); } /** * Whether this element is nillable. * * @return Whether this element is nillable. */ public boolean isNillable() { boolean nillable = false; if (xmlElement != null) { nillable = xmlElement.nillable(); } return nillable; } /** * Whether this element is required. * * @return Whether this element is required. */ public boolean isRequired() { boolean required = BeanValidationUtils.isNotNull(this); if (xmlElement != null && !required) { required = xmlElement.required(); } return required; } /** * The min occurs of this element. * * @return The min occurs of this element. */ public int getMinOccurs() { if (isRequired()) { return 1; } DecoratedTypeMirror accessorType = getAccessorType(); boolean primitive = accessorType.isPrimitive(); if ((!primitive) && (accessorType.isArray())) { //we have to check if the component type if its an array type, too. DecoratedTypeMirror componentType = TypeMirrorUtils.getComponentType(accessorType, this.env); primitive = componentType.isPrimitive() && componentType.getKind() != TypeKind.BYTE; } return primitive ? 1 : 0; } /** * The max occurs of this element. * * @return The max occurs of this element. */ public String getMaxOccurs() { return isCollectionType() ? "unbounded" : "1"; } /** * The default value, or null if none exists. * * @return The default value, or null if none exists. */ public String getDefaultValue() { String defaultValue = null; if ((xmlElement != null) && (!"\u0000".equals(xmlElement.defaultValue()))) { defaultValue = xmlElement.defaultValue(); } return defaultValue; } /** * The choices for this element. * * @return The choices for this element. */ public Collection<? extends Element> getChoices() { return choices; } /** * Whether this xml element is wrapped. * * @return Whether this xml element is wrapped. */ public boolean isWrapped() { return (isCollectionType() && (getAnnotation(XmlElementWrapper.class) != null)); } /** * The name of the wrapper element. * * @return The name of the wrapper element. */ public String getWrapperName() { String name = getSimpleName().toString(); XmlElementWrapper xmlElementWrapper = getAnnotation(XmlElementWrapper.class); if ((xmlElementWrapper != null) && (!"##default".equals(xmlElementWrapper.name()))) { name = xmlElementWrapper.name(); } return name; } /** * The namespace of the wrapper element. * * @return The namespace of the wrapper element. */ public String getWrapperNamespace() { String namespace = null; if (getForm() == XmlNsForm.QUALIFIED) { namespace = getTypeDefinition().getNamespace(); } XmlElementWrapper xmlElementWrapper = getAnnotation(XmlElementWrapper.class); if ((xmlElementWrapper != null) && (!"##default".equals(xmlElementWrapper.namespace()))) { namespace = xmlElementWrapper.namespace(); } return namespace; } /** * Whether the wrapper is nillable. * * @return Whether the wrapper is nillable. */ public boolean isWrapperNillable() { boolean nillable = false; XmlElementWrapper xmlElementWrapper = getAnnotation(XmlElementWrapper.class); if (xmlElementWrapper != null) { nillable = xmlElementWrapper.nillable(); } return nillable; } /** * Whether this is a choice of multiple element refs. * * @return Whether this is a choice of multiple element refs. */ public boolean isElementRefs() { return false; } }