/**
* 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.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.DecoratedExecutableElement;
import com.webcohesion.enunciate.javac.decorations.element.DecoratedTypeElement;
import com.webcohesion.enunciate.javac.decorations.element.PropertyElement;
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.types.XmlClassType;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.xml.bind.annotation.*;
import javax.xml.namespace.QName;
import java.beans.Introspector;
import java.util.*;
import java.util.concurrent.Callable;
/**
* A xml type definition.
*
* @author Ryan Heaton
*/
public abstract class TypeDefinition extends DecoratedTypeElement implements HasFacets {
private final javax.xml.bind.annotation.XmlType xmlType;
private final Schema schema;
private final SortedSet<Element> elements;
private final Collection<Attribute> attributes;
private final Value xmlValue;
private final Accessor xmlID;
private final boolean hasAnyAttribute;
private final TypeMirror anyAttributeQNameEnumRef;
private final AnyElement anyElement;
private final LinkedList<javax.lang.model.element.Element> referencedFrom = new LinkedList<javax.lang.model.element.Element>();
private final Set<Facet> facets = new TreeSet<Facet>();
protected final EnunciateJaxbContext context;
protected TypeDefinition(TypeElement delegate, EnunciateJaxbContext context) {
super(delegate, context.getContext().getProcessingEnvironment());
this.xmlType = getAnnotation(javax.xml.bind.annotation.XmlType.class);
this.schema = new Schema(context.getContext().getProcessingEnvironment().getElementUtils().getPackageOf(delegate), env);
ElementComparator comparator = new ElementComparator(getPropertyOrder(), getAccessorOrder(), env);
SortedSet<Element> elementAccessors = new TreeSet<Element>(comparator);
AccessorFilter filter = new AccessorFilter(getAccessType());
Collection<Attribute> attributeAccessors = new ArrayList<Attribute>();
Value value = null;
Accessor xmlID = null;
AnyElement anyElement = null;
boolean hasAnyAttribute = false;
TypeMirror anyAttributeQNameEnumRef = null;
for (javax.lang.model.element.Element accessor : loadPotentialAccessors(filter)) {
Accessor added;
if (isAttribute(accessor)) {
Attribute attribute = new Attribute(accessor, this, context);
attributeAccessors.add(attribute);
added = attribute;
}
else if (isValue(accessor)) {
if (value != null) {
throw new EnunciateException("Accessor " + accessor.getSimpleName() + " of " + getQualifiedName() + ": a type definition cannot have more than one xml value.");
}
value = new Value(accessor, this, context);
added = value;
}
else if (isElementRef(accessor)) {
ElementRef elementRef = new ElementRef(accessor, this, context);
if (!elementAccessors.add(elementRef)) {
//see http://jira.codehaus.org/browse/ENUNCIATE-381; the case for this is when an annotated field has an associated public property
//we'll just silently continue
continue;
}
added = elementRef;
}
else if (isAnyAttribute(accessor)) {
hasAnyAttribute = true;
final XmlQNameEnumRef enumRef = accessor.getAnnotation(XmlQNameEnumRef.class);
if (enumRef != null) {
anyAttributeQNameEnumRef = Annotations.mirrorOf(new Callable<Class<?>>() {
@Override
public Class<?> call() throws Exception {
return enumRef.value();
}
}, this.env);
}
continue;
}
else if (isAnyElement(accessor)) {
anyElement = new AnyElement(accessor, this, context);
continue;
}
else if (isUnsupported(accessor)) {
throw new EnunciateException("Accessor " + accessor.getSimpleName() + " of " + getQualifiedName() + ": sorry, we currently don't support mixed or wildard elements. Maybe someday...");
}
else {
//its an element accessor.
if (accessor instanceof PropertyElement) {
//if the accessor is a property and either the getter or setter overrides ANY method of ANY superclass, exclude it.
if (overridesAnother(((PropertyElement) accessor).getGetter()) || overridesAnother(((PropertyElement) accessor).getSetter())) {
continue;
}
}
Element element = new Element(accessor, this, context);
if (!elementAccessors.add(element)) {
//see http://jira.codehaus.org/browse/ENUNCIATE-381; the case for this is when an annotated field has an associated public property
//we'll just silently continue
continue;
}
added = element;
}
if (added.getAnnotation(XmlID.class) != null) {
if (xmlID != null) {
throw new EnunciateException("Accessor " + added.getSimpleName() + " of " + getQualifiedName() + ": more than one XML id specified.");
}
xmlID = added;
}
}
this.elements = Collections.unmodifiableSortedSet(elementAccessors);
this.attributes = Collections.unmodifiableCollection(attributeAccessors);
this.xmlValue = value;
this.xmlID = xmlID;
this.hasAnyAttribute = hasAnyAttribute;
this.anyAttributeQNameEnumRef = anyAttributeQNameEnumRef;
this.anyElement = anyElement;
this.facets.addAll(Facet.gatherFacets(delegate, context.getContext()));
this.facets.addAll(this.schema.getFacets());
this.context = context;
}
protected TypeDefinition(TypeDefinition copy) {
super(copy.delegate, copy.env);
this.xmlType = copy.xmlType;
this.schema = copy.schema;
this.elements = copy.elements;
this.attributes = copy.attributes;
this.xmlValue = copy.xmlValue;
this.xmlID = copy.xmlID;
this.hasAnyAttribute = copy.hasAnyAttribute;
this.anyAttributeQNameEnumRef = copy.anyAttributeQNameEnumRef;
this.anyElement = copy.anyElement;
this.facets.addAll(copy.facets);
this.context = copy.context;
}
/**
* Load the potential accessors for this type definition.
*
* @param filter The filter.
* @return the potential accessors for this type definition.
*/
protected List<javax.lang.model.element.Element> loadPotentialAccessors(AccessorFilter filter) {
List<VariableElement> potentialFields = new ArrayList<VariableElement>();
List<PropertyElement> potentialProperties = new ArrayList<PropertyElement>();
aggregatePotentialAccessors(potentialFields, potentialProperties, this, filter, false);
List<javax.lang.model.element.Element> accessors = new ArrayList<javax.lang.model.element.Element>();
accessors.addAll(potentialFields);
accessors.addAll(potentialProperties);
return accessors;
}
/**
* Aggregate the potential accessor into their separate buckets for the given class declaration, recursively including transient superclasses.
*
* @param fields The fields.
* @param properties The properties.
* @param clazz The class.
* @param filter The filter.
*/
protected void aggregatePotentialAccessors(List<VariableElement> fields, List<PropertyElement> properties, DecoratedTypeElement clazz, AccessorFilter filter, boolean inlineAccessorsOfSuperclasses) {
DecoratedTypeElement superDeclaration = clazz.getSuperclass() != null ? (DecoratedTypeElement) this.env.getTypeUtils().asElement(clazz.getSuperclass()) : null;
if (superDeclaration != null && (isXmlTransient(superDeclaration) || inlineAccessorsOfSuperclasses)) {
aggregatePotentialAccessors(fields, properties, superDeclaration, filter, true);
}
for (VariableElement fieldDeclaration : ElementFilter.fieldsIn(clazz.getEnclosedElements())) {
if (!filter.accept((DecoratedElement) fieldDeclaration)) {
remove(fieldDeclaration, fields);
}
else {
addOrReplace(fieldDeclaration, fields);
}
}
for (PropertyElement propertyDeclaration : clazz.getProperties()) {
if (!filter.accept(propertyDeclaration)) {
remove(propertyDeclaration, properties);
}
else {
addOrReplace(propertyDeclaration, properties);
}
}
}
/**
* Whether the given method declaration overrides any method.
*
* @param method The method declaration.
* @return Whether the given method declaration overrides any method.
*/
protected boolean overridesAnother(DecoratedExecutableElement method) {
if (method == null) {
return false;
}
final TypeElement declaringType = (TypeElement) method.getEnclosingElement();
TypeElement superType = (TypeElement) this.env.getTypeUtils().asElement(declaringType.getSuperclass());
if (superType != null && superType.getAnnotation(XmlTransient.class) == null) { //ignore transient supertypes.
while (superType != null && !Object.class.getName().equals(superType.getQualifiedName().toString())) {
List<ExecutableElement> methods = ElementFilter.methodsIn(superType.getEnclosedElements());
for (ExecutableElement candidate : methods) {
if (this.env.getElementUtils().overrides(method, candidate, declaringType)) {
return true;
}
}
superType = (TypeElement) this.env.getTypeUtils().asElement(superType.getSuperclass());
}
}
return false;
}
/**
* Add the specified member declaration, or if it is already in the list (by name), replace it.
*
* @param memberDeclaration The member to add/replace.
* @param memberDeclarations The other members.
*/
protected <M extends javax.lang.model.element.Element> void addOrReplace(M memberDeclaration, List<M> memberDeclarations) {
remove(memberDeclaration, memberDeclarations);
memberDeclarations.add(memberDeclaration);
}
/**
* Remove specified member declaration from the specified list, if it exists..
*
* @param memberDeclaration The member to remove.
* @param memberDeclarations The other members.
*/
protected <M extends javax.lang.model.element.Element> void remove(M memberDeclaration, List<M> memberDeclarations) {
Iterator<M> it = memberDeclarations.iterator();
while (it.hasNext()) {
javax.lang.model.element.Element candidate = it.next();
if (candidate.getSimpleName().equals(memberDeclaration.getSimpleName())) {
it.remove();
}
}
}
/**
* Whether a declaration is an xml attribute.
*
* @param declaration The declaration to check.
* @return Whether a declaration is an attribute.
*/
protected boolean isAttribute(javax.lang.model.element.Element declaration) {
//todo: the attribute wildcard?
return (declaration.getAnnotation(XmlAttribute.class) != null);
}
/**
* Whether a declaration is an xml value.
*
* @param declaration The declaration to check.
* @return Whether a declaration is an value.
*/
protected boolean isValue(javax.lang.model.element.Element declaration) {
return (declaration.getAnnotation(XmlValue.class) != null);
}
/**
* Whether a declaration is an xml element ref.
*
* @param declaration The declaration to check.
* @return Whether a declaration is an xml element ref.
*/
protected boolean isElementRef(javax.lang.model.element.Element declaration) {
return ((declaration.getAnnotation(XmlElementRef.class) != null) || (declaration.getAnnotation(XmlElementRefs.class) != null));
}
/**
* Whether the member declaration is XmlAnyAttribute.
*
* @param declaration The declaration.
* @return Whether the member declaration is XmlAnyAttribute.
*/
protected boolean isAnyAttribute(javax.lang.model.element.Element declaration) {
return declaration.getAnnotation(XmlAnyAttribute.class) != null;
}
/**
* Whether the member declaration is XmlAnyElement.
*
* @param declaration The declaration.
* @return Whether the member declaration is XmlAnyElement.
*/
protected boolean isAnyElement(javax.lang.model.element.Element declaration) {
return declaration.getAnnotation(XmlAnyElement.class) != null;
}
/**
* Whether a declaration is an xml-mixed property.
*
* @param declaration The declaration to check.
* @return Whether a declaration is an mixed.
*/
protected boolean isUnsupported(javax.lang.model.element.Element declaration) {
//todo: support xml-mixed?
return (declaration.getAnnotation(XmlMixed.class) != null);
}
/**
* The name of the xml type element.
*
* @return The name of the xml type element.
*/
public String getName() {
String name = Introspector.decapitalize(getSimpleName().toString());
if ((xmlType != null) && (!"##default".equals(xmlType.name()))) {
name = xmlType.name();
if ("".equals(name)) {
name = null;
}
}
return name;
}
/**
* The namespace of the xml type element.
*
* @return The namespace of the xml type element.
*/
public String getNamespace() {
String namespace = getPackage().getNamespace();
if ((xmlType != null) && (!"##default".equals(xmlType.namespace()))) {
namespace = xmlType.namespace();
}
return namespace;
}
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 = getSimpleName().toString();
ClientName clientName = getAnnotation(ClientName.class);
if (clientName != null) {
clientSimpleName = clientName.value();
}
return clientSimpleName;
}
/**
* The qname of this type definition.
*
* @return The qname of this type definition.
*/
public QName getQname() {
String localPart = getName();
if (localPart == null) {
localPart = "";
}
return new QName(getNamespace(), localPart);
}
/**
* The default access type for the beans in this class.
*
* @return The default access type for the beans in this class.
*/
public XmlAccessType getAccessType() {
XmlAccessType accessType = getPackage().getAccessType();
XmlAccessorType xmlAccessorType = getAnnotation(XmlAccessorType.class);
if (xmlAccessorType != null) {
accessType = xmlAccessorType.value();
}
else {
XmlAccessType inheritedAccessType = getInheritedAccessType(this);
if (inheritedAccessType != null) {
accessType = inheritedAccessType;
}
}
return accessType;
}
/**
* Get the inherited accessor type of the given class, or null if none is found.
*
* @param declaration The inherited accessor type.
* @return The inherited accessor type of the given class, or null if none is found.
*/
protected XmlAccessType getInheritedAccessType(TypeElement declaration) {
TypeMirror superclass = declaration.getSuperclass();
if (superclass != null && superclass.getKind() != TypeKind.NONE) {
TypeElement superDeclaration = (TypeElement) this.env.getTypeUtils().asElement(superclass);
if ((superDeclaration != null) && (!Object.class.getName().equals(superDeclaration.getQualifiedName().toString()))) {
XmlAccessorType xmlAccessorType = superDeclaration.getAnnotation(XmlAccessorType.class);
if (xmlAccessorType != null) {
return xmlAccessorType.value();
}
else {
return getInheritedAccessType(superDeclaration);
}
}
}
return null;
}
/**
* The property order of this xml type.
*
* @return The property order of this xml type.
*/
public String[] getPropertyOrder() {
String[] propertyOrder = null;
if (xmlType != null) {
String[] propOrder = xmlType.propOrder();
if ((propOrder != null) && (propOrder.length > 0) && ((propOrder.length > 1) || !("".equals(propOrder[0])))) {
propertyOrder = propOrder;
}
}
return propertyOrder;
}
/**
* The default accessor order of the beans in this package.
*
* @return The default accessor order of the beans in this package.
*/
public XmlAccessOrder getAccessorOrder() {
XmlAccessOrder order = getPackage().getAccessorOrder();
XmlAccessorOrder xmlAccessorOrder = getAnnotation(XmlAccessorOrder.class);
if (xmlAccessorOrder != null) {
order = xmlAccessorOrder.value();
}
return order;
}
/**
* @return The list of class names that this type definition wants you to "see also".
*/
public Collection<TypeMirror> getSeeAlsos() {
Collection<TypeMirror> seeAlsos = null;
XmlSeeAlso seeAlsoInfo = getAnnotation(XmlSeeAlso.class);
if (seeAlsoInfo != null) {
seeAlsos = new ArrayList<TypeMirror>();
try {
for (Class clazz : seeAlsoInfo.value()) {
TypeElement typeDeclaration = this.env.getElementUtils().getTypeElement(clazz.getName());
seeAlsos.add(typeDeclaration.asType());
}
}
catch (MirroredTypesException e) {
seeAlsos.addAll(TypeMirrorDecorator.decorate(e.getTypeMirrors(), this.env));
}
}
return seeAlsos;
}
/**
* Whether this type definition has an "anyAttribute" definition.
*
* @return Whether this type definition has an "anyAttribute" definition.
*/
public boolean isHasAnyAttribute() {
return hasAnyAttribute;
}
/**
* The enum type containing the known qnames for attributes of the 'any' attribute definition. <code>null</code> if none.
*
* @return The enum type containing the known qnames for attributes of the 'any' attribute definition. <code>null</code> if none.
*/
public TypeMirror getAnyAttributeQNameEnumRef() {
return anyAttributeQNameEnumRef;
}
/**
* The "anyElement" element.
*
* @return The "anyElement" element.
*/
public AnyElement getAnyElement() {
return anyElement;
}
/**
* The elements of this type definition.
*
* @return The elements of this type definition.
*/
public SortedSet<Element> getElements() {
return elements;
}
/**
* The attributes of this type definition.
*
* @return The attributes of this type definition.
*/
public Collection<Attribute> getAttributes() {
return attributes;
}
/**
* The value of this type definition.
*
* @return The value of this type definition.
*/
public Value getValue() {
return xmlValue;
}
public List<Accessor> getAllAccessors() {
ArrayList<Accessor> accessors = new ArrayList<Accessor>();
accessors.addAll(getAllAttributes());
accessors.addAll(getAllValues());
accessors.addAll(getAllElements());
return accessors;
}
public List<Attribute> getAllAttributes() {
ArrayList<Attribute> attributes = new ArrayList<Attribute>();
com.webcohesion.enunciate.modules.jaxb.model.types.XmlType baseType = getBaseType();
if (baseType instanceof XmlClassType) {
attributes.addAll(((XmlClassType) baseType).getTypeDefinition().getAllAttributes());
}
MY_ATTRIBUTES : for (Attribute attribute : getAttributes()) {
for (Attribute other : attributes) {
if (attribute.overrides(other)) {
continue MY_ATTRIBUTES;
}
}
attributes.add(attribute);
}
return attributes;
}
public List<Value> getAllValues() {
ArrayList<Value> values = new ArrayList<Value>();
com.webcohesion.enunciate.modules.jaxb.model.types.XmlType baseType = getBaseType();
if (baseType instanceof XmlClassType) {
values.addAll(((XmlClassType) baseType).getTypeDefinition().getAllValues());
}
Value value = getValue();
if (value != null && values.isEmpty()) {
values.add(value);
}
return values;
}
public List<Element> getAllElements() {
ArrayList<Element> elements = new ArrayList<Element>();
com.webcohesion.enunciate.modules.jaxb.model.types.XmlType baseType = getBaseType();
if (baseType instanceof XmlClassType) {
elements.addAll(((XmlClassType) baseType).getTypeDefinition().getAllElements());
}
MY_ELEMENTS : for (Element element : getElements()) {
for (Element other : elements) {
if (element.overrides(other)) {
continue MY_ELEMENTS;
}
}
elements.add(element);
}
return elements;
}
/**
* The accessor that is the xml id of this type definition, or null if none.
*
* @return The accessor that is the xml id of this type definition, or null if none.
*/
public Accessor getXmlID() {
return xmlID;
}
/**
* Whether a declaration is xml transient.
*
* @param declaration The declaration on which to determine xml transience.
* @return Whether a declaration is xml transient.
*/
protected boolean isXmlTransient(javax.lang.model.element.Element declaration) {
return (declaration.getAnnotation(XmlTransient.class) != null);
}
/**
* Whether this xml type is anonymous.
*
* @return Whether this xml type is anonymous.
*/
public boolean isAnonymous() {
return getName() == null;
}
/**
* The schema for this complex type.
*
* @return The schema for this complex type.
*/
public Schema getSchema() {
return schema;
}
// Inherited.
@Override
public Schema getPackage() {
return getSchema();
}
/**
* Whether this is a complex type.
*
* @return Whether this is a complex type.
*/
public boolean isComplex() {
return false;
}
/**
* Whether this is a enum type.
*
* @return Whether this is a enum type.
*/
public boolean isEnum() {
return false;
}
/**
* Whether this is a simple type.
*
* @return Whether this is a simple type.
*/
public boolean isSimple() {
return false;
}
/**
* Whether this type definition is a base object (i.e. a root of the object hierarchy).
*
* @return Whether this type definition is a base object
*/
public boolean isBaseObject() {
return true;
}
/**
* Set of (human-readable) locations that this type definition is referenced from.
*
* @return The referenced-from list.
*/
public LinkedList<javax.lang.model.element.Element> getReferencedFrom() {
return referencedFrom;
}
/**
* The facets here applicable.
*
* @return The facets here applicable.
*/
public Set<Facet> getFacets() {
return facets;
}
/**
* The base type of this type definition.
*
* @return The base type of this type definition.
*/
public abstract com.webcohesion.enunciate.modules.jaxb.model.types.XmlType getBaseType();
}