/** * 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.DecoratedDeclaredType; 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.XmlType; import com.webcohesion.enunciate.modules.jaxb.model.types.XmlTypeFactory; import javax.lang.model.element.TypeElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.xml.XMLConstants; import javax.xml.bind.JAXBElement; import javax.xml.bind.annotation.XmlElementRef; import javax.xml.bind.annotation.XmlElementRefs; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.namespace.QName; import java.util.*; import java.util.concurrent.Callable; /** * An accessor that is marshalled in xml to an xml element. * * @author Ryan Heaton */ @SuppressWarnings ( "unchecked" ) public class ElementRef extends Element { private final XmlElementRef xmlElementRef; private final Collection<ElementRef> choices; private final ReferencedElement referencedElement; private boolean isChoice = false; public ElementRef(javax.lang.model.element.Element delegate, TypeDefinition typedef, EnunciateJaxbContext context) { super(delegate, typedef, context); XmlElementRef xmlElementRef = getAnnotation(XmlElementRef.class); XmlElementRefs xmlElementRefs = getAnnotation(XmlElementRefs.class); if (xmlElementRefs != null) { XmlElementRef[] elementRefChoices = xmlElementRefs.value(); if (elementRefChoices.length == 0) { xmlElementRefs = null; } else if ((xmlElementRef == null) && (elementRefChoices.length == 1)) { xmlElementRef = elementRefChoices[0]; xmlElementRefs = null; } } this.xmlElementRef = xmlElementRef; Collection<ElementRef> choices; if (xmlElementRefs != null) { choices = new ArrayList<ElementRef>(); for (XmlElementRef elementRef : xmlElementRefs.value()) { choices.add(new ElementRef(getDelegate(), getTypeDefinition(), elementRef, context)); } this.referencedElement = null; } else { DecoratedTypeMirror accessorType = getBareAccessorType(); if (accessorType.isInstanceOf(JAXBElement.class)) { //this is either a single-valued JAXBElement, or a parametric collection of them... //todo: throw an exception if this is referencing a non-global element for this namespace? choices = new ArrayList<ElementRef>(); choices.add(this); QName refQname = new QName(xmlElementRef.namespace(), xmlElementRef.name()); List<? extends TypeMirror> elementTypeArguments = ((DecoratedDeclaredType) accessorType).getTypeArguments(); DecoratedTypeMirror elementOf = elementTypeArguments == null || elementTypeArguments.size() != 1 ? TypeMirrorUtils.objectType(context.getContext().getProcessingEnvironment()) : (DecoratedTypeMirror) elementTypeArguments.get(0); this.referencedElement = new TypeReferencedElement(refQname, elementOf); } else if (isCollectionType()) { choices = new CollectionOfElementRefChoices(); this.referencedElement = null; } else { choices = new ArrayList<ElementRef>(); choices.add(this); this.referencedElement = loadRef(); } } this.choices = choices; } /** * Construct an element accessor with a specific element ref annotation. * * @param delegate The delegate. * @param typedef The type definition. * @param xmlElementRef The specific element ref annotation. */ protected ElementRef(javax.lang.model.element.Element delegate, TypeDefinition typedef, XmlElementRef xmlElementRef, EnunciateJaxbContext context) { super(delegate, typedef, context); this.xmlElementRef = xmlElementRef; this.choices = new ArrayList<ElementRef>(); this.choices.add(this); this.referencedElement = loadRef(); this.isChoice = true; } /** * Construct an element accessor with a specific base type. * * @param delegate The delegate. * @param typedef The type definition. * @param ref The referenced root element. */ private ElementRef(javax.lang.model.element.Element delegate, TypeDefinition typedef, ElementDeclaration ref, EnunciateJaxbContext context) { super(delegate, typedef, context); this.xmlElementRef = null; this.choices = new ArrayList<ElementRef>(); this.choices.add(this); this.referencedElement = new ElementReferencedElement(ref); this.isChoice = true; } /** * Load the qname of the referenced root element declaration. * * @return the qname of the referenced root element declaration. */ protected ReferencedElement loadRef() { DecoratedTypeMirror refType = null; if (xmlElementRef != null) { refType = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return xmlElementRef.type(); } }, this.env, XmlElementRef.DEFAULT.class); } if (refType == null) { refType = getBareAccessorType(); } TypeElement declaration = null; if (refType.isDeclared()) { declaration = (TypeElement) ((DeclaredType)refType).asElement(); } ReferencedElement referencedElement = null; if (refType.isInstanceOf(JAXBElement.class)) { String localName = xmlElementRef != null && !"##default".equals(xmlElementRef.name()) ? xmlElementRef.name() : null; String namespace = xmlElementRef != null ? xmlElementRef.namespace() : ""; if (localName == null) { throw new EnunciateException("Member " + getName() + " of " + getTypeDefinition().getQualifiedName() + ": @XmlElementRef annotates a type JAXBElement without specifying the name of the JAXB element."); } QName refQname = new QName(namespace, localName); List<? extends TypeMirror> elementTypeArguments = ((DecoratedDeclaredType) refType).getTypeArguments(); DecoratedTypeMirror elementOf = elementTypeArguments == null || elementTypeArguments.size() != 1 ? TypeMirrorUtils.objectType(context.getContext().getProcessingEnvironment()) : (DecoratedTypeMirror) elementTypeArguments.get(0); referencedElement = new TypeReferencedElement(refQname, elementOf); } else if (declaration != null && declaration.getAnnotation(XmlRootElement.class) != null) { RootElementDeclaration refElement = new RootElementDeclaration(declaration, null, this.context); referencedElement = new DeclarationReferencedElement(new QName(refElement.getNamespace(), refElement.getName()), declaration); } if (referencedElement == null) { throw new EnunciateException("Member " + getSimpleName() + " of " + getTypeDefinition().getQualifiedName() + ": " + refType + " is neither JAXBElement nor a root element declaration."); } return referencedElement; } /** * Whether this is a choice of multiple element refs. * * @return Whether this is a choice of multiple element refs. */ public boolean isElementRefs() { return (this.referencedElement == null); } /** * The name of an element ref is the name of the element it references, unless the type is a JAXBElement, in which * case the name is specified. * * @return The name of the element ref. */ @Override public String getName() { if (isElementRefs()) { throw new UnsupportedOperationException("No single reference for this element: multiple choices."); } return this.referencedElement.getQname().getLocalPart(); } /** * The namespace of an element ref is the namespace of the element it references, unless the type is a JAXBElement, in which * case the namespace is specified. * * @return The namespace of the element ref. */ @Override public String getNamespace() { if (isElementRefs()) { throw new UnsupportedOperationException("No single reference for this element: multiple choices."); } //it's kind of weird to return null when the namespace is the default namesapce, but that's what the rest of the classes do... return XMLConstants.NULL_NS_URI.equals(this.referencedElement.getQname().getNamespaceURI()) ? null : this.referencedElement.getQname().getNamespaceURI(); } /** * The ref for this element ref. * * @return The ref. */ @Override public QName getRef() { if (isElementRefs()) { throw new UnsupportedOperationException("No single reference for this element: multiple choices."); } return this.referencedElement.getQname(); } /** * There is no base type for an element ref. * * @throws UnsupportedOperationException Because there is no such things as a base type for an element ref. */ @Override public XmlType getBaseType() { throw new UnsupportedOperationException("There is no base type for an element ref."); } @Override public XmlType getXmlType() { if (isElementRefs()) { throw new UnsupportedOperationException("No single xml type for this element: multiple choices."); } return this.referencedElement.getXmlType(); } @Override public boolean isQNameType() { return false; } /** * The type of an element ref accessor can be specified by an annotation. * * @return The accessor type. */ @Override public DecoratedTypeMirror getAccessorType() { DecoratedTypeMirror specifiedType = null; if (xmlElementRef != null) { specifiedType = Annotations.mirrorOf(new Callable<Class<?>>() { @Override public Class<?> call() throws Exception { return xmlElementRef.type(); } }, this.env, XmlElementRef.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(); } /** * An element ref is not binary data. It may refer to an element that is binary data, though... * * @return false */ @Override public boolean isBinaryData() { return false; } /** * An element ref is not a swa ref. In may refer to an element that is a swa ref, though... * * @return false */ @Override public boolean isSwaRef() { return false; } /** * An element ref is not an MTOM attachment. It may refer to an element that is an MTOM attachment, though... * * @return false */ @Override public boolean isMTOMAttachment() { return false; } /** * An element ref is not nillable. * * @return false */ @Override public boolean isNillable() { return false; } /** * An element ref is not required. * * @return false. */ @Override public boolean isRequired() { return false; } /** * The min occurs of an element ref is 0 * * @return 0 */ @Override public int getMinOccurs() { return isRequired() ? 1 : 0; } /** * The choices for this element. * * @return The choices for this element. */ @Override public Collection<ElementRef> getChoices() { return choices; } @Override public boolean isElementRef() { return true; } /** * Lazy-loaded collection of element ref choices. * * @author Ryan Heaton */ @SuppressWarnings ( "NullableProblems" ) private class CollectionOfElementRefChoices extends AbstractCollection<ElementRef> { @Override public Iterator<ElementRef> iterator() { return lookupRefs().iterator(); } @Override public int size() { return lookupRefs().size(); } private Collection<ElementRef> lookupRefs() { Collection<ElementRef> choices = new ArrayList<ElementRef>(); Collection<QName> qnamesAdded = new HashSet<QName>(); //if it's a parametric collection type, we need to provide a choice between all subclasses of the base type. DecoratedTypeMirror typeMirror = getBareAccessorType(); javax.lang.model.element.Element element = env.getTypeUtils().asElement(typeMirror); if (element != null) { ElementDeclaration xmlElement = context.findElementDeclaration(element); if (xmlElement != null) { if (qnamesAdded.add(xmlElement.getQname())) { choices.add(new ElementRef(getDelegate(), getTypeDefinition(), xmlElement, context)); } } } return choices; } } private interface ReferencedElement { QName getQname(); XmlType getXmlType(); } private class TypeReferencedElement implements ReferencedElement { private final QName qname; private final DecoratedTypeMirror mirror; public TypeReferencedElement(QName qname, DecoratedTypeMirror mirror) { this.qname = qname; this.mirror = mirror; } @Override public QName getQname() { return qname; } @Override public XmlType getXmlType() { return XmlTypeFactory.getXmlType(this.mirror, context); } } private class DeclarationReferencedElement implements ReferencedElement { private final QName qname; private final TypeElement declaration; public DeclarationReferencedElement(QName qname, TypeElement declaration) { this.qname = qname; this.declaration = declaration; } @Override public QName getQname() { return qname; } @Override public XmlType getXmlType() { TypeDefinition typeDefinition = context.findTypeDefinition(this.declaration); if (typeDefinition == null) { throw new IllegalStateException("Cannot find type definition for " + this.declaration); } return new XmlClassType(typeDefinition); } } private class ElementReferencedElement implements ReferencedElement { private final ElementDeclaration element; public ElementReferencedElement(ElementDeclaration element) { this.element = element; } @Override public QName getQname() { return this.element.getQname(); } @Override public XmlType getXmlType() { return this.element instanceof LocalElementDeclaration ? ((LocalElementDeclaration) this.element).getElementXmlType() : new XmlClassType(((RootElementDeclaration)this.element).getTypeDefinition()); } } }