/**
* 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.facets.Facet;
import com.webcohesion.enunciate.facets.HasFacets;
import com.webcohesion.enunciate.javac.decorations.Annotations;
import com.webcohesion.enunciate.javac.decorations.TypeMirrorDecorator;
import com.webcohesion.enunciate.javac.decorations.element.DecoratedElement;
import com.webcohesion.enunciate.javac.decorations.element.DecoratedTypeElement;
import com.webcohesion.enunciate.javac.decorations.element.PropertyElement;
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.metadata.ClientName;
import com.webcohesion.enunciate.metadata.qname.XmlQNameEnumRef;
import com.webcohesion.enunciate.modules.jaxb.EnunciateJaxbContext;
import com.webcohesion.enunciate.modules.jaxb.model.adapters.Adaptable;
import com.webcohesion.enunciate.modules.jaxb.model.adapters.AdapterType;
import com.webcohesion.enunciate.modules.jaxb.model.types.KnownXmlType;
import com.webcohesion.enunciate.modules.jaxb.model.types.XmlType;
import com.webcohesion.enunciate.modules.jaxb.model.types.XmlTypeFactory;
import com.webcohesion.enunciate.modules.jaxb.model.util.JAXBUtil;
import com.webcohesion.enunciate.modules.jaxb.model.util.MapType;
import com.webcohesion.enunciate.util.HasClientConvertibleType;
import com.webcohesion.enunciate.util.OptionalUtils;
import javax.activation.DataHandler;
import javax.lang.model.element.Element;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.*;
import javax.lang.model.util.ElementFilter;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
import java.util.*;
import java.util.concurrent.Callable;
/**
* An accessor for a field or method value into a type.
*
* @author Ryan Heaton
*/
@SuppressWarnings ( "unchecked" )
public abstract class Accessor extends DecoratedElement<javax.lang.model.element.Element> implements Adaptable, HasFacets, HasClientConvertibleType {
final TypeDefinition typeDefinition;
final AdapterType adapterType;
final Set<Facet> facets = new TreeSet<Facet>();
final EnunciateJaxbContext context;
public Accessor(javax.lang.model.element.Element delegate, TypeDefinition typeDef, EnunciateJaxbContext context) {
super(delegate, context.getContext().getProcessingEnvironment());
this.typeDefinition = typeDef;
this.facets.addAll(Facet.gatherFacets(delegate, context.getContext()));
this.facets.addAll(typeDef.getFacets());
this.context = context;
this.adapterType = JAXBUtil.findAdapterType(this, context);
}
/**
* The name of the accessor.
*
* @return The name of the accessor.
*/
public abstract String getName();
/**
* The namespace of the accessor.
*
* @return The namespace of the accessor.
*/
public abstract String getNamespace();
/**
* The JAXB context.
*
* @return The Enunciate JAXB context.
*/
public EnunciateJaxbContext getContext() {
return context;
}
/**
* The simple name for client-side code generation.
*
* @return The simple name for client-side code generation.
*/
public String getClientSimpleName() {
String clientSimpleName = this.delegate.getSimpleName().toString();
ClientName clientName = this.delegate.getAnnotation(ClientName.class);
if (clientName != null) {
clientSimpleName = clientName.value();
}
return clientSimpleName;
}
/**
* The type of the accessor.
*
* @return The type of the accessor.
*/
public DecoratedTypeMirror getAccessorType() {
DecoratedTypeMirror accessorType = (DecoratedTypeMirror) asType();
accessorType = OptionalUtils.stripOptional(accessorType, this.context.getContext().getProcessingEnvironment());
DecoratedDeclaredType normalizedCollection = JAXBUtil.getNormalizedCollection(accessorType, this.context.getContext().getProcessingEnvironment());
if (normalizedCollection != null) {
accessorType = normalizedCollection;
}
else {
MapType mapType = MapType.findMapType(accessorType, this.context);
if (mapType != null) {
accessorType = mapType;
}
}
return accessorType;
}
@Override
public TypeMirror getClientConvertibleType() {
return getAccessorType();
}
/**
* The bare (i.e. unwrapped) type of the accessor.
*
* @return The bare type of the accessor.
*/
public DecoratedTypeMirror getBareAccessorType() {
if (isCollectionType()) {
return getCollectionItemType();
}
if (isXmlList()) {
DecoratedTypeMirror componentType = getCollectionItemType();
return componentType != null ? componentType : getAccessorType();
}
return getAccessorType();
}
/**
* The base xml type of the accessor. The base type is either:
* <p/>
* <ol>
* <li>The xml type of the accessor type.</li>
* <li>The xml type of the component type of the accessor type if the accessor
* type is a collection type.</li>
* </ol>
*
* @return The base type.
*/
public XmlType getBaseType() {
//first check to see if the base type is dictated by a specific annotation.
if (isXmlID()) {
return KnownXmlType.ID;
}
if (isXmlIDREF()) {
return KnownXmlType.IDREF;
}
if (isSwaRef()) {
return KnownXmlType.SWAREF;
}
XmlType xmlType = XmlTypeFactory.findSpecifiedType(this, this.context);
return (xmlType != null) ? xmlType : XmlTypeFactory.getXmlType(getAccessorType(), this.context);
}
/**
* The XML type of the property. This can be different from the "base" type, which doesn't apply to element refs.
*
* @return The XML type of the property.
*/
public XmlType getXmlType() {
return getBaseType();
}
/**
* The qname for the referenced accessor, if this accessor 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() {
return null;
}
/**
* The type definition for this accessor.
*
* @return The type definition for this accessor.
*/
public TypeDefinition getTypeDefinition() {
return typeDefinition;
}
/**
* Whether this accessor is specified as an xml list.
*
* @return Whether this accessor is specified as an xml list.
*/
public boolean isXmlList() {
return this.delegate.getAnnotation(XmlList.class) != null;
}
/**
* Whether this accessor is an XML ID.
*
* @return Whether this accessor is an XMLID.
*/
public boolean isXmlID() {
return this.delegate.getAnnotation(XmlID.class) != null;
}
/**
* Whether this accessor is an XML IDREF.
*
* @return Whether this accessor is an XML IDREF.
*/
public boolean isXmlIDREF() {
return this.delegate.getAnnotation(XmlIDREF.class) != null;
}
/**
* Whether this accessor consists of binary data.
*
* @return Whether this accessor consists of binary data.
*/
public boolean isBinaryData() {
return isSwaRef() || KnownXmlType.BASE64_BINARY.getQname().equals(getBaseType().getQname());
}
/**
* Whether this access is a QName type.
*
* @return Whether this access is a QName type.
*/
public boolean isQNameType() {
return getBaseType() == KnownXmlType.QNAME;
}
/**
* Get the resolved accessor type for this accessor.
*
* @return the resolved accessor type for this accessor.
*/
public DecoratedTypeMirror getResolvedAccessorType() {
DecoratedTypeMirror accessorType = getAccessorType();
if (isAdapted()) {
accessorType = (DecoratedTypeMirror) getAdapterType().getAdaptingType(accessorType, this.context.getContext());
}
return accessorType;
}
/**
* Whether this accessor is a swa ref.
*
* @return Whether this accessor is a swa ref.
*/
public boolean isSwaRef() {
DecoratedTypeMirror<?> accessorType = (DecoratedTypeMirror) getAccessorType();
return (getAnnotation(XmlAttachmentRef.class) != null) && (accessorType.isInstanceOf(DataHandler.class));
}
/**
* Whether this accessor is an MTOM attachment.
*
* @return Whether this accessor is an MTOM attachment.
*/
public boolean isMTOMAttachment() {
return (getAnnotation(XmlInlineBinaryData.class) == null) && (KnownXmlType.BASE64_BINARY.getQname().equals(getBaseType().getQname()));
}
/**
* The suggested mime type of the binary data, or null if none.
*
* @return The suggested mime type of the binary data, or null if none.
*/
public String getMimeType() {
XmlMimeType mimeType = getAnnotation(XmlMimeType.class);
if (mimeType != null) {
return mimeType.value();
}
return null;
}
/**
* Whether the accessor type is a collection type.
*
* @return Whether the accessor type is a collection type.
*/
public boolean isCollectionType() {
if (isXmlList()) {
return false;
}
DecoratedTypeMirror accessorType = getAccessorType();
if (isAdapted()) {
accessorType = (DecoratedTypeMirror) getAdapterType().getAdaptingType(accessorType, this.context.getContext());
}
if (accessorType.isArray()) {
//special case for byte[]
return ((ArrayType) accessorType).getComponentType().getKind() != TypeKind.BYTE;
}
return accessorType.isCollection();
}
/**
* If this is a collection type, return the type parameter of the collection, or null if this isn't a
* parameterized collection type.
*
* @return the type parameter of the collection.
*/
public DecoratedTypeMirror getCollectionItemType() {
if (isAdapted()) {
DecoratedTypeMirror adaptingType = (DecoratedTypeMirror) getAdapterType().getAdaptingType();
if (adaptingType.isCollection()) {
return TypeMirrorUtils.getComponentType(adaptingType, this.context.getContext().getProcessingEnvironment());
}
else {
return adaptingType;
}
}
else {
return TypeMirrorUtils.getComponentType(getAccessorType(), this.context.getContext().getProcessingEnvironment());
}
}
/**
* Returns the accessor for the XML id, or null if none was found or if this isn't an Xml IDREF accessor.
*
* @return The accessor, or null.
*/
public DecoratedElement getAccessorForXmlID() {
if (isXmlIDREF()) {
DecoratedTypeMirror accessorType = getBareAccessorType();
if (accessorType.isDeclared()) {
return getXmlIDAccessor((DecoratedDeclaredType) accessorType);
}
}
return null;
}
/**
* Gets the xml id accessor for the specified class type (recursively through superclasses).
*
* @param classType The class type.
* @return The xml id accessor.
*/
private DecoratedElement getXmlIDAccessor(DecoratedDeclaredType classType) {
if (classType == null) {
return null;
}
DecoratedTypeElement declaration = (DecoratedTypeElement) classType.asElement();
if ((declaration == null) || (Object.class.getName().equals(declaration.getQualifiedName().toString()))) {
return null;
}
for (VariableElement field : ElementFilter.fieldsIn(declaration.getEnclosedElements())) {
if (field.getAnnotation(XmlID.class) != null) {
return (DecoratedElement) field;
}
}
for (PropertyElement property : declaration.getProperties()) {
if (property.getAnnotation(XmlID.class) != null) {
return property;
}
}
TypeMirror superclass = declaration.getSuperclass();
if (superclass == null || superclass.getKind() == TypeKind.NONE) {
return null;
}
return getXmlIDAccessor((DecoratedDeclaredType) superclass);
}
/**
* @return The list of class names that this type definition wants you to "see also".
*/
public Collection<DecoratedTypeMirror> getSeeAlsos() {
Collection<DecoratedTypeMirror> seeAlsos = null;
XmlSeeAlso seeAlsoInfo = getAnnotation(XmlSeeAlso.class);
if (seeAlsoInfo != null) {
seeAlsos = new ArrayList<DecoratedTypeMirror>();
try {
for (Class clazz : seeAlsoInfo.value()) {
seeAlsos.add(TypeMirrorUtils.mirrorOf(clazz, this.env));
}
}
catch (MirroredTypesException e) {
seeAlsos.addAll((Collection<? extends DecoratedTypeMirror>) TypeMirrorDecorator.decorate(e.getTypeMirrors(), this.env));
}
}
return seeAlsos;
}
// Inherited.
public boolean isAdapted() {
return this.adapterType != null;
}
// Inherited.
public AdapterType getAdapterType() {
return this.adapterType;
}
/**
* Whether this accessor is an attribute.
*
* @return Whether this accessor is an attribute.
*/
public boolean isAttribute() {
return false;
}
/**
* Whether this accessor is a value.
*
* @return Whether this accessor is a value.
*/
public boolean isValue() {
return false;
}
/**
* Whether this accessor is an element ref.
*
* @return Whether this accessor is an element ref.
*/
public boolean isElementRef() {
return false;
}
/**
* Whether this QName accessor references a QName enum type.
*
* @return Whether this QName accessor references a QName enum type.
*/
public boolean isReferencesQNameEnum() {
return getAnnotation(XmlQNameEnumRef.class) != null;
}
/**
* The enum type containing the known qnames for this qname enum accessor, or null is this accessor doesn't reference a known qname type.
*
* @return The enum type containing the known qnames for this qname enum accessor.
*/
public DecoratedTypeMirror getQNameEnumRef() {
final XmlQNameEnumRef enumRef = getAnnotation(XmlQNameEnumRef.class);
DecoratedTypeMirror qnameEnumType = null;
if (enumRef != null) {
qnameEnumType = Annotations.mirrorOf(new Callable<Class<?>>() {
@Override
public Class<?> call() throws Exception {
return enumRef.value();
}
}, this.env);
}
return qnameEnumType;
}
/**
* Set of (human-readable) locations that this type definition is referenced from.
*
* @return The referenced-from list.
*/
public LinkedList<Element> getReferencedFrom() {
LinkedList<Element> stack = new LinkedList<Element>(this.typeDefinition.getReferencedFrom());
stack.add(this);
return stack;
}
/**
* The facets here applicable.
*
* @return The facets here applicable.
*/
public Set<Facet> getFacets() {
return facets;
}
public boolean overrides(Accessor accessor) {
return accessor != null && accessor != this && accessor.getAnnotation(XmlTransient.class) == null && getSimpleName().toString().equals(accessor.getSimpleName().toString());
}
}