/**
* 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;
import com.webcohesion.enunciate.EnunciateContext;
import com.webcohesion.enunciate.EnunciateException;
import com.webcohesion.enunciate.javac.decorations.element.DecoratedTypeElement;
import com.webcohesion.enunciate.javac.decorations.type.DecoratedDeclaredType;
import com.webcohesion.enunciate.javac.decorations.type.DecoratedTypeMirror;
import com.webcohesion.enunciate.metadata.qname.XmlQNameEnum;
import com.webcohesion.enunciate.module.EnunciateModuleContext;
import com.webcohesion.enunciate.modules.jaxb.model.*;
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.util.JAXBUtil;
import com.webcohesion.enunciate.modules.jaxb.model.util.MapType;
import com.webcohesion.enunciate.util.OneTimeLogMessage;
import javax.activation.DataHandler;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.*;
import javax.lang.model.util.Elements;
import javax.lang.model.util.SimpleTypeVisitor6;
import javax.lang.model.util.Types;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.annotation.*;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.namespace.QName;
import java.sql.Timestamp;
import java.util.*;
/**
* @author Ryan Heaton
*/
@SuppressWarnings ( "unchecked" )
public class EnunciateJaxbContext extends EnunciateModuleContext {
private int prefixIndex = 0;
private final boolean disableExamples;
private final Map<String, XmlType> knownTypes;
private final Map<String, TypeDefinition> typeDefinitions;
private final Map<QName, TypeDefinition> typeDefinitionsByQName;
private final Map<String, ElementDeclaration> elementDeclarations;
private final Map<String, String> namespacePrefixes;
private final Map<String, SchemaInfo> schemas;
private final Map<String, Map<String, XmlSchemaType>> packageSpecifiedTypes;
public EnunciateJaxbContext(EnunciateContext context, boolean disableExamples) {
super(context);
this.disableExamples = disableExamples;
this.knownTypes = loadKnownTypes();
this.typeDefinitions = new HashMap<String, TypeDefinition>();
this.typeDefinitionsByQName = new HashMap<QName, TypeDefinition>();
this.elementDeclarations = new HashMap<String, ElementDeclaration>();
this.namespacePrefixes = loadKnownPrefixes(context);
this.schemas = new HashMap<String, SchemaInfo>();
this.packageSpecifiedTypes = new HashMap<String, Map<String, XmlSchemaType>>();
}
protected Map<String, String> loadKnownPrefixes(EnunciateContext context) {
Map<String, String> namespacePrefixes = loadDefaultPrefixes();
namespacePrefixes.putAll(context.getConfiguration().getNamespaces());
return namespacePrefixes;
}
/**
* Loads a map of known namespaces as keys to their associated prefixes.
*
* @return A map of known namespaces.
*/
protected Map<String, String> loadDefaultPrefixes() {
HashMap<String, String> knownNamespaces = new HashMap<String, String>();
knownNamespaces.put("http://schemas.xmlsoap.org/wsdl/", "wsdl");
knownNamespaces.put("http://schemas.xmlsoap.org/wsdl/http/", "http");
knownNamespaces.put("http://schemas.xmlsoap.org/wsdl/mime/", "mime");
knownNamespaces.put("http://schemas.xmlsoap.org/wsdl/soap/", "soap");
knownNamespaces.put("http://schemas.xmlsoap.org/wsdl/soap12/", "soap12");
knownNamespaces.put("http://schemas.xmlsoap.org/soap/encoding/", "soapenc");
knownNamespaces.put("http://www.w3.org/2001/XMLSchema", "xs");
knownNamespaces.put("http://www.w3.org/2001/XMLSchema-instance", "xsi");
knownNamespaces.put("http://ws-i.org/profiles/basic/1.1/xsd", "wsi");
knownNamespaces.put("http://wadl.dev.java.net/2009/02", "wadl");
knownNamespaces.put("http://www.w3.org/XML/1998/namespace", "xml");
return knownNamespaces;
}
public boolean isDisableExamples() {
return disableExamples;
}
public EnunciateContext getContext() {
return context;
}
public XmlType getKnownType(Element declaration) {
if (declaration instanceof TypeElement) {
return this.knownTypes.get(((TypeElement) declaration).getQualifiedName().toString());
}
return null;
}
public TypeDefinition findTypeDefinition(Element declaration) {
if (declaration instanceof TypeElement) {
return this.typeDefinitions.get(((TypeElement) declaration).getQualifiedName().toString());
}
return null;
}
public ElementDeclaration findElementDeclaration(Element declaredElement) {
if (declaredElement instanceof TypeElement) {
return this.elementDeclarations.get(((TypeElement) declaredElement).getQualifiedName().toString());
}
else if (declaredElement instanceof ExecutableElement) {
return this.elementDeclarations.get(declaredElement.toString());
}
return null;
}
public Map<String, XmlSchemaType> getPackageSpecifiedTypes(String packageName) {
return this.packageSpecifiedTypes.get(packageName);
}
public void setPackageSpecifiedTypes(String packageName, Map<String, XmlSchemaType> explicitTypes) {
this.packageSpecifiedTypes.put(packageName, explicitTypes);
}
public Map<String, String> getNamespacePrefixes() {
return namespacePrefixes;
}
public void addNamespacePrefix(String namespace, String prefix) {
this.namespacePrefixes.put(namespace, prefix);
}
public Map<String, SchemaInfo> getSchemas() {
return schemas;
}
protected Map<String, XmlType> loadKnownTypes() {
HashMap<String, XmlType> knownTypes = new HashMap<String, XmlType>();
knownTypes.put(Boolean.class.getName(), KnownXmlType.BOOLEAN);
knownTypes.put(Byte.class.getName(), KnownXmlType.BYTE);
knownTypes.put(Character.class.getName(), KnownXmlType.UNSIGNED_SHORT);
knownTypes.put(Double.class.getName(), KnownXmlType.DOUBLE);
knownTypes.put(Float.class.getName(), KnownXmlType.FLOAT);
knownTypes.put(Integer.class.getName(), KnownXmlType.INT);
knownTypes.put(Long.class.getName(), KnownXmlType.LONG);
knownTypes.put(Short.class.getName(), KnownXmlType.SHORT);
knownTypes.put(Boolean.TYPE.getName(), KnownXmlType.BOOLEAN);
knownTypes.put(Byte.TYPE.getName(), KnownXmlType.BYTE);
knownTypes.put(Double.TYPE.getName(), KnownXmlType.DOUBLE);
knownTypes.put(Float.TYPE.getName(), KnownXmlType.FLOAT);
knownTypes.put(Integer.TYPE.getName(), KnownXmlType.INT);
knownTypes.put(Long.TYPE.getName(), KnownXmlType.LONG);
knownTypes.put(Short.TYPE.getName(), KnownXmlType.SHORT);
knownTypes.put(Character.TYPE.getName(), KnownXmlType.UNSIGNED_SHORT);
knownTypes.put(String.class.getName(), KnownXmlType.STRING);
knownTypes.put(java.math.BigInteger.class.getName(), KnownXmlType.INTEGER);
knownTypes.put(java.math.BigDecimal.class.getName(), KnownXmlType.DECIMAL);
knownTypes.put(java.util.Calendar.class.getName(), KnownXmlType.DATE_TIME);
knownTypes.put(java.util.Date.class.getName(), KnownXmlType.DATE_TIME);
knownTypes.put(Timestamp.class.getName(), KnownXmlType.DATE_TIME);
knownTypes.put(javax.xml.namespace.QName.class.getName(), KnownXmlType.QNAME);
knownTypes.put(java.net.URI.class.getName(), KnownXmlType.STRING);
knownTypes.put(javax.xml.datatype.Duration.class.getName(), KnownXmlType.DURATION);
knownTypes.put(java.lang.Object.class.getName(), KnownXmlType.ANY_TYPE);
knownTypes.put(byte[].class.getName(), KnownXmlType.BASE64_BINARY);
knownTypes.put(java.nio.ByteBuffer.class.getName(), KnownXmlType.BASE64_BINARY);
knownTypes.put(java.awt.Image.class.getName(), KnownXmlType.BASE64_BINARY);
knownTypes.put(DataHandler.class.getName(), KnownXmlType.BASE64_BINARY);
knownTypes.put(javax.xml.transform.Source.class.getName(), KnownXmlType.BASE64_BINARY);
knownTypes.put(java.util.UUID.class.getName(), KnownXmlType.STRING);
knownTypes.put(XMLGregorianCalendar.class.getName(), KnownXmlType.DATE_TIME); //JAXB spec says it maps to anySimpleType, but we can just assume dateTime...
knownTypes.put(GregorianCalendar.class.getName(), KnownXmlType.DATE_TIME);
return knownTypes;
}
/**
* Find the type definition for a class given the class's declaration.
*
* @param declaration The declaration.
* @return The type definition.
*/
protected TypeDefinition createTypeDefinition(TypeElement declaration) {
if (declaration.getKind() == ElementKind.INTERFACE) {
if (declaration.getAnnotation(javax.xml.bind.annotation.XmlType.class) != null) {
throw new EnunciateException(declaration.getQualifiedName() + ": an interface must not be annotated with @XmlType.");
}
}
declaration = narrowToAdaptingType(declaration);
if (isEnumType(declaration)) {
if (declaration.getAnnotation(XmlQNameEnum.class) != null) {
return new QNameEnumTypeDefinition(declaration, this);
}
else {
return new EnumTypeDefinition(declaration, this);
}
}
else {
ComplexTypeDefinition typeDef = new ComplexTypeDefinition(declaration, this);
if ((typeDef.getValue() != null) && (hasNeitherAttributesNorElements(typeDef))) {
return new SimpleTypeDefinition(typeDef);
}
else {
return typeDef;
}
}
}
/**
* Narrows the existing declaration down to its adapting declaration, if it's being adapted. Otherwise, the original declaration will be returned.
*
* @param declaration The declaration to narrow.
* @return The narrowed declaration.
*/
protected TypeElement narrowToAdaptingType(TypeElement declaration) {
AdapterType adapterType = JAXBUtil.findAdapterType(declaration, this);
if (adapterType != null) {
TypeMirror adaptingType = adapterType.getAdaptingType();
if (adaptingType.getKind() != TypeKind.DECLARED) {
return declaration;
}
else {
TypeElement adaptingDeclaration = (TypeElement) ((DeclaredType) adaptingType).asElement();
if (adaptingDeclaration == null) {
throw new EnunciateException(String.format("Class %s is being adapted by a type (%s) that doesn't seem to be on the classpath.", declaration.getQualifiedName(), adaptingType));
}
return adaptingDeclaration;
}
}
return declaration;
}
/**
* A quick check to see if a declaration defines a enum schema type.
*
* @param declaration The declaration to check.
* @return the value of the check.
*/
protected boolean isEnumType(TypeElement declaration) {
return declaration.getKind() == ElementKind.ENUM;
}
/**
* Whether the specified type definition has neither attributes nor elements.
*
* @param typeDef The type def.
* @return Whether the specified type definition has neither attributes nor elements.
*/
protected boolean hasNeitherAttributesNorElements(TypeDefinition typeDef) {
boolean none = (typeDef.getAttributes().isEmpty()) && (typeDef.getElements().isEmpty());
TypeMirror superclass = typeDef.getSuperclass();
if (superclass instanceof DeclaredType) {
TypeElement superDeclaration = (TypeElement) ((DeclaredType) superclass).asElement();
if (!Object.class.getName().equals(superDeclaration.getQualifiedName().toString())) {
none &= hasNeitherAttributesNorElements(new ComplexTypeDefinition(superDeclaration, this));
}
}
return none;
}
/**
* Add a namespace.
*
* @param namespace The namespace to add.
* @return The prefix for the namespace.
*/
public String addNamespace(String namespace) {
String prefix = this.namespacePrefixes.get(namespace);
if (prefix == null) {
prefix = generatePrefix(namespace);
this.namespacePrefixes.put(namespace, prefix);
}
return prefix;
}
/**
* Generate a prefix for the given namespace.
*
* @param namespace The namespace for which to generate a prefix.
* @return The prefix that was generated.
*/
protected String generatePrefix(String namespace) {
String prefix = "ns" + (prefixIndex++);
while (this.namespacePrefixes.values().contains(prefix)) {
prefix = "ns" + (prefixIndex++);
}
return prefix;
}
/**
* Adds a schema declaration to the model.
*
* @param schema The schema declaration to add to the model.
*/
public void add(Schema schema) {
add(schema, new LinkedList<Element>());
}
/**
* Add a root element to the model.
*
* @param rootElement The root element to add.
* @param stack The context stack.
*/
public void add(RootElementDeclaration rootElement, LinkedList<Element> stack) {
if (findElementDeclaration(rootElement) == null) {
this.elementDeclarations.put(rootElement.getQualifiedName().toString(), rootElement);
debug("Added %s as a root XML element.", rootElement.getQualifiedName());
add(rootElement.getSchema());
String namespace = rootElement.getNamespace();
String prefix = addNamespace(namespace);
SchemaInfo schemaInfo = schemas.get(namespace);
if (schemaInfo == null) {
schemaInfo = new SchemaInfo(this);
schemaInfo.setId(prefix);
schemaInfo.setNamespace(namespace);
schemas.put(namespace, schemaInfo);
}
schemaInfo.getRootElements().add(rootElement);
addReferencedTypeDefinitions(rootElement, stack);
}
}
/**
* Add an XML registry.
*
* @param registry The registry to add.
*/
public void add(Registry registry) {
add(registry, new LinkedList<Element>());
}
protected void add(Registry registry, LinkedList<Element> stack) {
add(registry.getSchema());
String namespace = registry.getSchema().getNamespace();
String prefix = addNamespace(namespace);
SchemaInfo schemaInfo = schemas.get(namespace);
if (schemaInfo == null) {
schemaInfo = new SchemaInfo(this);
schemaInfo.setId(prefix);
schemaInfo.setNamespace(namespace);
schemas.put(namespace, schemaInfo);
}
schemaInfo.getRegistries().add(registry);
if (this.context.isExcluded(registry)) {
warn("Added %s as an XML registry even though is was supposed to be excluded according to configuration. It was referenced from %s%s, so it had to be included to prevent broken references.", registry.getQualifiedName(), stack.size() > 0 ? stack.get(0) : "an unknown location", stack.size() > 1 ? " of " + stack.get(1) : "");
}
else {
debug("Added %s as an XML registry.", registry.getQualifiedName());
}
if (getContext().getProcessingEnvironment().findSourcePosition(registry) == null) {
OneTimeLogMessage.SOURCE_FILES_NOT_FOUND.log(getContext());
debug("Unable to find source file for %s.", registry.getQualifiedName());
}
stack.push(registry);
try {
addReferencedTypeDefinitions(registry, stack);
for (LocalElementDeclaration led : registry.getLocalElementDeclarations()) {
add(led, stack);
}
}
finally {
stack.pop();
}
}
/**
* Add the referenced type definitions for a registry..
*
* @param registry The registry.
*/
protected void addReferencedTypeDefinitions(Registry registry, LinkedList<Element> stack) {
addSeeAlsoTypeDefinitions(registry, stack);
for (ExecutableElement methodDeclaration : registry.getInstanceFactoryMethods()) {
stack.push(methodDeclaration);
try {
addReferencedTypeDefinitions(methodDeclaration.getReturnType(), stack);
}
finally {
stack.pop();
}
}
}
protected void add(LocalElementDeclaration led, LinkedList<Element> stack) {
String namespace = led.getNamespace();
String prefix = addNamespace(namespace);
SchemaInfo schemaInfo = schemas.get(namespace);
if (schemaInfo == null) {
schemaInfo = new SchemaInfo(this);
schemaInfo.setId(prefix);
schemaInfo.setNamespace(namespace);
schemas.put(namespace, schemaInfo);
}
TypeMirror elementType = led.getElementType();
if (elementType instanceof DeclaredType) {
Element element = ((DeclaredType) elementType).asElement();
if (element instanceof TypeElement) {
this.elementDeclarations.put(((TypeElement) element).getQualifiedName().toString(), led);
}
}
schemaInfo.getLocalElementDeclarations().add(led);
if (this.context.isExcluded(led)) {
warn("Added %s as a local element declaration even though is was supposed to be excluded according to configuration. It was referenced from %s%s, so it had to be included to prevent broken references.", led.getSimpleName(), stack.size() > 0 ? stack.get(0) : "an unknown location", stack.size() > 1 ? " of " + stack.get(1) : "");
}
else {
debug("Added %s as a local element declaration.", led.getSimpleName());
}
addReferencedTypeDefinitions(led, stack);
}
/**
* Adds the referenced type definitions for the specified local element declaration.
*
* @param led The local element declaration.
*/
protected void addReferencedTypeDefinitions(LocalElementDeclaration led, LinkedList<Element> stack) {
addSeeAlsoTypeDefinitions(led, stack);
DecoratedTypeElement scope = led.getElementScope();
if (scope != null && scope.getKind() == ElementKind.CLASS && !isKnownTypeDefinition(scope)) {
add(createTypeDefinition(scope), stack);
}
TypeElement typeElement = null;
TypeMirror elementType = led.getElementType();
if (elementType instanceof DeclaredType) {
Element element = ((DeclaredType) elementType).asElement();
if (element instanceof TypeElement) {
typeElement = (TypeElement) element;
}
}
if (scope != null && scope.getKind() == ElementKind.CLASS && !isKnownTypeDefinition(typeElement)) {
add(createTypeDefinition(typeElement), stack);
}
}
public boolean isKnownTypeDefinition(TypeElement el) {
return findTypeDefinition(el) != null || isKnownType(el);
}
/**
* Add any statically-referenced type definitions to the model.
*
* @param rootEl The root element.
* @param stack The context stack.
*/
public void addReferencedTypeDefinitions(RootElementDeclaration rootEl, LinkedList<Element> stack) {
TypeDefinition typeDefinition = rootEl.getTypeDefinition();
if (typeDefinition != null) {
add(typeDefinition, stack);
}
else {
//some root elements don't have a reference to their type definitions.
add(createTypeDefinition(rootEl.getDelegate()), stack);
}
}
protected void add(Schema schema, LinkedList<Element> stack) {
stack.add(schema);
try {
String namespace = schema.getNamespace();
String prefix = addNamespace(namespace);
this.namespacePrefixes.putAll(schema.getSpecifiedNamespacePrefixes());
SchemaInfo schemaInfo = schemas.get(namespace);
if (schemaInfo == null) {
schemaInfo = new SchemaInfo(this);
schemaInfo.setId(prefix);
schemaInfo.setNamespace(namespace);
schemas.put(namespace, schemaInfo);
}
if (schema.getElementFormDefault() != XmlNsForm.UNSET) {
for (Schema pckg : schemaInfo.getPackages()) {
if ((pckg.getElementFormDefault() != null) && (schema.getElementFormDefault() != pckg.getElementFormDefault())) {
throw new EnunciateException(schema.getQualifiedName() + ": inconsistent elementFormDefault declarations: " + pckg.getQualifiedName());
}
}
}
if (schema.getAttributeFormDefault() != XmlNsForm.UNSET) {
for (Schema pckg : schemaInfo.getPackages()) {
if ((pckg.getAttributeFormDefault() != null) && (schema.getAttributeFormDefault() != pckg.getAttributeFormDefault())) {
throw new EnunciateException(schema.getQualifiedName() + ": inconsistent attributeFormDefault declarations: " + pckg.getQualifiedName());
}
}
}
schemaInfo.getPackages().add(schema);
}
finally {
stack.pop();
}
}
protected void add(TypeDefinition typeDef, LinkedList<Element> stack) {
if (findTypeDefinition(typeDef) == null && !isKnownType(typeDef)) {
TypeDefinition previous = this.typeDefinitionsByQName.put(typeDef.getQname(), typeDef);
if (previous != null) {
warn("JAXB XML type definition %s named %s conflicts with %s of the same name.", typeDef.getQualifiedName(), typeDef.getQname(), previous.getQualifiedName());
warn("This will cause bugs in the generated documentation: all references to %s will be resolved to %s.", previous.getQualifiedName(), typeDef.getQualifiedName());
warn("It's recommended that you customize the name of one of these types using the @XmlType annotation.");
//remove the conflicted type definition:
this.typeDefinitions.remove(previous.getQualifiedName().toString());
this.schemas.get(previous.getNamespace()).getTypeDefinitions().remove(previous);
}
this.typeDefinitions.put(typeDef.getQualifiedName().toString(), typeDef);
if (this.context.isExcluded(typeDef)) {
warn("Added %s as a JAXB type definition even though is was supposed to be excluded according to configuration. It was referenced from %s%s, so it had to be included to prevent broken references.", typeDef.getQualifiedName(), stack.size() > 0 ? stack.get(0) : "an unknown location", stack.size() > 1 ? " of " + stack.get(1) : "");
}
else {
debug("Added %s as a JAXB type definition.", typeDef.getQualifiedName());
}
if (getContext().getProcessingEnvironment().findSourcePosition(typeDef) == null) {
OneTimeLogMessage.SOURCE_FILES_NOT_FOUND.log(getContext());
debug("Unable to find source file for %s.", typeDef.getQualifiedName());
}
if (typeDef.getAnnotation(XmlRootElement.class) != null && findElementDeclaration(typeDef) == null) {
//if the type definition is a root element, we want to make sure it's added to the model.
add(new RootElementDeclaration(typeDef.getDelegate(), typeDef, this), stack);
}
typeDef.getReferencedFrom().addAll(stack);
try {
stack.push(typeDef);
add(typeDef.getSchema(), stack);
String namespace = typeDef.getNamespace();
String prefix = addNamespace(namespace);
SchemaInfo schemaInfo = this.schemas.get(namespace);
if (schemaInfo == null) {
schemaInfo = new SchemaInfo(this);
schemaInfo.setId(prefix);
schemaInfo.setNamespace(namespace);
this.schemas.put(namespace, schemaInfo);
}
schemaInfo.getTypeDefinitions().add(typeDef);
addSeeAlsoTypeDefinitions(typeDef, stack);
for (com.webcohesion.enunciate.modules.jaxb.model.Element element : typeDef.getElements()) {
addReferencedTypeDefinitions(element, stack);
ImplicitSchemaElement implicitElement = getImplicitElement(element);
if (implicitElement != null) {
String implicitNamespace = element.isWrapped() ? element.getWrapperNamespace() : element.getNamespace();
SchemaInfo referencedSchemaInfo = schemas.get(implicitNamespace);
if (referencedSchemaInfo == null) {
referencedSchemaInfo = new SchemaInfo(this);
referencedSchemaInfo.setId(addNamespace(implicitNamespace));
referencedSchemaInfo.setNamespace(implicitNamespace);
schemas.put(implicitNamespace, referencedSchemaInfo);
}
referencedSchemaInfo.getImplicitSchemaElements().add(implicitElement);
}
}
for (Attribute attribute : typeDef.getAttributes()) {
addReferencedTypeDefinitions(attribute, stack);
ImplicitSchemaAttribute implicitAttribute = getImplicitAttribute(attribute);
if (implicitAttribute != null) {
String implicitAttributeNamespace = attribute.getNamespace();
SchemaInfo referencedSchemaInfo = schemas.get(implicitAttributeNamespace);
if (referencedSchemaInfo == null) {
referencedSchemaInfo = new SchemaInfo(this);
referencedSchemaInfo.setId(addNamespace(implicitAttributeNamespace));
referencedSchemaInfo.setNamespace(implicitAttributeNamespace);
schemas.put(implicitAttributeNamespace, referencedSchemaInfo);
}
referencedSchemaInfo.getImplicitSchemaAttributes().add(implicitAttribute);
}
}
if (typeDef.getAnyAttributeQNameEnumRef() != null) {
addReferencedTypeDefinitions(typeDef.getAnyAttributeQNameEnumRef(), stack);
}
Value value = typeDef.getValue();
if (value != null) {
addReferencedTypeDefinitions(value, stack);
}
TypeMirror superclass = typeDef.getSuperclass();
if (!typeDef.isEnum() && superclass != null && superclass.getKind() != TypeKind.NONE) {
addReferencedTypeDefinitions(superclass, stack);
}
}
finally {
stack.pop();
}
}
}
protected void addReferencedTypeDefinitions(Accessor accessor, LinkedList<Element> stack) {
addSeeAlsoTypeDefinitions(accessor, stack);
TypeMirror enumRef = accessor.getQNameEnumRef();
if (enumRef != null) {
addReferencedTypeDefinitions(enumRef, stack);
}
}
/**
* Add the type definition(s) referenced by the given attribute.
*
* @param attribute The attribute.
* @param stack The context stack.
*/
protected void addReferencedTypeDefinitions(Attribute attribute, LinkedList<Element> stack) {
addReferencedTypeDefinitions((Accessor) attribute, stack);
addReferencedTypeDefinitions(attribute.isAdapted() ? attribute.getAdapterType() : attribute.getAccessorType(), stack);
}
/**
* Add the type definition(s) referenced by the given value.
*
* @param value The value.
* @param stack The context stack.
*/
protected void addReferencedTypeDefinitions(Value value, LinkedList<Element> stack) {
addReferencedTypeDefinitions((Accessor) value, stack);
addReferencedTypeDefinitions(value.isAdapted() ? value.getAdapterType() : value.getAccessorType(), stack);
}
/**
* Add the referenced type definitions for the specified element.
*
* @param element The element.
* @param stack The context stack.
*/
protected void addReferencedTypeDefinitions(com.webcohesion.enunciate.modules.jaxb.model.Element element, LinkedList<Element> stack) {
addReferencedTypeDefinitions((Accessor) element, stack);
if (element instanceof ElementRef && element.isCollectionType()) {
//special case for collections of element refs because the collection is lazy-loaded.
addReferencedTypeDefinitions(element.getAccessorType(), stack);
}
else {
for (com.webcohesion.enunciate.modules.jaxb.model.Element choice : element.getChoices()) {
addReferencedTypeDefinitions(choice.isAdapted() ? choice.getAdapterType() : choice.getAccessorType(), stack);
}
}
}
/**
* Adds any referenced type definitions for the specified type mirror.
*
* @param type The type mirror.
*/
public void addReferencedTypeDefinitions(TypeMirror type, LinkedList<Element> stack) {
type.accept(new ReferencedTypeDefinitionVisitor(), new ReferenceContext(stack));
}
/**
* Gets the implicit element for the specified element, or null if there is no implicit element.
*
* @param element The element.
* @return The implicit element, or null if none.
*/
protected ImplicitSchemaElement getImplicitElement(com.webcohesion.enunciate.modules.jaxb.model.Element element) {
if (!(element instanceof ElementRef)) {
boolean qualified = element.getForm() == XmlNsForm.QUALIFIED;
String typeNamespace = element.getTypeDefinition().getNamespace();
typeNamespace = typeNamespace == null ? "" : typeNamespace;
String elementNamespace = element.isWrapped() ? element.getWrapperNamespace() : element.getNamespace();
elementNamespace = elementNamespace == null ? "" : elementNamespace;
if ((!elementNamespace.equals(typeNamespace)) && (qualified || !"".equals(elementNamespace))) {
return element.isWrapped() ? new ImplicitWrappedElementRef(element) : new ImplicitElementRef(element);
}
}
return null;
}
/**
* Gets the implicit attribute for the specified attribute, or null if there is no implicit attribute.
*
* @param attribute The attribute.
* @return The implicit attribute, or null if none.
*/
protected ImplicitSchemaAttribute getImplicitAttribute(Attribute attribute) {
boolean qualified = attribute.getForm() == XmlNsForm.QUALIFIED;
String typeNamespace = attribute.getTypeDefinition().getNamespace();
typeNamespace = typeNamespace == null ? "" : typeNamespace;
String attributeNamespace = attribute.getNamespace();
attributeNamespace = attributeNamespace == null ? "" : attributeNamespace;
if ((!attributeNamespace.equals(typeNamespace)) && (qualified || !"".equals(attributeNamespace))) {
return new ImplicitAttributeRef(attribute);
}
else {
return null;
}
}
/**
* Add any type definitions that are referenced using {@link javax.xml.bind.annotation.XmlSeeAlso}.
*
* @param declaration The declaration.
*/
protected void addSeeAlsoTypeDefinitions(Element declaration, LinkedList<Element> stack) {
XmlSeeAlso seeAlso = declaration.getAnnotation(XmlSeeAlso.class);
if (seeAlso != null) {
Elements elementUtils = getContext().getProcessingEnvironment().getElementUtils();
Types typeUtils = getContext().getProcessingEnvironment().getTypeUtils();
stack.push(elementUtils.getTypeElement(XmlSeeAlso.class.getName()));
try {
Class[] classes = seeAlso.value();
for (Class clazz : classes) {
addSeeAlsoReference(elementUtils.getTypeElement(clazz.getName()), stack);
}
}
catch (MirroredTypeException e) {
TypeMirror mirror = e.getTypeMirror();
Element element = typeUtils.asElement(mirror);
if (element instanceof TypeElement) {
addSeeAlsoReference((TypeElement) element, stack);
}
}
catch (MirroredTypesException e) {
List<? extends TypeMirror> mirrors = e.getTypeMirrors();
for (TypeMirror mirror : mirrors) {
Element element = typeUtils.asElement(mirror);
if (element instanceof TypeElement) {
addSeeAlsoReference((TypeElement) element, stack);
}
}
}
finally {
stack.pop();
}
}
else if (declaration instanceof TypeElement) {
// No annotation tells us what to do, so we'll look up subtypes and add them
for (Element el : getContext().getApiElements()) {
if ((el instanceof TypeElement) && !((TypeElement)el).getQualifiedName().contentEquals(((TypeElement)declaration).getQualifiedName()) && ((DecoratedTypeMirror) el.asType()).isInstanceOf(declaration)) {
add(createTypeDefinition((TypeElement) el), stack);
}
}
}
}
/**
* Add a "see also" reference.
*
* @param typeDeclaration The reference.
* @param stack The context stack.
*/
protected void addSeeAlsoReference(TypeElement typeDeclaration, LinkedList<Element> stack) {
if (!isKnownTypeDefinition(typeDeclaration) && typeDeclaration.getAnnotation(XmlRegistry.class) == null) {
add(createTypeDefinition(typeDeclaration), stack);
}
}
/**
* Whether the specified type is a known type.
*
* @param typeDef The type def.
* @return Whether the specified type is a known type.
*/
protected boolean isKnownType(TypeElement typeDef) {
return knownTypes.containsKey(typeDef.getQualifiedName().toString()) || ((DecoratedTypeMirror) typeDef.asType()).isInstanceOf(JAXBElement.class);
}
/**
* Visitor for XML-referenced type definitions.
*/
private class ReferencedTypeDefinitionVisitor extends SimpleTypeVisitor6<Void, ReferenceContext> {
@Override
public Void visitArray(ArrayType t, ReferenceContext context) {
return t.getComponentType().accept(this, context);
}
@Override
public Void visitDeclared(DeclaredType declaredType, ReferenceContext context) {
TypeElement declaration = (TypeElement) declaredType.asElement();
if (declaration.getKind() == ElementKind.ENUM) {
if (!isKnownTypeDefinition(declaration)) {
add(createTypeDefinition(declaration), context.referenceStack);
}
}
else if (declaredType instanceof AdapterType) {
((AdapterType) declaredType).getAdaptingType().accept(this, context);
}
else {
String qualifiedName = declaration.getQualifiedName().toString();
if (Object.class.getName().equals(qualifiedName)) {
//skip base object; not a type definition.
return null;
}
if (context.recursionStack.contains(declaration)) {
//we're already visiting this class...
return null;
}
context.recursionStack.push(declaration);
try {
MapType mapType = MapType.findMapType(declaredType, EnunciateJaxbContext.this);
if (mapType == null) {
if (!isKnownTypeDefinition(declaration) && !((DecoratedDeclaredType) declaredType).isCollection() && !((DecoratedDeclaredType) declaredType).isInstanceOf(JAXBElement.class)) {
add(createTypeDefinition(declaration), context.referenceStack);
}
List<? extends TypeMirror> typeArgs = declaredType.getTypeArguments();
if (typeArgs != null) {
for (TypeMirror typeArg : typeArgs) {
typeArg.accept(this, context);
}
}
}
else {
mapType.getKeyType().accept(this, context);
mapType.getValueType().accept(this, context);
}
}
finally {
context.recursionStack.pop();
}
}
return null;
}
@Override
public Void visitTypeVariable(TypeVariable t, ReferenceContext context) {
return t.getUpperBound().accept(this, context);
}
@Override
public Void visitWildcard(WildcardType t, ReferenceContext context) {
TypeMirror extendsBound = t.getExtendsBound();
if (extendsBound != null) {
extendsBound.accept(this, context);
}
TypeMirror superBound = t.getSuperBound();
if (superBound != null) {
superBound.accept(this, context);
}
return null;
}
@Override
public Void visitUnknown(TypeMirror t, ReferenceContext context) {
return defaultAction(t, context);
}
}
private static class ReferenceContext {
LinkedList<Element> referenceStack;
LinkedList<Element> recursionStack;
public ReferenceContext(LinkedList<Element> referenceStack) {
this.referenceStack = referenceStack;
recursionStack = new LinkedList<Element>();
}
}
}