org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader.java Source code

Java tutorial

Introduction

Here is the source code for org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader.java

Source

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later.
 * See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
 */
package org.hibernate.cfg.annotations.reflection;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AssociationOverride;
import javax.persistence.AssociationOverrides;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Basic;
import javax.persistence.Cacheable;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ColumnResult;
import javax.persistence.ConstructorResult;
import javax.persistence.Convert;
import javax.persistence.Converts;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.DiscriminatorType;
import javax.persistence.DiscriminatorValue;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.EntityListeners;
import javax.persistence.EntityResult;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.ExcludeDefaultListeners;
import javax.persistence.ExcludeSuperclassListeners;
import javax.persistence.FetchType;
import javax.persistence.FieldResult;
import javax.persistence.ForeignKey;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Index;
import javax.persistence.Inheritance;
import javax.persistence.InheritanceType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.MapKeyClass;
import javax.persistence.MapKeyColumn;
import javax.persistence.MapKeyEnumerated;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.MapKeyJoinColumns;
import javax.persistence.MapKeyTemporal;
import javax.persistence.MappedSuperclass;
import javax.persistence.MapsId;
import javax.persistence.NamedAttributeNode;
import javax.persistence.NamedEntityGraph;
import javax.persistence.NamedEntityGraphs;
import javax.persistence.NamedNativeQueries;
import javax.persistence.NamedNativeQuery;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.NamedStoredProcedureQueries;
import javax.persistence.NamedStoredProcedureQuery;
import javax.persistence.NamedSubgraph;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import javax.persistence.ParameterMode;
import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostRemove;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreRemove;
import javax.persistence.PreUpdate;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.PrimaryKeyJoinColumns;
import javax.persistence.QueryHint;
import javax.persistence.SecondaryTable;
import javax.persistence.SecondaryTables;
import javax.persistence.SequenceGenerator;
import javax.persistence.SqlResultSetMapping;
import javax.persistence.SqlResultSetMappings;
import javax.persistence.StoredProcedureParameter;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Transient;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;

import org.hibernate.AnnotationException;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.Columns;
import org.hibernate.annotations.ManyToAny;
import org.hibernate.annotations.common.annotationfactory.AnnotationDescriptor;
import org.hibernate.annotations.common.annotationfactory.AnnotationFactory;
import org.hibernate.annotations.common.reflection.AnnotationReader;
import org.hibernate.annotations.common.reflection.ReflectionUtil;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ReflectHelper;
import org.hibernate.internal.util.StringHelper;

import org.dom4j.Attribute;
import org.dom4j.Element;

/**
 * Encapsulates the overriding of Java annotations from an EJB 3.0 descriptor.
 *
 * @author Paolo Perrotta
 * @author Davide Marchignoli
 * @author Emmanuel Bernard
 * @author Hardy Ferentschik
 */
@SuppressWarnings("unchecked")
public class JPAOverriddenAnnotationReader implements AnnotationReader {
    private static final CoreMessageLogger LOG = CoreLogging.messageLogger(JPAOverriddenAnnotationReader.class);

    private static final String SCHEMA_VALIDATION = "Activate schema validation for more information";
    private static final String WORD_SEPARATOR = "-";

    private static enum PropertyType {
        PROPERTY, FIELD, METHOD
    }

    private static final Map<Class, String> annotationToXml;

    static {
        annotationToXml = new HashMap<Class, String>();
        annotationToXml.put(Entity.class, "entity");
        annotationToXml.put(MappedSuperclass.class, "mapped-superclass");
        annotationToXml.put(Embeddable.class, "embeddable");
        annotationToXml.put(Table.class, "table");
        annotationToXml.put(SecondaryTable.class, "secondary-table");
        annotationToXml.put(SecondaryTables.class, "secondary-table");
        annotationToXml.put(PrimaryKeyJoinColumn.class, "primary-key-join-column");
        annotationToXml.put(PrimaryKeyJoinColumns.class, "primary-key-join-column");
        annotationToXml.put(IdClass.class, "id-class");
        annotationToXml.put(Inheritance.class, "inheritance");
        annotationToXml.put(DiscriminatorValue.class, "discriminator-value");
        annotationToXml.put(DiscriminatorColumn.class, "discriminator-column");
        annotationToXml.put(SequenceGenerator.class, "sequence-generator");
        annotationToXml.put(TableGenerator.class, "table-generator");
        annotationToXml.put(NamedEntityGraph.class, "named-entity-graph");
        annotationToXml.put(NamedEntityGraphs.class, "named-entity-graph");
        annotationToXml.put(NamedQuery.class, "named-query");
        annotationToXml.put(NamedQueries.class, "named-query");
        annotationToXml.put(NamedNativeQuery.class, "named-native-query");
        annotationToXml.put(NamedNativeQueries.class, "named-native-query");
        annotationToXml.put(NamedStoredProcedureQuery.class, "named-stored-procedure-query");
        annotationToXml.put(NamedStoredProcedureQueries.class, "named-stored-procedure-query");
        annotationToXml.put(SqlResultSetMapping.class, "sql-result-set-mapping");
        annotationToXml.put(SqlResultSetMappings.class, "sql-result-set-mapping");
        annotationToXml.put(ExcludeDefaultListeners.class, "exclude-default-listeners");
        annotationToXml.put(ExcludeSuperclassListeners.class, "exclude-superclass-listeners");
        annotationToXml.put(AccessType.class, "access");
        annotationToXml.put(AttributeOverride.class, "attribute-override");
        annotationToXml.put(AttributeOverrides.class, "attribute-override");
        annotationToXml.put(AttributeOverride.class, "association-override");
        annotationToXml.put(AttributeOverrides.class, "association-override");
        annotationToXml.put(AttributeOverride.class, "map-key-attribute-override");
        annotationToXml.put(AttributeOverrides.class, "map-key-attribute-override");
        annotationToXml.put(Id.class, "id");
        annotationToXml.put(EmbeddedId.class, "embedded-id");
        annotationToXml.put(GeneratedValue.class, "generated-value");
        annotationToXml.put(Column.class, "column");
        annotationToXml.put(Columns.class, "column");
        annotationToXml.put(Temporal.class, "temporal");
        annotationToXml.put(Lob.class, "lob");
        annotationToXml.put(Enumerated.class, "enumerated");
        annotationToXml.put(Version.class, "version");
        annotationToXml.put(Transient.class, "transient");
        annotationToXml.put(Basic.class, "basic");
        annotationToXml.put(Embedded.class, "embedded");
        annotationToXml.put(ManyToOne.class, "many-to-one");
        annotationToXml.put(OneToOne.class, "one-to-one");
        annotationToXml.put(OneToMany.class, "one-to-many");
        annotationToXml.put(ManyToMany.class, "many-to-many");
        annotationToXml.put(Any.class, "any");
        annotationToXml.put(ManyToAny.class, "many-to-any");
        annotationToXml.put(JoinTable.class, "join-table");
        annotationToXml.put(JoinColumn.class, "join-column");
        annotationToXml.put(JoinColumns.class, "join-column");
        annotationToXml.put(MapKey.class, "map-key");
        annotationToXml.put(OrderBy.class, "order-by");
        annotationToXml.put(EntityListeners.class, "entity-listeners");
        annotationToXml.put(PrePersist.class, "pre-persist");
        annotationToXml.put(PreRemove.class, "pre-remove");
        annotationToXml.put(PreUpdate.class, "pre-update");
        annotationToXml.put(PostPersist.class, "post-persist");
        annotationToXml.put(PostRemove.class, "post-remove");
        annotationToXml.put(PostUpdate.class, "post-update");
        annotationToXml.put(PostLoad.class, "post-load");
        annotationToXml.put(CollectionTable.class, "collection-table");
        annotationToXml.put(MapKeyClass.class, "map-key-class");
        annotationToXml.put(MapKeyTemporal.class, "map-key-temporal");
        annotationToXml.put(MapKeyEnumerated.class, "map-key-enumerated");
        annotationToXml.put(MapKeyColumn.class, "map-key-column");
        annotationToXml.put(MapKeyJoinColumn.class, "map-key-join-column");
        annotationToXml.put(MapKeyJoinColumns.class, "map-key-join-column");
        annotationToXml.put(OrderColumn.class, "order-column");
        annotationToXml.put(Cacheable.class, "cacheable");
        annotationToXml.put(Index.class, "index");
        annotationToXml.put(ForeignKey.class, "foreign-key");
        annotationToXml.put(Convert.class, "convert");
        annotationToXml.put(Converts.class, "convert");
        annotationToXml.put(ConstructorResult.class, "constructor-result");
    }

    private XMLContext xmlContext;
    private final AnnotatedElement element;
    private String className;
    private String propertyName;
    private PropertyType propertyType;
    private transient Annotation[] annotations;
    private transient Map<Class, Annotation> annotationsMap;
    private transient List<Element> elementsForProperty;
    private AccessibleObject mirroredAttribute;

    public JPAOverriddenAnnotationReader(AnnotatedElement el, XMLContext xmlContext) {
        this.element = el;
        this.xmlContext = xmlContext;
        if (el instanceof Class) {
            Class clazz = (Class) el;
            className = clazz.getName();
        } else if (el instanceof Field) {
            Field field = (Field) el;
            className = field.getDeclaringClass().getName();
            propertyName = field.getName();
            propertyType = PropertyType.FIELD;
            String expectedGetter = "get" + Character.toUpperCase(propertyName.charAt(0))
                    + propertyName.substring(1);
            try {
                mirroredAttribute = field.getDeclaringClass().getDeclaredMethod(expectedGetter);
            } catch (NoSuchMethodException e) {
                //no method
            }
        } else if (el instanceof Method) {
            Method method = (Method) el;
            className = method.getDeclaringClass().getName();
            propertyName = method.getName();

            // YUCK!  The null here is the 'boundType', we'd rather get the TypeEnvironment()
            if (ReflectionUtil.isProperty(method, null, PersistentAttributeFilter.INSTANCE)) {
                if (propertyName.startsWith("get")) {
                    propertyName = Introspector.decapitalize(propertyName.substring("get".length()));
                } else if (propertyName.startsWith("is")) {
                    propertyName = Introspector.decapitalize(propertyName.substring("is".length()));
                } else {
                    throw new RuntimeException("Method " + propertyName + " is not a property getter");
                }
                propertyType = PropertyType.PROPERTY;
                try {
                    mirroredAttribute = method.getDeclaringClass().getDeclaredField(propertyName);
                } catch (NoSuchFieldException e) {
                    //no method
                }
            } else {
                propertyType = PropertyType.METHOD;
            }
        } else {
            className = null;
            propertyName = null;
        }
    }

    public <T extends Annotation> T getAnnotation(Class<T> annotationType) {
        initAnnotations();
        return (T) annotationsMap.get(annotationType);
    }

    public <T extends Annotation> boolean isAnnotationPresent(Class<T> annotationType) {
        initAnnotations();
        return annotationsMap.containsKey(annotationType);
    }

    public Annotation[] getAnnotations() {
        initAnnotations();
        return annotations;
    }

    /*
     * The idea is to create annotation proxies for the xml configuration elements. Using this proxy annotations together
     * with the {@link JPAMetadataProvider} allows to handle xml configuration the same way as annotation configuration.
     */
    private void initAnnotations() {
        if (annotations == null) {
            XMLContext.Default defaults = xmlContext.getDefault(className);
            if (className != null && propertyName == null) {
                //is a class
                Element tree = xmlContext.getXMLTree(className);
                Annotation[] annotations = getPhysicalAnnotations();
                List<Annotation> annotationList = new ArrayList<Annotation>(annotations.length + 5);
                annotationsMap = new HashMap<Class, Annotation>(annotations.length + 5);
                for (Annotation annotation : annotations) {
                    if (!annotationToXml.containsKey(annotation.annotationType())) {
                        //unknown annotations are left over
                        annotationList.add(annotation);
                    }
                }
                addIfNotNull(annotationList, getEntity(tree, defaults));
                addIfNotNull(annotationList, getMappedSuperclass(tree, defaults));
                addIfNotNull(annotationList, getEmbeddable(tree, defaults));
                addIfNotNull(annotationList, getTable(tree, defaults));
                addIfNotNull(annotationList, getSecondaryTables(tree, defaults));
                addIfNotNull(annotationList, getPrimaryKeyJoinColumns(tree, defaults, true));
                addIfNotNull(annotationList, getIdClass(tree, defaults));
                addIfNotNull(annotationList, getCacheable(tree, defaults));
                addIfNotNull(annotationList, getInheritance(tree, defaults));
                addIfNotNull(annotationList, getDiscriminatorValue(tree, defaults));
                addIfNotNull(annotationList, getDiscriminatorColumn(tree, defaults));
                addIfNotNull(annotationList, getSequenceGenerator(tree, defaults));
                addIfNotNull(annotationList, getTableGenerator(tree, defaults));
                addIfNotNull(annotationList, getNamedQueries(tree, defaults));
                addIfNotNull(annotationList, getNamedNativeQueries(tree, defaults));
                addIfNotNull(annotationList, getNamedStoredProcedureQueries(tree, defaults));
                addIfNotNull(annotationList, getNamedEntityGraphs(tree, defaults));
                addIfNotNull(annotationList, getSqlResultSetMappings(tree, defaults));
                addIfNotNull(annotationList, getExcludeDefaultListeners(tree, defaults));
                addIfNotNull(annotationList, getExcludeSuperclassListeners(tree, defaults));
                addIfNotNull(annotationList, getAccessType(tree, defaults));
                addIfNotNull(annotationList, getAttributeOverrides(tree, defaults, true));
                addIfNotNull(annotationList, getAssociationOverrides(tree, defaults, true));
                addIfNotNull(annotationList, getEntityListeners(tree, defaults));
                addIfNotNull(annotationList, getConverts(tree, defaults));

                this.annotations = annotationList.toArray(new Annotation[annotationList.size()]);
                for (Annotation ann : this.annotations) {
                    annotationsMap.put(ann.annotationType(), ann);
                }
                checkForOrphanProperties(tree);
            } else if (className != null) { //&& propertyName != null ) { //always true but less confusing
                Element tree = xmlContext.getXMLTree(className);
                Annotation[] annotations = getPhysicalAnnotations();
                List<Annotation> annotationList = new ArrayList<Annotation>(annotations.length + 5);
                annotationsMap = new HashMap<Class, Annotation>(annotations.length + 5);
                for (Annotation annotation : annotations) {
                    if (!annotationToXml.containsKey(annotation.annotationType())) {
                        //unknown annotations are left over
                        annotationList.add(annotation);
                    }
                }
                preCalculateElementsForProperty(tree);
                Transient transientAnn = getTransient(defaults);
                if (transientAnn != null) {
                    annotationList.add(transientAnn);
                } else {
                    if (defaults.canUseJavaAnnotations()) {
                        Annotation annotation = getPhysicalAnnotation(Access.class);
                        addIfNotNull(annotationList, annotation);
                    }
                    getId(annotationList, defaults);
                    getEmbeddedId(annotationList, defaults);
                    getEmbedded(annotationList, defaults);
                    getBasic(annotationList, defaults);
                    getVersion(annotationList, defaults);
                    getAssociation(ManyToOne.class, annotationList, defaults);
                    getAssociation(OneToOne.class, annotationList, defaults);
                    getAssociation(OneToMany.class, annotationList, defaults);
                    getAssociation(ManyToMany.class, annotationList, defaults);
                    getAssociation(Any.class, annotationList, defaults);
                    getAssociation(ManyToAny.class, annotationList, defaults);
                    getElementCollection(annotationList, defaults);
                    addIfNotNull(annotationList, getSequenceGenerator(elementsForProperty, defaults));
                    addIfNotNull(annotationList, getTableGenerator(elementsForProperty, defaults));
                    addIfNotNull(annotationList, getConvertsForAttribute(elementsForProperty, defaults));
                }
                processEventAnnotations(annotationList, defaults);
                //FIXME use annotationsMap rather than annotationList this will be faster since the annotation type is usually known at put() time
                this.annotations = annotationList.toArray(new Annotation[annotationList.size()]);
                for (Annotation ann : this.annotations) {
                    annotationsMap.put(ann.annotationType(), ann);
                }
            } else {
                this.annotations = getPhysicalAnnotations();
                annotationsMap = new HashMap<Class, Annotation>(annotations.length + 5);
                for (Annotation ann : this.annotations) {
                    annotationsMap.put(ann.annotationType(), ann);
                }
            }
        }
    }

    private Annotation getConvertsForAttribute(List<Element> elementsForProperty, XMLContext.Default defaults) {
        // NOTE : we use a map here to make sure that an xml and annotation referring to the same attribute
        // properly overrides.  Very sparse map, yes, but easy setup.
        // todo : revisit this
        // although bear in mind that this code is no longer used in 5.0...

        final Map<String, Convert> convertAnnotationsMap = new HashMap<String, Convert>();

        for (Element element : elementsForProperty) {
            final boolean isBasic = "basic".equals(element.getName());
            final boolean isEmbedded = "embedded".equals(element.getName());

            // todo : can be collections too

            final boolean canHaveConverts = isBasic || isEmbedded;

            if (!canHaveConverts) {
                continue;
            }

            final String attributeNamePrefix = isBasic ? null : propertyName;
            applyXmlDefinedConverts(element, defaults, attributeNamePrefix, convertAnnotationsMap);
        }

        // NOTE : per section 12.2.3.16 of the spec <convert/> is additive, although only if "metadata-complete" is not
        // specified in the XML

        if (defaults.canUseJavaAnnotations()) {
            // todo : note sure how to best handle attributeNamePrefix here
            applyPhysicalConvertAnnotations(propertyName, convertAnnotationsMap);
        }

        if (!convertAnnotationsMap.isEmpty()) {
            final AnnotationDescriptor groupingDescriptor = new AnnotationDescriptor(Converts.class);
            groupingDescriptor.setValue("value",
                    convertAnnotationsMap.values().toArray(new Convert[convertAnnotationsMap.size()]));
            return AnnotationFactory.create(groupingDescriptor);
        }

        return null;
    }

    private Converts getConverts(Element tree, XMLContext.Default defaults) {
        // NOTE : we use a map here to make sure that an xml and annotation referring to the same attribute
        // properly overrides.  Bit sparse, but easy...
        final Map<String, Convert> convertAnnotationsMap = new HashMap<String, Convert>();

        if (tree != null) {
            applyXmlDefinedConverts(tree, defaults, null, convertAnnotationsMap);
        }

        // NOTE : per section 12.2.3.16 of the spec <convert/> is additive, although only if "metadata-complete" is not
        // specified in the XML

        if (defaults.canUseJavaAnnotations()) {
            applyPhysicalConvertAnnotations(null, convertAnnotationsMap);
        }

        if (!convertAnnotationsMap.isEmpty()) {
            final AnnotationDescriptor groupingDescriptor = new AnnotationDescriptor(Converts.class);
            groupingDescriptor.setValue("value",
                    convertAnnotationsMap.values().toArray(new Convert[convertAnnotationsMap.size()]));
            return AnnotationFactory.create(groupingDescriptor);
        }

        return null;
    }

    private void applyXmlDefinedConverts(Element containingElement, XMLContext.Default defaults,
            String attributeNamePrefix, Map<String, Convert> convertAnnotationsMap) {
        final List<Element> convertElements = containingElement.elements("convert");
        for (Element convertElement : convertElements) {
            final AnnotationDescriptor convertAnnotationDescriptor = new AnnotationDescriptor(Convert.class);
            copyStringAttribute(convertAnnotationDescriptor, convertElement, "attribute-name", false);
            copyBooleanAttribute(convertAnnotationDescriptor, convertElement, "disable-conversion");

            final Attribute converterClassAttr = convertElement.attribute("converter");
            if (converterClassAttr != null) {
                final String converterClassName = XMLContext.buildSafeClassName(converterClassAttr.getValue(),
                        defaults);
                try {
                    final Class converterClass = ReflectHelper.classForName(converterClassName, this.getClass());
                    convertAnnotationDescriptor.setValue("converter", converterClass);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException(
                            "Unable to find specified converter class id-class: " + converterClassName, e);
                }
            }
            final Convert convertAnnotation = AnnotationFactory.create(convertAnnotationDescriptor);
            final String qualifiedAttributeName = qualifyConverterAttributeName(attributeNamePrefix,
                    convertAnnotation.attributeName());
            convertAnnotationsMap.put(qualifiedAttributeName, convertAnnotation);
        }

    }

    private String qualifyConverterAttributeName(String attributeNamePrefix, String specifiedAttributeName) {
        String qualifiedAttributeName;
        if (StringHelper.isNotEmpty(specifiedAttributeName)) {
            if (StringHelper.isNotEmpty(attributeNamePrefix)) {
                qualifiedAttributeName = attributeNamePrefix + '.' + specifiedAttributeName;
            } else {
                qualifiedAttributeName = specifiedAttributeName;
            }
        } else {
            qualifiedAttributeName = "";
        }
        return qualifiedAttributeName;
    }

    private void applyPhysicalConvertAnnotations(String attributeNamePrefix,
            Map<String, Convert> convertAnnotationsMap) {
        final Convert physicalAnnotation = getPhysicalAnnotation(Convert.class);
        if (physicalAnnotation != null) {
            // only add if no XML element named a converter for this attribute
            final String qualifiedAttributeName = qualifyConverterAttributeName(attributeNamePrefix,
                    physicalAnnotation.attributeName());
            if (!convertAnnotationsMap.containsKey(qualifiedAttributeName)) {
                convertAnnotationsMap.put(qualifiedAttributeName, physicalAnnotation);
            }
        }
        final Converts physicalGroupingAnnotation = getPhysicalAnnotation(Converts.class);
        if (physicalGroupingAnnotation != null) {
            for (Convert convertAnnotation : physicalGroupingAnnotation.value()) {
                // again, only add if no XML element named a converter for this attribute
                final String qualifiedAttributeName = qualifyConverterAttributeName(attributeNamePrefix,
                        convertAnnotation.attributeName());
                if (!convertAnnotationsMap.containsKey(qualifiedAttributeName)) {
                    convertAnnotationsMap.put(qualifiedAttributeName, convertAnnotation);
                }
            }
        }
    }

    private void checkForOrphanProperties(Element tree) {
        Class clazz;
        try {
            clazz = ReflectHelper.classForName(className, this.getClass());
        } catch (ClassNotFoundException e) {
            return; //a primitive type most likely
        }
        Element element = tree != null ? tree.element("attributes") : null;
        //put entity.attributes elements
        if (element != null) {
            //precompute the list of properties
            //TODO is it really useful...
            Set<String> properties = new HashSet<String>();
            for (Field field : clazz.getFields()) {
                properties.add(field.getName());
            }
            for (Method method : clazz.getMethods()) {
                String name = method.getName();
                if (name.startsWith("get")) {
                    properties.add(Introspector.decapitalize(name.substring("get".length())));
                } else if (name.startsWith("is")) {
                    properties.add(Introspector.decapitalize(name.substring("is".length())));
                }
            }
            for (Element subelement : (List<Element>) element.elements()) {
                String propertyName = subelement.attributeValue("name");
                if (!properties.contains(propertyName)) {
                    LOG.propertyNotFound(StringHelper.qualify(className, propertyName));
                }
            }
        }
    }

    /**
     * Adds {@code annotation} to the list (only if it's not null) and then returns it.
     *
     * @param annotationList The list of annotations.
     * @param annotation The annotation to add to the list.
     *
     * @return The annotation which was added to the list or {@code null}.
     */
    private Annotation addIfNotNull(List<Annotation> annotationList, Annotation annotation) {
        if (annotation != null) {
            annotationList.add(annotation);
        }
        return annotation;
    }

    //TODO mutualize the next 2 methods
    private Annotation getTableGenerator(List<Element> elementsForProperty, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            Element subelement = element != null ? element.element(annotationToXml.get(TableGenerator.class))
                    : null;
            if (subelement != null) {
                return buildTableGeneratorAnnotation(subelement, defaults);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(TableGenerator.class);
        } else {
            return null;
        }
    }

    private Annotation getSequenceGenerator(List<Element> elementsForProperty, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            Element subelement = element != null ? element.element(annotationToXml.get(SequenceGenerator.class))
                    : null;
            if (subelement != null) {
                return buildSequenceGeneratorAnnotation(subelement);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(SequenceGenerator.class);
        } else {
            return null;
        }
    }

    private void processEventAnnotations(List<Annotation> annotationList, XMLContext.Default defaults) {
        boolean eventElement = false;
        for (Element element : elementsForProperty) {
            String elementName = element.getName();
            if ("pre-persist".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PrePersist.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("pre-remove".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PreRemove.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("pre-update".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PreUpdate.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-persist".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostPersist.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-remove".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostRemove.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-update".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostUpdate.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            } else if ("post-load".equals(elementName)) {
                AnnotationDescriptor ad = new AnnotationDescriptor(PostLoad.class);
                annotationList.add(AnnotationFactory.create(ad));
                eventElement = true;
            }
        }
        if (!eventElement && defaults.canUseJavaAnnotations()) {
            Annotation ann = getPhysicalAnnotation(PrePersist.class);
            addIfNotNull(annotationList, ann);
            ann = getPhysicalAnnotation(PreRemove.class);
            addIfNotNull(annotationList, ann);
            ann = getPhysicalAnnotation(PreUpdate.class);
            addIfNotNull(annotationList, ann);
            ann = getPhysicalAnnotation(PostPersist.class);
            addIfNotNull(annotationList, ann);
            ann = getPhysicalAnnotation(PostRemove.class);
            addIfNotNull(annotationList, ann);
            ann = getPhysicalAnnotation(PostUpdate.class);
            addIfNotNull(annotationList, ann);
            ann = getPhysicalAnnotation(PostLoad.class);
            addIfNotNull(annotationList, ann);
        }
    }

    private EntityListeners getEntityListeners(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("entity-listeners") : null;
        if (element != null) {
            List<Class> entityListenerClasses = new ArrayList<Class>();
            for (Element subelement : (List<Element>) element.elements("entity-listener")) {
                String className = subelement.attributeValue("class");
                try {
                    entityListenerClasses.add(ReflectHelper
                            .classForName(XMLContext.buildSafeClassName(className, defaults), this.getClass()));
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find " + element.getPath() + ".class: " + className,
                            e);
                }
            }
            AnnotationDescriptor ad = new AnnotationDescriptor(EntityListeners.class);
            ad.setValue("value", entityListenerClasses.toArray(new Class[entityListenerClasses.size()]));
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(EntityListeners.class);
        } else {
            return null;
        }
    }

    private JoinTable overridesDefaultsInJoinTable(Annotation annotation, XMLContext.Default defaults) {
        //no element but might have some default or some annotation
        boolean defaultToJoinTable = !(isPhysicalAnnotationPresent(JoinColumn.class)
                || isPhysicalAnnotationPresent(JoinColumns.class));
        final Class<? extends Annotation> annotationClass = annotation.annotationType();
        defaultToJoinTable = defaultToJoinTable && ((annotationClass == ManyToMany.class
                && StringHelper.isEmpty(((ManyToMany) annotation).mappedBy()))
                || (annotationClass == OneToMany.class && StringHelper.isEmpty(((OneToMany) annotation).mappedBy()))
                || (annotationClass == ElementCollection.class));
        final Class<JoinTable> annotationType = JoinTable.class;
        if (defaultToJoinTable && (StringHelper.isNotEmpty(defaults.getCatalog())
                || StringHelper.isNotEmpty(defaults.getSchema()))) {
            AnnotationDescriptor ad = new AnnotationDescriptor(annotationType);
            if (defaults.canUseJavaAnnotations()) {
                JoinTable table = getPhysicalAnnotation(annotationType);
                if (table != null) {
                    ad.setValue("name", table.name());
                    ad.setValue("schema", table.schema());
                    ad.setValue("catalog", table.catalog());
                    ad.setValue("uniqueConstraints", table.uniqueConstraints());
                    ad.setValue("joinColumns", table.joinColumns());
                    ad.setValue("inverseJoinColumns", table.inverseJoinColumns());
                }
            }
            if (StringHelper.isEmpty((String) ad.valueOf("schema"))
                    && StringHelper.isNotEmpty(defaults.getSchema())) {
                ad.setValue("schema", defaults.getSchema());
            }
            if (StringHelper.isEmpty((String) ad.valueOf("catalog"))
                    && StringHelper.isNotEmpty(defaults.getCatalog())) {
                ad.setValue("catalog", defaults.getCatalog());
            }
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(annotationType);
        } else {
            return null;
        }
    }

    private void getJoinTable(List<Annotation> annotationList, Element tree, XMLContext.Default defaults) {
        addIfNotNull(annotationList, buildJoinTable(tree, defaults));
    }

    /*
     * no partial overriding possible
     */
    private JoinTable buildJoinTable(Element tree, XMLContext.Default defaults) {
        Element subelement = tree == null ? null : tree.element("join-table");
        final Class<JoinTable> annotationType = JoinTable.class;
        if (subelement == null) {
            return null;
        }
        //ignore java annotation, an element is defined
        AnnotationDescriptor annotation = new AnnotationDescriptor(annotationType);
        copyStringAttribute(annotation, subelement, "name", false);
        copyStringAttribute(annotation, subelement, "catalog", false);
        if (StringHelper.isNotEmpty(defaults.getCatalog())
                && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
            annotation.setValue("catalog", defaults.getCatalog());
        }
        copyStringAttribute(annotation, subelement, "schema", false);
        if (StringHelper.isNotEmpty(defaults.getSchema())
                && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
            annotation.setValue("schema", defaults.getSchema());
        }
        buildUniqueConstraints(annotation, subelement);
        buildIndex(annotation, subelement);
        annotation.setValue("joinColumns", getJoinColumns(subelement, false));
        annotation.setValue("inverseJoinColumns", getJoinColumns(subelement, true));
        return AnnotationFactory.create(annotation);
    }

    /**
     * As per section 12.2 of the JPA 2.0 specification, the association
     * subelements (many-to-one, one-to-many, one-to-one, many-to-many,
     * element-collection) completely override the mapping for the specified
     * field or property.  Thus, any methods which might in some contexts merge
     * with annotations must not do so in this context.
     *
     * @see #getElementCollection(List, org.hibernate.cfg.annotations.reflection.XMLContext.Default)
     */
    private void getAssociation(Class<? extends Annotation> annotationType, List<Annotation> annotationList,
            XMLContext.Default defaults) {
        String xmlName = annotationToXml.get(annotationType);
        for (Element element : elementsForProperty) {
            if (xmlName.equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(annotationType);
                addTargetClass(element, ad, "target-entity", defaults);
                getFetchType(ad, element);
                getCascades(ad, element, defaults);
                getJoinTable(annotationList, element, defaults);
                buildJoinColumns(annotationList, element);
                Annotation annotation = getPrimaryKeyJoinColumns(element, defaults, false);
                addIfNotNull(annotationList, annotation);
                copyBooleanAttribute(ad, element, "optional");
                copyBooleanAttribute(ad, element, "orphan-removal");
                copyStringAttribute(ad, element, "mapped-by", false);
                getOrderBy(annotationList, element);
                getMapKey(annotationList, element);
                getMapKeyClass(annotationList, element, defaults);
                getMapKeyColumn(annotationList, element);
                getOrderColumn(annotationList, element);
                getMapKeyTemporal(annotationList, element);
                getMapKeyEnumerated(annotationList, element);
                annotation = getMapKeyAttributeOverrides(element, defaults);
                addIfNotNull(annotationList, annotation);
                buildMapKeyJoinColumns(annotationList, element);
                getAssociationId(annotationList, element);
                getMapsId(annotationList, element);
                annotationList.add(AnnotationFactory.create(ad));
                getAccessType(annotationList, element);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getPhysicalAnnotation(annotationType);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = overridesDefaultsInJoinTable(annotation, defaults);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(JoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(JoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(PrimaryKeyJoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(PrimaryKeyJoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKey.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(OrderBy.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Lob.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Enumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyClass.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyTemporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyEnumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyJoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyJoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(OrderColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Cascade.class);
                addIfNotNull(annotationList, annotation);
            } else if (isPhysicalAnnotationPresent(ElementCollection.class)) { //JPA2
                annotation = overridesDefaultsInJoinTable(getPhysicalAnnotation(ElementCollection.class), defaults);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKey.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(OrderBy.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Lob.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Enumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(OrderColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyClass.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyTemporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyEnumerated.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyJoinColumn.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(MapKeyJoinColumns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(CollectionTable.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private void buildMapKeyJoinColumns(List<Annotation> annotationList, Element element) {
        MapKeyJoinColumn[] joinColumns = getMapKeyJoinColumns(element);
        if (joinColumns.length > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKeyJoinColumns.class);
            ad.setValue("value", joinColumns);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private MapKeyJoinColumn[] getMapKeyJoinColumns(Element element) {
        List<Element> subelements = element != null ? element.elements("map-key-join-column") : null;
        List<MapKeyJoinColumn> joinColumns = new ArrayList<MapKeyJoinColumn>();
        if (subelements != null) {
            for (Element subelement : subelements) {
                AnnotationDescriptor column = new AnnotationDescriptor(MapKeyJoinColumn.class);
                copyStringAttribute(column, subelement, "name", false);
                copyStringAttribute(column, subelement, "referenced-column-name", false);
                copyBooleanAttribute(column, subelement, "unique");
                copyBooleanAttribute(column, subelement, "nullable");
                copyBooleanAttribute(column, subelement, "insertable");
                copyBooleanAttribute(column, subelement, "updatable");
                copyStringAttribute(column, subelement, "column-definition", false);
                copyStringAttribute(column, subelement, "table", false);
                joinColumns.add((MapKeyJoinColumn) AnnotationFactory.create(column));
            }
        }
        return joinColumns.toArray(new MapKeyJoinColumn[joinColumns.size()]);
    }

    private AttributeOverrides getMapKeyAttributeOverrides(Element tree, XMLContext.Default defaults) {
        List<AttributeOverride> attributes = buildAttributeOverrides(tree, "map-key-attribute-override");
        return mergeAttributeOverrides(defaults, attributes, false);
    }

    private Cacheable getCacheable(Element element, XMLContext.Default defaults) {
        if (element != null) {
            String attValue = element.attributeValue("cacheable");
            if (attValue != null) {
                AnnotationDescriptor ad = new AnnotationDescriptor(Cacheable.class);
                ad.setValue("value", Boolean.valueOf(attValue));
                return AnnotationFactory.create(ad);
            }
        }
        if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(Cacheable.class);
        } else {
            return null;
        }
    }

    /**
     * Adds a @MapKeyEnumerated annotation to the specified annotationList if the specified element
     * contains a map-key-enumerated sub-element. This should only be the case for
     * element-collection, many-to-many, or one-to-many associations.
     */
    private void getMapKeyEnumerated(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("map-key-enumerated") : null;
        if (subelement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKeyEnumerated.class);
            EnumType value = EnumType.valueOf(subelement.getTextTrim());
            ad.setValue("value", value);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    /**
     * Adds a @MapKeyTemporal annotation to the specified annotationList if the specified element
     * contains a map-key-temporal sub-element. This should only be the case for element-collection,
     * many-to-many, or one-to-many associations.
     */
    private void getMapKeyTemporal(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("map-key-temporal") : null;
        if (subelement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKeyTemporal.class);
            TemporalType value = TemporalType.valueOf(subelement.getTextTrim());
            ad.setValue("value", value);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    /**
     * Adds an @OrderColumn annotation to the specified annotationList if the specified element
     * contains an order-column sub-element. This should only be the case for element-collection,
     * many-to-many, or one-to-many associations.
     */
    private void getOrderColumn(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("order-column") : null;
        if (subelement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(OrderColumn.class);
            copyStringAttribute(ad, subelement, "name", false);
            copyBooleanAttribute(ad, subelement, "nullable");
            copyBooleanAttribute(ad, subelement, "insertable");
            copyBooleanAttribute(ad, subelement, "updatable");
            copyStringAttribute(ad, subelement, "column-definition", false);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    /**
     * Adds a @MapsId annotation to the specified annotationList if the specified element has the
     * maps-id attribute set. This should only be the case for many-to-one or one-to-one
     * associations.
     */
    private void getMapsId(List<Annotation> annotationList, Element element) {
        String attrVal = element.attributeValue("maps-id");
        if (attrVal != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(MapsId.class);
            ad.setValue("value", attrVal);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    /**
     * Adds an @Id annotation to the specified annotationList if the specified element has the id
     * attribute set to true. This should only be the case for many-to-one or one-to-one
     * associations.
     */
    private void getAssociationId(List<Annotation> annotationList, Element element) {
        String attrVal = element.attributeValue("id");
        if ("true".equals(attrVal)) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Id.class);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void addTargetClass(Element element, AnnotationDescriptor ad, String nodeName,
            XMLContext.Default defaults) {
        String className = element.attributeValue(nodeName);
        if (className != null) {
            Class clazz;
            try {
                clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(className, defaults),
                        this.getClass());
            } catch (ClassNotFoundException e) {
                throw new AnnotationException(
                        "Unable to find " + element.getPath() + " " + nodeName + ": " + className, e);
            }
            ad.setValue(getJavaAttributeNameFromXMLOne(nodeName), clazz);
        }
    }

    /**
     * As per sections 12.2.3.23.9, 12.2.4.8.9 and 12.2.5.3.6 of the JPA 2.0
     * specification, the element-collection subelement completely overrides the
     * mapping for the specified field or property.  Thus, any methods which
     * might in some contexts merge with annotations must not do so in this
     * context.
     */
    private void getElementCollection(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("element-collection".equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(ElementCollection.class);
                addTargetClass(element, ad, "target-class", defaults);
                getFetchType(ad, element);
                getOrderBy(annotationList, element);
                getOrderColumn(annotationList, element);
                getMapKey(annotationList, element);
                getMapKeyClass(annotationList, element, defaults);
                getMapKeyTemporal(annotationList, element);
                getMapKeyEnumerated(annotationList, element);
                getMapKeyColumn(annotationList, element);
                buildMapKeyJoinColumns(annotationList, element);
                Annotation annotation = getColumn(element.element("column"), false, element);
                addIfNotNull(annotationList, annotation);
                getTemporal(annotationList, element);
                getEnumerated(annotationList, element);
                getLob(annotationList, element);
                //Both map-key-attribute-overrides and attribute-overrides
                //translate into AttributeOverride annotations, which need
                //need to be wrapped in the same AttributeOverrides annotation.
                List<AttributeOverride> attributes = new ArrayList<AttributeOverride>();
                attributes.addAll(buildAttributeOverrides(element, "map-key-attribute-override"));
                attributes.addAll(buildAttributeOverrides(element, "attribute-override"));
                annotation = mergeAttributeOverrides(defaults, attributes, false);
                addIfNotNull(annotationList, annotation);
                annotation = getAssociationOverrides(element, defaults, false);
                addIfNotNull(annotationList, annotation);
                getCollectionTable(annotationList, element, defaults);
                annotationList.add(AnnotationFactory.create(ad));
                getAccessType(annotationList, element);
            }
        }
    }

    private void getOrderBy(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("order-by") : null;
        if (subelement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(OrderBy.class);
            copyStringElement(subelement, ad, "value");
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getMapKey(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("map-key") : null;
        if (subelement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKey.class);
            copyStringAttribute(ad, subelement, "name", false);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getMapKeyColumn(List<Annotation> annotationList, Element element) {
        Element subelement = element != null ? element.element("map-key-column") : null;
        if (subelement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKeyColumn.class);
            copyStringAttribute(ad, subelement, "name", false);
            copyBooleanAttribute(ad, subelement, "unique");
            copyBooleanAttribute(ad, subelement, "nullable");
            copyBooleanAttribute(ad, subelement, "insertable");
            copyBooleanAttribute(ad, subelement, "updatable");
            copyStringAttribute(ad, subelement, "column-definition", false);
            copyStringAttribute(ad, subelement, "table", false);
            copyIntegerAttribute(ad, subelement, "length");
            copyIntegerAttribute(ad, subelement, "precision");
            copyIntegerAttribute(ad, subelement, "scale");
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getMapKeyClass(List<Annotation> annotationList, Element element, XMLContext.Default defaults) {
        String nodeName = "map-key-class";
        Element subelement = element != null ? element.element(nodeName) : null;
        if (subelement != null) {
            String mapKeyClassName = subelement.attributeValue("class");
            AnnotationDescriptor ad = new AnnotationDescriptor(MapKeyClass.class);
            if (StringHelper.isNotEmpty(mapKeyClassName)) {
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(mapKeyClassName, defaults),
                            this.getClass());
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException(
                            "Unable to find " + element.getPath() + " " + nodeName + ": " + mapKeyClassName, e);
                }
                ad.setValue("value", clazz);
            }
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getCollectionTable(List<Annotation> annotationList, Element element, XMLContext.Default defaults) {
        Element subelement = element != null ? element.element("collection-table") : null;
        if (subelement != null) {
            AnnotationDescriptor annotation = new AnnotationDescriptor(CollectionTable.class);
            copyStringAttribute(annotation, subelement, "name", false);
            copyStringAttribute(annotation, subelement, "catalog", false);
            if (StringHelper.isNotEmpty(defaults.getCatalog())
                    && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
                annotation.setValue("catalog", defaults.getCatalog());
            }
            copyStringAttribute(annotation, subelement, "schema", false);
            if (StringHelper.isNotEmpty(defaults.getSchema())
                    && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
                annotation.setValue("schema", defaults.getSchema());
            }
            JoinColumn[] joinColumns = getJoinColumns(subelement, false);
            if (joinColumns.length > 0) {
                annotation.setValue("joinColumns", joinColumns);
            }
            buildUniqueConstraints(annotation, subelement);
            buildIndex(annotation, subelement);
            annotationList.add(AnnotationFactory.create(annotation));
        }
    }

    private void buildJoinColumns(List<Annotation> annotationList, Element element) {
        JoinColumn[] joinColumns = getJoinColumns(element, false);
        if (joinColumns.length > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(JoinColumns.class);
            ad.setValue("value", joinColumns);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getCascades(AnnotationDescriptor ad, Element element, XMLContext.Default defaults) {
        List<Element> elements = element != null ? element.elements("cascade") : new ArrayList<Element>(0);
        List<CascadeType> cascades = new ArrayList<CascadeType>();
        for (Element subelement : elements) {
            if (subelement.element("cascade-all") != null) {
                cascades.add(CascadeType.ALL);
            }
            if (subelement.element("cascade-persist") != null) {
                cascades.add(CascadeType.PERSIST);
            }
            if (subelement.element("cascade-merge") != null) {
                cascades.add(CascadeType.MERGE);
            }
            if (subelement.element("cascade-remove") != null) {
                cascades.add(CascadeType.REMOVE);
            }
            if (subelement.element("cascade-refresh") != null) {
                cascades.add(CascadeType.REFRESH);
            }
            if (subelement.element("cascade-detach") != null) {
                cascades.add(CascadeType.DETACH);
            }
        }
        if (Boolean.TRUE.equals(defaults.getCascadePersist()) && !cascades.contains(CascadeType.ALL)
                && !cascades.contains(CascadeType.PERSIST)) {
            cascades.add(CascadeType.PERSIST);
        }
        if (cascades.size() > 0) {
            ad.setValue("cascade", cascades.toArray(new CascadeType[cascades.size()]));
        }
    }

    private void getEmbedded(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("embedded".equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(Embedded.class);
                annotationList.add(AnnotationFactory.create(ad));
                Annotation annotation = getAttributeOverrides(element, defaults, false);
                addIfNotNull(annotationList, annotation);
                annotation = getAssociationOverrides(element, defaults, false);
                addIfNotNull(annotationList, annotation);
                getAccessType(annotationList, element);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getPhysicalAnnotation(Embedded.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getPhysicalAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private Transient getTransient(XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("transient".equals(element.getName())) {
                AnnotationDescriptor ad = new AnnotationDescriptor(Transient.class);
                return AnnotationFactory.create(ad);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(Transient.class);
        } else {
            return null;
        }
    }

    private void getVersion(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("version".equals(element.getName())) {
                Annotation annotation = buildColumns(element);
                addIfNotNull(annotationList, annotation);
                getTemporal(annotationList, element);
                AnnotationDescriptor basic = new AnnotationDescriptor(Version.class);
                annotationList.add(AnnotationFactory.create(basic));
                getAccessType(annotationList, element);
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            //we have nothing, so Java annotations might occurs
            Annotation annotation = getPhysicalAnnotation(Version.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getPhysicalAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private void getBasic(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("basic".equals(element.getName())) {
                Annotation annotation = buildColumns(element);
                addIfNotNull(annotationList, annotation);
                getAccessType(annotationList, element);
                getTemporal(annotationList, element);
                getLob(annotationList, element);
                getEnumerated(annotationList, element);
                AnnotationDescriptor basic = new AnnotationDescriptor(Basic.class);
                getFetchType(basic, element);
                copyBooleanAttribute(basic, element, "optional");
                annotationList.add(AnnotationFactory.create(basic));
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            //no annotation presence constraint, basic is the default
            Annotation annotation = getPhysicalAnnotation(Basic.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(Lob.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(Enumerated.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(Temporal.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(Column.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(Columns.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(AttributeOverride.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(AttributeOverrides.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(AssociationOverride.class);
            addIfNotNull(annotationList, annotation);
            annotation = getPhysicalAnnotation(AssociationOverrides.class);
            addIfNotNull(annotationList, annotation);
        }
    }

    private void getEnumerated(List<Annotation> annotationList, Element element) {
        Element subElement = element != null ? element.element("enumerated") : null;
        if (subElement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Enumerated.class);
            String enumerated = subElement.getTextTrim();
            if ("ORDINAL".equalsIgnoreCase(enumerated)) {
                ad.setValue("value", EnumType.ORDINAL);
            } else if ("STRING".equalsIgnoreCase(enumerated)) {
                ad.setValue("value", EnumType.STRING);
            } else if (StringHelper.isNotEmpty(enumerated)) {
                throw new AnnotationException("Unknown EnumType: " + enumerated + ". " + SCHEMA_VALIDATION);
            }
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getLob(List<Annotation> annotationList, Element element) {
        Element subElement = element != null ? element.element("lob") : null;
        if (subElement != null) {
            annotationList.add(AnnotationFactory.create(new AnnotationDescriptor(Lob.class)));
        }
    }

    private void getFetchType(AnnotationDescriptor descriptor, Element element) {
        String fetchString = element != null ? element.attributeValue("fetch") : null;
        if (fetchString != null) {
            if ("eager".equalsIgnoreCase(fetchString)) {
                descriptor.setValue("fetch", FetchType.EAGER);
            } else if ("lazy".equalsIgnoreCase(fetchString)) {
                descriptor.setValue("fetch", FetchType.LAZY);
            }
        }
    }

    private void getEmbeddedId(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("embedded-id".equals(element.getName())) {
                if (isProcessingId(defaults)) {
                    Annotation annotation = getAttributeOverrides(element, defaults, false);
                    addIfNotNull(annotationList, annotation);
                    annotation = getAssociationOverrides(element, defaults, false);
                    addIfNotNull(annotationList, annotation);
                    AnnotationDescriptor ad = new AnnotationDescriptor(EmbeddedId.class);
                    annotationList.add(AnnotationFactory.create(ad));
                    getAccessType(annotationList, element);
                }
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getPhysicalAnnotation(EmbeddedId.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getPhysicalAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(GeneratedValue.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(TableGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(SequenceGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private void preCalculateElementsForProperty(Element tree) {
        elementsForProperty = new ArrayList<Element>();
        Element element = tree != null ? tree.element("attributes") : null;
        //put entity.attributes elements
        if (element != null) {
            for (Element subelement : (List<Element>) element.elements()) {
                if (propertyName.equals(subelement.attributeValue("name"))) {
                    elementsForProperty.add(subelement);
                }
            }
        }
        //add pre-* etc from entity and pure entity listener classes
        if (tree != null) {
            for (Element subelement : (List<Element>) tree.elements()) {
                if (propertyName.equals(subelement.attributeValue("method-name"))) {
                    elementsForProperty.add(subelement);
                }
            }
        }
    }

    private void getId(List<Annotation> annotationList, XMLContext.Default defaults) {
        for (Element element : elementsForProperty) {
            if ("id".equals(element.getName())) {
                boolean processId = isProcessingId(defaults);
                if (processId) {
                    Annotation annotation = buildColumns(element);
                    addIfNotNull(annotationList, annotation);
                    annotation = buildGeneratedValue(element);
                    addIfNotNull(annotationList, annotation);
                    getTemporal(annotationList, element);
                    //FIXME: fix the priority of xml over java for generator names
                    annotation = getTableGenerator(element, defaults);
                    addIfNotNull(annotationList, annotation);
                    annotation = getSequenceGenerator(element, defaults);
                    addIfNotNull(annotationList, annotation);
                    AnnotationDescriptor id = new AnnotationDescriptor(Id.class);
                    annotationList.add(AnnotationFactory.create(id));
                    getAccessType(annotationList, element);
                }
            }
        }
        if (elementsForProperty.size() == 0 && defaults.canUseJavaAnnotations()) {
            Annotation annotation = getPhysicalAnnotation(Id.class);
            if (annotation != null) {
                annotationList.add(annotation);
                annotation = getPhysicalAnnotation(Column.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Columns.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(GeneratedValue.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(Temporal.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(TableGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(SequenceGenerator.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AttributeOverrides.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverride.class);
                addIfNotNull(annotationList, annotation);
                annotation = getPhysicalAnnotation(AssociationOverrides.class);
                addIfNotNull(annotationList, annotation);
            }
        }
    }

    private boolean isProcessingId(XMLContext.Default defaults) {
        boolean isExplicit = defaults.getAccess() != null;
        boolean correctAccess = (PropertyType.PROPERTY.equals(propertyType)
                && AccessType.PROPERTY.equals(defaults.getAccess()))
                || (PropertyType.FIELD.equals(propertyType) && AccessType.FIELD.equals(defaults.getAccess()));
        boolean hasId = defaults.canUseJavaAnnotations()
                && (isPhysicalAnnotationPresent(Id.class) || isPhysicalAnnotationPresent(EmbeddedId.class));
        //if ( properAccessOnMetadataComplete || properOverridingOnMetadataNonComplete ) {
        boolean mirrorAttributeIsId = defaults.canUseJavaAnnotations()
                && (mirroredAttribute != null && (mirroredAttribute.isAnnotationPresent(Id.class)
                        || mirroredAttribute.isAnnotationPresent(EmbeddedId.class)));
        boolean propertyIsDefault = PropertyType.PROPERTY.equals(propertyType) && !mirrorAttributeIsId;
        return correctAccess || (!isExplicit && hasId) || (!isExplicit && propertyIsDefault);
    }

    private Columns buildColumns(Element element) {
        List<Element> subelements = element.elements("column");
        List<Column> columns = new ArrayList<Column>(subelements.size());
        for (Element subelement : subelements) {
            columns.add(getColumn(subelement, false, element));
        }
        if (columns.size() > 0) {
            AnnotationDescriptor columnsDescr = new AnnotationDescriptor(Columns.class);
            columnsDescr.setValue("columns", columns.toArray(new Column[columns.size()]));
            return AnnotationFactory.create(columnsDescr);
        } else {
            return null;
        }
    }

    private GeneratedValue buildGeneratedValue(Element element) {
        Element subElement = element != null ? element.element("generated-value") : null;
        if (subElement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(GeneratedValue.class);
            String strategy = subElement.attributeValue("strategy");
            if ("TABLE".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.TABLE);
            } else if ("SEQUENCE".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.SEQUENCE);
            } else if ("IDENTITY".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.IDENTITY);
            } else if ("AUTO".equalsIgnoreCase(strategy)) {
                ad.setValue("strategy", GenerationType.AUTO);
            } else if (StringHelper.isNotEmpty(strategy)) {
                throw new AnnotationException("Unknown GenerationType: " + strategy + ". " + SCHEMA_VALIDATION);
            }
            copyStringAttribute(ad, subElement, "generator", false);
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void getTemporal(List<Annotation> annotationList, Element element) {
        Element subElement = element != null ? element.element("temporal") : null;
        if (subElement != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Temporal.class);
            String temporal = subElement.getTextTrim();
            if ("DATE".equalsIgnoreCase(temporal)) {
                ad.setValue("value", TemporalType.DATE);
            } else if ("TIME".equalsIgnoreCase(temporal)) {
                ad.setValue("value", TemporalType.TIME);
            } else if ("TIMESTAMP".equalsIgnoreCase(temporal)) {
                ad.setValue("value", TemporalType.TIMESTAMP);
            } else if (StringHelper.isNotEmpty(temporal)) {
                throw new AnnotationException("Unknown TemporalType: " + temporal + ". " + SCHEMA_VALIDATION);
            }
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    private void getAccessType(List<Annotation> annotationList, Element element) {
        if (element == null) {
            return;
        }
        String access = element.attributeValue("access");
        if (access != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Access.class);
            AccessType type;
            try {
                type = AccessType.valueOf(access);
            } catch (IllegalArgumentException e) {
                throw new AnnotationException(access + " is not a valid access type. Check you xml confguration.");
            }

            if ((AccessType.PROPERTY.equals(type) && this.element instanceof Method)
                    || (AccessType.FIELD.equals(type) && this.element instanceof Field)) {
                return;
            }

            ad.setValue("value", type);
            annotationList.add(AnnotationFactory.create(ad));
        }
    }

    /**
     * @param mergeWithAnnotations Whether to use Java annotations for this
     * element, if present and not disabled by the XMLContext defaults.
     * In some contexts (such as an element-collection mapping) merging
     * with annotations is never allowed.
     */
    private AssociationOverrides getAssociationOverrides(Element tree, XMLContext.Default defaults,
            boolean mergeWithAnnotations) {
        List<AssociationOverride> attributes = buildAssociationOverrides(tree, defaults);
        if (mergeWithAnnotations && defaults.canUseJavaAnnotations()) {
            AssociationOverride annotation = getPhysicalAnnotation(AssociationOverride.class);
            addAssociationOverrideIfNeeded(annotation, attributes);
            AssociationOverrides annotations = getPhysicalAnnotation(AssociationOverrides.class);
            if (annotations != null) {
                for (AssociationOverride current : annotations.value()) {
                    addAssociationOverrideIfNeeded(current, attributes);
                }
            }
        }
        if (attributes.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(AssociationOverrides.class);
            ad.setValue("value", attributes.toArray(new AssociationOverride[attributes.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private List<AssociationOverride> buildAssociationOverrides(Element element, XMLContext.Default defaults) {
        List<Element> subelements = element == null ? null : element.elements("association-override");
        List<AssociationOverride> overrides = new ArrayList<AssociationOverride>();
        if (subelements != null && subelements.size() > 0) {
            for (Element current : subelements) {
                AnnotationDescriptor override = new AnnotationDescriptor(AssociationOverride.class);
                copyStringAttribute(override, current, "name", true);
                override.setValue("joinColumns", getJoinColumns(current, false));
                JoinTable joinTable = buildJoinTable(current, defaults);
                if (joinTable != null) {
                    override.setValue("joinTable", joinTable);
                }
                overrides.add((AssociationOverride) AnnotationFactory.create(override));
            }
        }
        return overrides;
    }

    private JoinColumn[] getJoinColumns(Element element, boolean isInverse) {
        List<Element> subelements = element != null
                ? element.elements(isInverse ? "inverse-join-column" : "join-column")
                : null;
        List<JoinColumn> joinColumns = new ArrayList<JoinColumn>();
        if (subelements != null) {
            for (Element subelement : subelements) {
                AnnotationDescriptor column = new AnnotationDescriptor(JoinColumn.class);
                copyStringAttribute(column, subelement, "name", false);
                copyStringAttribute(column, subelement, "referenced-column-name", false);
                copyBooleanAttribute(column, subelement, "unique");
                copyBooleanAttribute(column, subelement, "nullable");
                copyBooleanAttribute(column, subelement, "insertable");
                copyBooleanAttribute(column, subelement, "updatable");
                copyStringAttribute(column, subelement, "column-definition", false);
                copyStringAttribute(column, subelement, "table", false);
                joinColumns.add((JoinColumn) AnnotationFactory.create(column));
            }
        }
        return joinColumns.toArray(new JoinColumn[joinColumns.size()]);
    }

    private void addAssociationOverrideIfNeeded(AssociationOverride annotation,
            List<AssociationOverride> overrides) {
        if (annotation != null) {
            String overrideName = annotation.name();
            boolean present = false;
            for (AssociationOverride current : overrides) {
                if (current.name().equals(overrideName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                overrides.add(annotation);
            }
        }
    }

    /**
     * @param mergeWithAnnotations Whether to use Java annotations for this
     * element, if present and not disabled by the XMLContext defaults.
     * In some contexts (such as an association mapping) merging with
     * annotations is never allowed.
     */
    private AttributeOverrides getAttributeOverrides(Element tree, XMLContext.Default defaults,
            boolean mergeWithAnnotations) {
        List<AttributeOverride> attributes = buildAttributeOverrides(tree, "attribute-override");
        return mergeAttributeOverrides(defaults, attributes, mergeWithAnnotations);
    }

    /**
     * @param mergeWithAnnotations Whether to use Java annotations for this
     * element, if present and not disabled by the XMLContext defaults.
     * In some contexts (such as an association mapping) merging with
     * annotations is never allowed.
     */
    private AttributeOverrides mergeAttributeOverrides(XMLContext.Default defaults,
            List<AttributeOverride> attributes, boolean mergeWithAnnotations) {
        if (mergeWithAnnotations && defaults.canUseJavaAnnotations()) {
            AttributeOverride annotation = getPhysicalAnnotation(AttributeOverride.class);
            addAttributeOverrideIfNeeded(annotation, attributes);
            AttributeOverrides annotations = getPhysicalAnnotation(AttributeOverrides.class);
            if (annotations != null) {
                for (AttributeOverride current : annotations.value()) {
                    addAttributeOverrideIfNeeded(current, attributes);
                }
            }
        }
        if (attributes.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(AttributeOverrides.class);
            ad.setValue("value", attributes.toArray(new AttributeOverride[attributes.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private List<AttributeOverride> buildAttributeOverrides(Element element, String nodeName) {
        List<Element> subelements = element == null ? null : element.elements(nodeName);
        return buildAttributeOverrides(subelements, nodeName);
    }

    private List<AttributeOverride> buildAttributeOverrides(List<Element> subelements, String nodeName) {
        List<AttributeOverride> overrides = new ArrayList<AttributeOverride>();
        if (subelements != null && subelements.size() > 0) {
            for (Element current : subelements) {
                if (!current.getName().equals(nodeName)) {
                    continue;
                }
                AnnotationDescriptor override = new AnnotationDescriptor(AttributeOverride.class);
                copyStringAttribute(override, current, "name", true);
                Element column = current.element("column");
                override.setValue("column", getColumn(column, true, current));
                overrides.add((AttributeOverride) AnnotationFactory.create(override));
            }
        }
        return overrides;
    }

    private Column getColumn(Element element, boolean isMandatory, Element current) {
        //Element subelement = element != null ? element.element( "column" ) : null;
        if (element != null) {
            AnnotationDescriptor column = new AnnotationDescriptor(Column.class);
            copyStringAttribute(column, element, "name", false);
            copyBooleanAttribute(column, element, "unique");
            copyBooleanAttribute(column, element, "nullable");
            copyBooleanAttribute(column, element, "insertable");
            copyBooleanAttribute(column, element, "updatable");
            copyStringAttribute(column, element, "column-definition", false);
            copyStringAttribute(column, element, "table", false);
            copyIntegerAttribute(column, element, "length");
            copyIntegerAttribute(column, element, "precision");
            copyIntegerAttribute(column, element, "scale");
            return (Column) AnnotationFactory.create(column);
        } else {
            if (isMandatory) {
                throw new AnnotationException(current.getPath() + ".column is mandatory. " + SCHEMA_VALIDATION);
            }
            return null;
        }
    }

    private void addAttributeOverrideIfNeeded(AttributeOverride annotation, List<AttributeOverride> overrides) {
        if (annotation != null) {
            String overrideName = annotation.name();
            boolean present = false;
            for (AttributeOverride current : overrides) {
                if (current.name().equals(overrideName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                overrides.add(annotation);
            }
        }
    }

    private Access getAccessType(Element tree, XMLContext.Default defaults) {
        String access = tree == null ? null : tree.attributeValue("access");
        if (access != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Access.class);
            AccessType type;
            try {
                type = AccessType.valueOf(access);
            } catch (IllegalArgumentException e) {
                throw new AnnotationException(access + " is not a valid access type. Check you xml confguration.");
            }
            ad.setValue("value", type);
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations() && isPhysicalAnnotationPresent(Access.class)) {
            return getPhysicalAnnotation(Access.class);
        } else if (defaults.getAccess() != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Access.class);
            ad.setValue("value", defaults.getAccess());
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private ExcludeSuperclassListeners getExcludeSuperclassListeners(Element tree, XMLContext.Default defaults) {
        return (ExcludeSuperclassListeners) getMarkerAnnotation(ExcludeSuperclassListeners.class, tree, defaults);
    }

    private ExcludeDefaultListeners getExcludeDefaultListeners(Element tree, XMLContext.Default defaults) {
        return (ExcludeDefaultListeners) getMarkerAnnotation(ExcludeDefaultListeners.class, tree, defaults);
    }

    private Annotation getMarkerAnnotation(Class<? extends Annotation> clazz, Element element,
            XMLContext.Default defaults) {
        Element subelement = element == null ? null : element.element(annotationToXml.get(clazz));
        if (subelement != null) {
            return AnnotationFactory.create(new AnnotationDescriptor(clazz));
        } else if (defaults.canUseJavaAnnotations()) {
            //TODO wonder whether it should be excluded so that user can undone it
            return getPhysicalAnnotation(clazz);
        } else {
            return null;
        }
    }

    private SqlResultSetMappings getSqlResultSetMappings(Element tree, XMLContext.Default defaults) {
        List<SqlResultSetMapping> results = buildSqlResultsetMappings(tree, defaults);
        if (defaults.canUseJavaAnnotations()) {
            SqlResultSetMapping annotation = getPhysicalAnnotation(SqlResultSetMapping.class);
            addSqlResultsetMappingIfNeeded(annotation, results);
            SqlResultSetMappings annotations = getPhysicalAnnotation(SqlResultSetMappings.class);
            if (annotations != null) {
                for (SqlResultSetMapping current : annotations.value()) {
                    addSqlResultsetMappingIfNeeded(current, results);
                }
            }
        }
        if (results.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(SqlResultSetMappings.class);
            ad.setValue("value", results.toArray(new SqlResultSetMapping[results.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    public static List<NamedEntityGraph> buildNamedEntityGraph(Element element, XMLContext.Default defaults) {
        if (element == null) {
            return new ArrayList<NamedEntityGraph>();
        }
        List<NamedEntityGraph> namedEntityGraphList = new ArrayList<NamedEntityGraph>();
        List<Element> namedEntityGraphElements = element.elements("named-entity-graph");
        for (Element subElement : namedEntityGraphElements) {
            AnnotationDescriptor ann = new AnnotationDescriptor(NamedEntityGraph.class);
            copyStringAttribute(ann, subElement, "name", false);
            copyBooleanAttribute(ann, subElement, "include-all-attributes");
            bindNamedAttributeNodes(subElement, ann);

            List<Element> subgraphNodes = subElement.elements("subgraph");
            bindNamedSubgraph(defaults, ann, subgraphNodes);
            List<Element> subclassSubgraphNodes = subElement.elements("subclass-subgraph");
            bindNamedSubgraph(defaults, ann, subclassSubgraphNodes);
            namedEntityGraphList.add((NamedEntityGraph) AnnotationFactory.create(ann));
        }
        //TODO
        return namedEntityGraphList;
    }

    private static void bindNamedSubgraph(XMLContext.Default defaults, AnnotationDescriptor ann,
            List<Element> subgraphNodes) {
        List<NamedSubgraph> annSubgraphNodes = new ArrayList<NamedSubgraph>();
        for (Element subgraphNode : subgraphNodes) {
            AnnotationDescriptor annSubgraphNode = new AnnotationDescriptor(NamedSubgraph.class);
            copyStringAttribute(annSubgraphNode, subgraphNode, "name", true);
            String clazzName = subgraphNode.attributeValue("class");
            Class clazz;
            try {
                clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                        JPAOverriddenAnnotationReader.class);
            } catch (ClassNotFoundException e) {
                throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
            }
            annSubgraphNode.setValue("type", clazz);
            bindNamedAttributeNodes(subgraphNode, annSubgraphNode);
            annSubgraphNodes.add((NamedSubgraph) AnnotationFactory.create(annSubgraphNode));
        }
        ann.setValue("subgraphs", annSubgraphNodes.toArray(new NamedSubgraph[annSubgraphNodes.size()]));
    }

    private static void bindNamedAttributeNodes(Element subElement, AnnotationDescriptor ann) {
        List<Element> namedAttributeNodes = subElement.elements("named-attribute-node");
        List<NamedAttributeNode> annNamedAttributeNodes = new ArrayList<NamedAttributeNode>();
        for (Element namedAttributeNode : namedAttributeNodes) {
            AnnotationDescriptor annNamedAttributeNode = new AnnotationDescriptor(NamedAttributeNode.class);
            copyStringAttribute(annNamedAttributeNode, namedAttributeNode, "value", "name", true);
            copyStringAttribute(annNamedAttributeNode, namedAttributeNode, "subgraph", false);
            copyStringAttribute(annNamedAttributeNode, namedAttributeNode, "key-subgraph", false);
            annNamedAttributeNodes.add((NamedAttributeNode) AnnotationFactory.create(annNamedAttributeNode));
        }
        ann.setValue("attributeNodes",
                annNamedAttributeNodes.toArray(new NamedAttributeNode[annNamedAttributeNodes.size()]));
    }

    public static List<NamedStoredProcedureQuery> buildNamedStoreProcedureQueries(Element element,
            XMLContext.Default defaults) {
        if (element == null) {
            return new ArrayList<NamedStoredProcedureQuery>();
        }
        List namedStoredProcedureElements = element.elements("named-stored-procedure-query");
        List<NamedStoredProcedureQuery> namedStoredProcedureQueries = new ArrayList<NamedStoredProcedureQuery>();
        for (Object obj : namedStoredProcedureElements) {
            Element subElement = (Element) obj;
            AnnotationDescriptor ann = new AnnotationDescriptor(NamedStoredProcedureQuery.class);
            copyStringAttribute(ann, subElement, "name", true);
            copyStringAttribute(ann, subElement, "procedure-name", true);

            List<Element> elements = subElement.elements("parameter");
            List<StoredProcedureParameter> storedProcedureParameters = new ArrayList<StoredProcedureParameter>();

            for (Element parameterElement : elements) {
                AnnotationDescriptor parameterDescriptor = new AnnotationDescriptor(StoredProcedureParameter.class);
                copyStringAttribute(parameterDescriptor, parameterElement, "name", false);
                String modeValue = parameterElement.attributeValue("mode");
                if (modeValue == null) {
                    parameterDescriptor.setValue("mode", ParameterMode.IN);
                } else {
                    parameterDescriptor.setValue("mode", ParameterMode.valueOf(modeValue.toUpperCase(Locale.ROOT)));
                }
                String clazzName = parameterElement.attributeValue("class");
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                            JPAOverriddenAnnotationReader.class);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
                }
                parameterDescriptor.setValue("type", clazz);
                storedProcedureParameters
                        .add((StoredProcedureParameter) AnnotationFactory.create(parameterDescriptor));
            }

            ann.setValue("parameters", storedProcedureParameters
                    .toArray(new StoredProcedureParameter[storedProcedureParameters.size()]));

            elements = subElement.elements("result-class");
            List<Class> returnClasses = new ArrayList<Class>();
            for (Element classElement : elements) {
                String clazzName = classElement.getTextTrim();
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                            JPAOverriddenAnnotationReader.class);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
                }
                returnClasses.add(clazz);
            }
            ann.setValue("resultClasses", returnClasses.toArray(new Class[returnClasses.size()]));

            elements = subElement.elements("result-set-mapping");
            List<String> resultSetMappings = new ArrayList<String>();
            for (Element resultSetMappingElement : elements) {
                resultSetMappings.add(resultSetMappingElement.getTextTrim());
            }
            ann.setValue("resultSetMappings", resultSetMappings.toArray(new String[resultSetMappings.size()]));
            elements = subElement.elements("hint");
            buildQueryHints(elements, ann);
            namedStoredProcedureQueries.add((NamedStoredProcedureQuery) AnnotationFactory.create(ann));
        }
        return namedStoredProcedureQueries;

    }

    public static List<SqlResultSetMapping> buildSqlResultsetMappings(Element element,
            XMLContext.Default defaults) {
        final List<SqlResultSetMapping> builtResultSetMappings = new ArrayList<SqlResultSetMapping>();
        if (element == null) {
            return builtResultSetMappings;
        }

        // iterate over each <sql-result-set-mapping/> element
        for (Object resultSetMappingElementObject : element.elements("sql-result-set-mapping")) {
            final Element resultSetMappingElement = (Element) resultSetMappingElementObject;

            final AnnotationDescriptor resultSetMappingAnnotation = new AnnotationDescriptor(
                    SqlResultSetMapping.class);
            copyStringAttribute(resultSetMappingAnnotation, resultSetMappingElement, "name", true);

            // iterate over the <sql-result-set-mapping/> sub-elements, which should include:
            //      * <entity-result/>
            //      * <column-result/>
            //      * <constructor-result/>

            List<EntityResult> entityResultAnnotations = null;
            List<ColumnResult> columnResultAnnotations = null;
            List<ConstructorResult> constructorResultAnnotations = null;

            for (Object resultElementObject : resultSetMappingElement.elements()) {
                final Element resultElement = (Element) resultElementObject;

                if ("entity-result".equals(resultElement.getName())) {
                    if (entityResultAnnotations == null) {
                        entityResultAnnotations = new ArrayList<EntityResult>();
                    }
                    // process the <entity-result/>
                    entityResultAnnotations.add(buildEntityResult(resultElement, defaults));
                } else if ("column-result".equals(resultElement.getName())) {
                    if (columnResultAnnotations == null) {
                        columnResultAnnotations = new ArrayList<ColumnResult>();
                    }
                    columnResultAnnotations.add(buildColumnResult(resultElement, defaults));
                } else if ("constructor-result".equals(resultElement.getName())) {
                    if (constructorResultAnnotations == null) {
                        constructorResultAnnotations = new ArrayList<ConstructorResult>();
                    }
                    constructorResultAnnotations.add(buildConstructorResult(resultElement, defaults));
                } else {
                    // most likely the <result-class/> this code used to handle.  I have left the code here,
                    // but commented it out for now.  I'll just log a warning for now.
                    LOG.debug("Encountered unrecognized sql-result-set-mapping sub-element : "
                            + resultElement.getName());

                    //               String clazzName = subelement.attributeValue( "result-class" );
                    //               if ( StringHelper.isNotEmpty( clazzName ) ) {
                    //                  Class clazz;
                    //                  try {
                    //                     clazz = ReflectHelper.classForName(
                    //                           XMLContext.buildSafeClassName( clazzName, defaults ),
                    //                           JPAOverriddenAnnotationReader.class
                    //                     );
                    //                  }
                    //                  catch ( ClassNotFoundException e ) {
                    //                     throw new AnnotationException( "Unable to find entity-class: " + clazzName, e );
                    //                  }
                    //                  ann.setValue( "resultClass", clazz );
                    //               }
                }
            }

            if (entityResultAnnotations != null && !entityResultAnnotations.isEmpty()) {
                resultSetMappingAnnotation.setValue("entities",
                        entityResultAnnotations.toArray(new EntityResult[entityResultAnnotations.size()]));
            }
            if (columnResultAnnotations != null && !columnResultAnnotations.isEmpty()) {
                resultSetMappingAnnotation.setValue("columns",
                        columnResultAnnotations.toArray(new ColumnResult[columnResultAnnotations.size()]));
            }
            if (constructorResultAnnotations != null && !constructorResultAnnotations.isEmpty()) {
                resultSetMappingAnnotation.setValue("classes", constructorResultAnnotations
                        .toArray(new ConstructorResult[constructorResultAnnotations.size()]));
            }

            // this was part of the old code too, but could never figure out what it is supposed to do...
            // copyStringAttribute( ann, subelement, "result-set-mapping", false );

            builtResultSetMappings.add((SqlResultSetMapping) AnnotationFactory.create(resultSetMappingAnnotation));
        }

        return builtResultSetMappings;
    }

    private static EntityResult buildEntityResult(Element entityResultElement, XMLContext.Default defaults) {
        final AnnotationDescriptor entityResultDescriptor = new AnnotationDescriptor(EntityResult.class);

        final Class entityClass = resolveClassReference(entityResultElement.attributeValue("entity-class"),
                defaults);
        entityResultDescriptor.setValue("entityClass", entityClass);

        copyStringAttribute(entityResultDescriptor, entityResultElement, "discriminator-column", false);

        // process the <field-result/> sub-elements
        List<FieldResult> fieldResultAnnotations = new ArrayList<FieldResult>();
        for (Element fieldResult : (List<Element>) entityResultElement.elements("field-result")) {
            AnnotationDescriptor fieldResultDescriptor = new AnnotationDescriptor(FieldResult.class);
            copyStringAttribute(fieldResultDescriptor, fieldResult, "name", true);
            copyStringAttribute(fieldResultDescriptor, fieldResult, "column", true);
            fieldResultAnnotations.add((FieldResult) AnnotationFactory.create(fieldResultDescriptor));
        }
        entityResultDescriptor.setValue("fields",
                fieldResultAnnotations.toArray(new FieldResult[fieldResultAnnotations.size()]));
        return AnnotationFactory.create(entityResultDescriptor);
    }

    private static Class resolveClassReference(String className, XMLContext.Default defaults) {
        if (className == null) {
            throw new AnnotationException("<entity-result> without entity-class. " + SCHEMA_VALIDATION);
        }
        try {
            return ReflectHelper.classForName(XMLContext.buildSafeClassName(className, defaults),
                    JPAOverriddenAnnotationReader.class);
        } catch (ClassNotFoundException e) {
            throw new AnnotationException("Unable to find specified class: " + className, e);
        }
    }

    private static ColumnResult buildColumnResult(Element columnResultElement, XMLContext.Default defaults) {
        //      AnnotationDescriptor columnResultDescriptor = new AnnotationDescriptor( ColumnResult.class );
        //      copyStringAttribute( columnResultDescriptor, columnResultElement, "name", true );
        //      return AnnotationFactory.create( columnResultDescriptor );

        AnnotationDescriptor columnResultDescriptor = new AnnotationDescriptor(ColumnResult.class);
        copyStringAttribute(columnResultDescriptor, columnResultElement, "name", true);
        final String columnTypeName = columnResultElement.attributeValue("class");
        if (StringHelper.isNotEmpty(columnTypeName)) {
            columnResultDescriptor.setValue("type", resolveClassReference(columnTypeName, defaults));
        }
        return AnnotationFactory.create(columnResultDescriptor);
    }

    private static ConstructorResult buildConstructorResult(Element constructorResultElement,
            XMLContext.Default defaults) {
        AnnotationDescriptor constructorResultDescriptor = new AnnotationDescriptor(ConstructorResult.class);

        final Class entityClass = resolveClassReference(constructorResultElement.attributeValue("target-class"),
                defaults);
        constructorResultDescriptor.setValue("targetClass", entityClass);

        List<ColumnResult> columnResultAnnotations = new ArrayList<ColumnResult>();
        for (Element columnResultElement : (List<Element>) constructorResultElement.elements("column")) {
            columnResultAnnotations.add(buildColumnResult(columnResultElement, defaults));
        }
        constructorResultDescriptor.setValue("columns",
                columnResultAnnotations.toArray(new ColumnResult[columnResultAnnotations.size()]));

        return AnnotationFactory.create(constructorResultDescriptor);
    }

    private void addSqlResultsetMappingIfNeeded(SqlResultSetMapping annotation,
            List<SqlResultSetMapping> resultsets) {
        if (annotation != null) {
            String resultsetName = annotation.name();
            boolean present = false;
            for (SqlResultSetMapping current : resultsets) {
                if (current.name().equals(resultsetName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                resultsets.add(annotation);
            }
        }
    }

    private NamedQueries getNamedQueries(Element tree, XMLContext.Default defaults) {
        //TODO avoid the Proxy Creation (@NamedQueries) when possible
        List<NamedQuery> queries = (List<NamedQuery>) buildNamedQueries(tree, false, defaults);
        if (defaults.canUseJavaAnnotations()) {
            NamedQuery annotation = getPhysicalAnnotation(NamedQuery.class);
            addNamedQueryIfNeeded(annotation, queries);
            NamedQueries annotations = getPhysicalAnnotation(NamedQueries.class);
            if (annotations != null) {
                for (NamedQuery current : annotations.value()) {
                    addNamedQueryIfNeeded(current, queries);
                }
            }
        }
        if (queries.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(NamedQueries.class);
            ad.setValue("value", queries.toArray(new NamedQuery[queries.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void addNamedQueryIfNeeded(NamedQuery annotation, List<NamedQuery> queries) {
        if (annotation != null) {
            String queryName = annotation.name();
            boolean present = false;
            for (NamedQuery current : queries) {
                if (current.name().equals(queryName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                queries.add(annotation);
            }
        }
    }

    private NamedEntityGraphs getNamedEntityGraphs(Element tree, XMLContext.Default defaults) {
        List<NamedEntityGraph> queries = buildNamedEntityGraph(tree, defaults);
        if (defaults.canUseJavaAnnotations()) {
            NamedEntityGraph annotation = getPhysicalAnnotation(NamedEntityGraph.class);
            addNamedEntityGraphIfNeeded(annotation, queries);
            NamedEntityGraphs annotations = getPhysicalAnnotation(NamedEntityGraphs.class);
            if (annotations != null) {
                for (NamedEntityGraph current : annotations.value()) {
                    addNamedEntityGraphIfNeeded(current, queries);
                }
            }
        }
        if (queries.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(NamedEntityGraphs.class);
            ad.setValue("value", queries.toArray(new NamedEntityGraph[queries.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void addNamedEntityGraphIfNeeded(NamedEntityGraph annotation, List<NamedEntityGraph> queries) {
        if (annotation != null) {
            String queryName = annotation.name();
            boolean present = false;
            for (NamedEntityGraph current : queries) {
                if (current.name().equals(queryName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                queries.add(annotation);
            }
        }

    }

    private NamedStoredProcedureQueries getNamedStoredProcedureQueries(Element tree, XMLContext.Default defaults) {
        List<NamedStoredProcedureQuery> queries = buildNamedStoreProcedureQueries(tree, defaults);
        if (defaults.canUseJavaAnnotations()) {
            NamedStoredProcedureQuery annotation = getPhysicalAnnotation(NamedStoredProcedureQuery.class);
            addNamedStoredProcedureQueryIfNeeded(annotation, queries);
            NamedStoredProcedureQueries annotations = getPhysicalAnnotation(NamedStoredProcedureQueries.class);
            if (annotations != null) {
                for (NamedStoredProcedureQuery current : annotations.value()) {
                    addNamedStoredProcedureQueryIfNeeded(current, queries);
                }
            }
        }
        if (queries.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(NamedStoredProcedureQueries.class);
            ad.setValue("value", queries.toArray(new NamedStoredProcedureQuery[queries.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void addNamedStoredProcedureQueryIfNeeded(NamedStoredProcedureQuery annotation,
            List<NamedStoredProcedureQuery> queries) {
        if (annotation != null) {
            String queryName = annotation.name();
            boolean present = false;
            for (NamedStoredProcedureQuery current : queries) {
                if (current.name().equals(queryName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                queries.add(annotation);
            }
        }
    }

    private NamedNativeQueries getNamedNativeQueries(Element tree, XMLContext.Default defaults) {
        List<NamedNativeQuery> queries = (List<NamedNativeQuery>) buildNamedQueries(tree, true, defaults);
        if (defaults.canUseJavaAnnotations()) {
            NamedNativeQuery annotation = getPhysicalAnnotation(NamedNativeQuery.class);
            addNamedNativeQueryIfNeeded(annotation, queries);
            NamedNativeQueries annotations = getPhysicalAnnotation(NamedNativeQueries.class);
            if (annotations != null) {
                for (NamedNativeQuery current : annotations.value()) {
                    addNamedNativeQueryIfNeeded(current, queries);
                }
            }
        }
        if (queries.size() > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(NamedNativeQueries.class);
            ad.setValue("value", queries.toArray(new NamedNativeQuery[queries.size()]));
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private void addNamedNativeQueryIfNeeded(NamedNativeQuery annotation, List<NamedNativeQuery> queries) {
        if (annotation != null) {
            String queryName = annotation.name();
            boolean present = false;
            for (NamedNativeQuery current : queries) {
                if (current.name().equals(queryName)) {
                    present = true;
                    break;
                }
            }
            if (!present) {
                queries.add(annotation);
            }
        }
    }

    private static void buildQueryHints(List<Element> elements, AnnotationDescriptor ann) {
        List<QueryHint> queryHints = new ArrayList<QueryHint>(elements.size());
        for (Element hint : elements) {
            AnnotationDescriptor hintDescriptor = new AnnotationDescriptor(QueryHint.class);
            String value = hint.attributeValue("name");
            if (value == null) {
                throw new AnnotationException("<hint> without name. " + SCHEMA_VALIDATION);
            }
            hintDescriptor.setValue("name", value);
            value = hint.attributeValue("value");
            if (value == null) {
                throw new AnnotationException("<hint> without value. " + SCHEMA_VALIDATION);
            }
            hintDescriptor.setValue("value", value);
            queryHints.add((QueryHint) AnnotationFactory.create(hintDescriptor));
        }
        ann.setValue("hints", queryHints.toArray(new QueryHint[queryHints.size()]));
    }

    public static List buildNamedQueries(Element element, boolean isNative, XMLContext.Default defaults) {
        if (element == null) {
            return new ArrayList();
        }
        List namedQueryElementList = isNative ? element.elements("named-native-query")
                : element.elements("named-query");
        List namedQueries = new ArrayList();
        Iterator it = namedQueryElementList.listIterator();
        while (it.hasNext()) {
            Element subelement = (Element) it.next();
            AnnotationDescriptor ann = new AnnotationDescriptor(
                    isNative ? NamedNativeQuery.class : NamedQuery.class);
            copyStringAttribute(ann, subelement, "name", false);
            Element queryElt = subelement.element("query");
            if (queryElt == null) {
                throw new AnnotationException("No <query> element found." + SCHEMA_VALIDATION);
            }
            copyStringElement(queryElt, ann, "query");
            List<Element> elements = subelement.elements("hint");
            buildQueryHints(elements, ann);
            String clazzName = subelement.attributeValue("result-class");
            if (StringHelper.isNotEmpty(clazzName)) {
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(clazzName, defaults),
                            JPAOverriddenAnnotationReader.class);
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find entity-class: " + clazzName, e);
                }
                ann.setValue("resultClass", clazz);
            }
            copyStringAttribute(ann, subelement, "result-set-mapping", false);
            namedQueries.add(AnnotationFactory.create(ann));
        }
        return namedQueries;
    }

    private TableGenerator getTableGenerator(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element(annotationToXml.get(TableGenerator.class)) : null;
        if (element != null) {
            return buildTableGeneratorAnnotation(element, defaults);
        } else if (defaults.canUseJavaAnnotations() && isPhysicalAnnotationPresent(TableGenerator.class)) {
            TableGenerator tableAnn = getPhysicalAnnotation(TableGenerator.class);
            if (StringHelper.isNotEmpty(defaults.getSchema()) || StringHelper.isNotEmpty(defaults.getCatalog())) {
                AnnotationDescriptor annotation = new AnnotationDescriptor(TableGenerator.class);
                annotation.setValue("name", tableAnn.name());
                annotation.setValue("table", tableAnn.table());
                annotation.setValue("catalog", tableAnn.table());
                if (StringHelper.isEmpty((String) annotation.valueOf("catalog"))
                        && StringHelper.isNotEmpty(defaults.getCatalog())) {
                    annotation.setValue("catalog", defaults.getCatalog());
                }
                annotation.setValue("schema", tableAnn.table());
                if (StringHelper.isEmpty((String) annotation.valueOf("schema"))
                        && StringHelper.isNotEmpty(defaults.getSchema())) {
                    annotation.setValue("catalog", defaults.getSchema());
                }
                annotation.setValue("pkColumnName", tableAnn.pkColumnName());
                annotation.setValue("valueColumnName", tableAnn.valueColumnName());
                annotation.setValue("pkColumnValue", tableAnn.pkColumnValue());
                annotation.setValue("initialValue", tableAnn.initialValue());
                annotation.setValue("allocationSize", tableAnn.allocationSize());
                annotation.setValue("uniqueConstraints", tableAnn.uniqueConstraints());
                return AnnotationFactory.create(annotation);
            } else {
                return tableAnn;
            }
        } else {
            return null;
        }
    }

    public static TableGenerator buildTableGeneratorAnnotation(Element element, XMLContext.Default defaults) {
        AnnotationDescriptor ad = new AnnotationDescriptor(TableGenerator.class);
        copyStringAttribute(ad, element, "name", false);
        copyStringAttribute(ad, element, "table", false);
        copyStringAttribute(ad, element, "catalog", false);
        copyStringAttribute(ad, element, "schema", false);
        copyStringAttribute(ad, element, "pk-column-name", false);
        copyStringAttribute(ad, element, "value-column-name", false);
        copyStringAttribute(ad, element, "pk-column-value", false);
        copyIntegerAttribute(ad, element, "initial-value");
        copyIntegerAttribute(ad, element, "allocation-size");
        buildUniqueConstraints(ad, element);
        if (StringHelper.isEmpty((String) ad.valueOf("schema")) && StringHelper.isNotEmpty(defaults.getSchema())) {
            ad.setValue("schema", defaults.getSchema());
        }
        if (StringHelper.isEmpty((String) ad.valueOf("catalog"))
                && StringHelper.isNotEmpty(defaults.getCatalog())) {
            ad.setValue("catalog", defaults.getCatalog());
        }
        return AnnotationFactory.create(ad);
    }

    private SequenceGenerator getSequenceGenerator(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element(annotationToXml.get(SequenceGenerator.class)) : null;
        if (element != null) {
            return buildSequenceGeneratorAnnotation(element);
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(SequenceGenerator.class);
        } else {
            return null;
        }
    }

    public static SequenceGenerator buildSequenceGeneratorAnnotation(Element element) {
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(SequenceGenerator.class);
            copyStringAttribute(ad, element, "name", false);
            copyStringAttribute(ad, element, "sequence-name", false);
            copyIntegerAttribute(ad, element, "initial-value");
            copyIntegerAttribute(ad, element, "allocation-size");
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private DiscriminatorColumn getDiscriminatorColumn(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("discriminator-column") : null;
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(DiscriminatorColumn.class);
            copyStringAttribute(ad, element, "name", false);
            copyStringAttribute(ad, element, "column-definition", false);
            String value = element.attributeValue("discriminator-type");
            DiscriminatorType type = DiscriminatorType.STRING;
            if (value != null) {
                if ("STRING".equals(value)) {
                    type = DiscriminatorType.STRING;
                } else if ("CHAR".equals(value)) {
                    type = DiscriminatorType.CHAR;
                } else if ("INTEGER".equals(value)) {
                    type = DiscriminatorType.INTEGER;
                } else {
                    throw new AnnotationException(
                            "Unknown DiscrimiatorType in XML: " + value + " (" + SCHEMA_VALIDATION + ")");
                }
            }
            ad.setValue("discriminatorType", type);
            copyIntegerAttribute(ad, element, "length");
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(DiscriminatorColumn.class);
        } else {
            return null;
        }
    }

    private DiscriminatorValue getDiscriminatorValue(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("discriminator-value") : null;
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(DiscriminatorValue.class);
            copyStringElement(element, ad, "value");
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(DiscriminatorValue.class);
        } else {
            return null;
        }
    }

    private Inheritance getInheritance(Element tree, XMLContext.Default defaults) {
        Element element = tree != null ? tree.element("inheritance") : null;
        if (element != null) {
            AnnotationDescriptor ad = new AnnotationDescriptor(Inheritance.class);
            Attribute attr = element.attribute("strategy");
            InheritanceType strategy = InheritanceType.SINGLE_TABLE;
            if (attr != null) {
                String value = attr.getValue();
                if ("SINGLE_TABLE".equals(value)) {
                    strategy = InheritanceType.SINGLE_TABLE;
                } else if ("JOINED".equals(value)) {
                    strategy = InheritanceType.JOINED;
                } else if ("TABLE_PER_CLASS".equals(value)) {
                    strategy = InheritanceType.TABLE_PER_CLASS;
                } else {
                    throw new AnnotationException(
                            "Unknown InheritanceType in XML: " + value + " (" + SCHEMA_VALIDATION + ")");
                }
            }
            ad.setValue("strategy", strategy);
            return AnnotationFactory.create(ad);
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(Inheritance.class);
        } else {
            return null;
        }
    }

    private IdClass getIdClass(Element tree, XMLContext.Default defaults) {
        Element element = tree == null ? null : tree.element("id-class");
        if (element != null) {
            Attribute attr = element.attribute("class");
            if (attr != null) {
                AnnotationDescriptor ad = new AnnotationDescriptor(IdClass.class);
                Class clazz;
                try {
                    clazz = ReflectHelper.classForName(XMLContext.buildSafeClassName(attr.getValue(), defaults),
                            this.getClass());
                } catch (ClassNotFoundException e) {
                    throw new AnnotationException("Unable to find id-class: " + attr.getValue(), e);
                }
                ad.setValue("value", clazz);
                return AnnotationFactory.create(ad);
            } else {
                throw new AnnotationException("id-class without class. " + SCHEMA_VALIDATION);
            }
        } else if (defaults.canUseJavaAnnotations()) {
            return getPhysicalAnnotation(IdClass.class);
        } else {
            return null;
        }
    }

    /**
     * @param mergeWithAnnotations Whether to use Java annotations for this
     * element, if present and not disabled by the XMLContext defaults.
     * In some contexts (such as an association mapping) merging with
     * annotations is never allowed.
     */
    private PrimaryKeyJoinColumns getPrimaryKeyJoinColumns(Element element, XMLContext.Default defaults,
            boolean mergeWithAnnotations) {
        PrimaryKeyJoinColumn[] columns = buildPrimaryKeyJoinColumns(element);
        if (mergeWithAnnotations) {
            if (columns.length == 0 && defaults.canUseJavaAnnotations()) {
                PrimaryKeyJoinColumn annotation = getPhysicalAnnotation(PrimaryKeyJoinColumn.class);
                if (annotation != null) {
                    columns = new PrimaryKeyJoinColumn[] { annotation };
                } else {
                    PrimaryKeyJoinColumns annotations = getPhysicalAnnotation(PrimaryKeyJoinColumns.class);
                    columns = annotations != null ? annotations.value() : columns;
                }
            }
        }
        if (columns.length > 0) {
            AnnotationDescriptor ad = new AnnotationDescriptor(PrimaryKeyJoinColumns.class);
            ad.setValue("value", columns);
            return AnnotationFactory.create(ad);
        } else {
            return null;
        }
    }

    private Entity getEntity(Element tree, XMLContext.Default defaults) {
        if (tree == null) {
            return defaults.canUseJavaAnnotations() ? getPhysicalAnnotation(Entity.class) : null;
        } else {
            if ("entity".equals(tree.getName())) {
                AnnotationDescriptor entity = new AnnotationDescriptor(Entity.class);
                copyStringAttribute(entity, tree, "name", false);
                if (defaults.canUseJavaAnnotations() && StringHelper.isEmpty((String) entity.valueOf("name"))) {
                    Entity javaAnn = getPhysicalAnnotation(Entity.class);
                    if (javaAnn != null) {
                        entity.setValue("name", javaAnn.name());
                    }
                }
                return AnnotationFactory.create(entity);
            } else {
                return null; //this is not an entity
            }
        }
    }

    private MappedSuperclass getMappedSuperclass(Element tree, XMLContext.Default defaults) {
        if (tree == null) {
            return defaults.canUseJavaAnnotations() ? getPhysicalAnnotation(MappedSuperclass.class) : null;
        } else {
            if ("mapped-superclass".equals(tree.getName())) {
                AnnotationDescriptor entity = new AnnotationDescriptor(MappedSuperclass.class);
                return AnnotationFactory.create(entity);
            } else {
                return null; //this is not an entity
            }
        }
    }

    private Embeddable getEmbeddable(Element tree, XMLContext.Default defaults) {
        if (tree == null) {
            return defaults.canUseJavaAnnotations() ? getPhysicalAnnotation(Embeddable.class) : null;
        } else {
            if ("embeddable".equals(tree.getName())) {
                AnnotationDescriptor entity = new AnnotationDescriptor(Embeddable.class);
                return AnnotationFactory.create(entity);
            } else {
                return null; //this is not an entity
            }
        }
    }

    private Table getTable(Element tree, XMLContext.Default defaults) {
        Element subelement = tree == null ? null : tree.element("table");
        if (subelement == null) {
            //no element but might have some default or some annotation
            if (StringHelper.isNotEmpty(defaults.getCatalog()) || StringHelper.isNotEmpty(defaults.getSchema())) {
                AnnotationDescriptor annotation = new AnnotationDescriptor(Table.class);
                if (defaults.canUseJavaAnnotations()) {
                    Table table = getPhysicalAnnotation(Table.class);
                    if (table != null) {
                        annotation.setValue("name", table.name());
                        annotation.setValue("schema", table.schema());
                        annotation.setValue("catalog", table.catalog());
                        annotation.setValue("uniqueConstraints", table.uniqueConstraints());
                        annotation.setValue("indexes", table.indexes());
                    }
                }
                if (StringHelper.isEmpty((String) annotation.valueOf("schema"))
                        && StringHelper.isNotEmpty(defaults.getSchema())) {
                    annotation.setValue("schema", defaults.getSchema());
                }
                if (StringHelper.isEmpty((String) annotation.valueOf("catalog"))
                        && StringHelper.isNotEmpty(defaults.getCatalog())) {
                    annotation.setValue("catalog", defaults.getCatalog());
                }
                return AnnotationFactory.create(annotation);
            } else if (defaults.canUseJavaAnnotations()) {
                return getPhysicalAnnotation(Table.class);
            } else {
                return null;
            }
        } else {
            //ignore java annotation, an element is defined
            AnnotationDescriptor annotation = new AnnotationDescriptor(Table.class);
            copyStringAttribute(annotation, subelement, "name", false);
            copyStringAttribute(annotation, subelement, "catalog", false);
            if (StringHelper.isNotEmpty(defaults.getCatalog())
                    && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
                annotation.setValue("catalog", defaults.getCatalog());
            }
            copyStringAttribute(annotation, subelement, "schema", false);
            if (StringHelper.isNotEmpty(defaults.getSchema())
                    && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
                annotation.setValue("schema", defaults.getSchema());
            }
            buildUniqueConstraints(annotation, subelement);
            buildIndex(annotation, subelement);
            return AnnotationFactory.create(annotation);
        }
    }

    private SecondaryTables getSecondaryTables(Element tree, XMLContext.Default defaults) {
        List<Element> elements = tree == null ? new ArrayList<Element>()
                : (List<Element>) tree.elements("secondary-table");
        List<SecondaryTable> secondaryTables = new ArrayList<SecondaryTable>(3);
        for (Element element : elements) {
            AnnotationDescriptor annotation = new AnnotationDescriptor(SecondaryTable.class);
            copyStringAttribute(annotation, element, "name", false);
            copyStringAttribute(annotation, element, "catalog", false);
            if (StringHelper.isNotEmpty(defaults.getCatalog())
                    && StringHelper.isEmpty((String) annotation.valueOf("catalog"))) {
                annotation.setValue("catalog", defaults.getCatalog());
            }
            copyStringAttribute(annotation, element, "schema", false);
            if (StringHelper.isNotEmpty(defaults.getSchema())
                    && StringHelper.isEmpty((String) annotation.valueOf("schema"))) {
                annotation.setValue("schema", defaults.getSchema());
            }
            buildUniqueConstraints(annotation, element);
            buildIndex(annotation, element);
            annotation.setValue("pkJoinColumns", buildPrimaryKeyJoinColumns(element));
            secondaryTables.add((SecondaryTable) AnnotationFactory.create(annotation));
        }
        /*
         * You can't have both secondary table in XML and Java,
         * since there would be no way to "remove" a secondary table
         */
        if (secondaryTables.size() == 0 && defaults.canUseJavaAnnotations()) {
            SecondaryTable secTableAnn = getPhysicalAnnotation(SecondaryTable.class);
            overridesDefaultInSecondaryTable(secTableAnn, defaults, secondaryTables);
            SecondaryTables secTablesAnn = getPhysicalAnnotation(SecondaryTables.class);
            if (secTablesAnn != null) {
                for (SecondaryTable table : secTablesAnn.value()) {
                    overridesDefaultInSecondaryTable(table, defaults, secondaryTables);
                }
            }
        }
        if (secondaryTables.size() > 0) {
            AnnotationDescriptor descriptor = new AnnotationDescriptor(SecondaryTables.class);
            descriptor.setValue("value", secondaryTables.toArray(new SecondaryTable[secondaryTables.size()]));
            return AnnotationFactory.create(descriptor);
        } else {
            return null;
        }
    }

    private void overridesDefaultInSecondaryTable(SecondaryTable secTableAnn, XMLContext.Default defaults,
            List<SecondaryTable> secondaryTables) {
        if (secTableAnn != null) {
            //handle default values
            if (StringHelper.isNotEmpty(defaults.getCatalog()) || StringHelper.isNotEmpty(defaults.getSchema())) {
                AnnotationDescriptor annotation = new AnnotationDescriptor(SecondaryTable.class);
                annotation.setValue("name", secTableAnn.name());
                annotation.setValue("schema", secTableAnn.schema());
                annotation.setValue("catalog", secTableAnn.catalog());
                annotation.setValue("uniqueConstraints", secTableAnn.uniqueConstraints());
                annotation.setValue("pkJoinColumns", secTableAnn.pkJoinColumns());
                if (StringHelper.isEmpty((String) annotation.valueOf("schema"))
                        && StringHelper.isNotEmpty(defaults.getSchema())) {
                    annotation.setValue("schema", defaults.getSchema());
                }
                if (StringHelper.isEmpty((String) annotation.valueOf("catalog"))
                        && StringHelper.isNotEmpty(defaults.getCatalog())) {
                    annotation.setValue("catalog", defaults.getCatalog());
                }
                secondaryTables.add((SecondaryTable) AnnotationFactory.create(annotation));
            } else {
                secondaryTables.add(secTableAnn);
            }
        }
    }

    private static void buildIndex(AnnotationDescriptor annotation, Element element) {
        List indexElementList = element.elements("index");
        Index[] indexes = new Index[indexElementList.size()];
        for (int i = 0; i < indexElementList.size(); i++) {
            Element subelement = (Element) indexElementList.get(i);
            AnnotationDescriptor indexAnn = new AnnotationDescriptor(Index.class);
            copyStringAttribute(indexAnn, subelement, "name", false);
            copyStringAttribute(indexAnn, subelement, "column-list", true);
            copyBooleanAttribute(indexAnn, subelement, "unique");
            indexes[i] = AnnotationFactory.create(indexAnn);
        }
        annotation.setValue("indexes", indexes);
    }

    private static void buildUniqueConstraints(AnnotationDescriptor annotation, Element element) {
        List uniqueConstraintElementList = element.elements("unique-constraint");
        UniqueConstraint[] uniqueConstraints = new UniqueConstraint[uniqueConstraintElementList.size()];
        int ucIndex = 0;
        Iterator ucIt = uniqueConstraintElementList.listIterator();
        while (ucIt.hasNext()) {
            Element subelement = (Element) ucIt.next();
            List<Element> columnNamesElements = subelement.elements("column-name");
            String[] columnNames = new String[columnNamesElements.size()];
            int columnNameIndex = 0;
            Iterator it = columnNamesElements.listIterator();
            while (it.hasNext()) {
                Element columnNameElt = (Element) it.next();
                columnNames[columnNameIndex++] = columnNameElt.getTextTrim();
            }
            AnnotationDescriptor ucAnn = new AnnotationDescriptor(UniqueConstraint.class);
            copyStringAttribute(ucAnn, subelement, "name", false);
            ucAnn.setValue("columnNames", columnNames);
            uniqueConstraints[ucIndex++] = AnnotationFactory.create(ucAnn);
        }
        annotation.setValue("uniqueConstraints", uniqueConstraints);
    }

    private PrimaryKeyJoinColumn[] buildPrimaryKeyJoinColumns(Element element) {
        if (element == null) {
            return new PrimaryKeyJoinColumn[] {};
        }
        List pkJoinColumnElementList = element.elements("primary-key-join-column");
        PrimaryKeyJoinColumn[] pkJoinColumns = new PrimaryKeyJoinColumn[pkJoinColumnElementList.size()];
        int index = 0;
        Iterator pkIt = pkJoinColumnElementList.listIterator();
        while (pkIt.hasNext()) {
            Element subelement = (Element) pkIt.next();
            AnnotationDescriptor pkAnn = new AnnotationDescriptor(PrimaryKeyJoinColumn.class);
            copyStringAttribute(pkAnn, subelement, "name", false);
            copyStringAttribute(pkAnn, subelement, "referenced-column-name", false);
            copyStringAttribute(pkAnn, subelement, "column-definition", false);
            pkJoinColumns[index++] = AnnotationFactory.create(pkAnn);
        }
        return pkJoinColumns;
    }

    /**
     * Copy a string attribute from an XML element to an annotation descriptor. The name of the annotation attribute is
     * computed from the name of the XML attribute by {@link #getJavaAttributeNameFromXMLOne(String)}.
     *
     * @param annotation annotation descriptor where to copy to the attribute.
     * @param element XML element from where to copy the attribute.
     * @param attributeName name of the XML attribute to copy.
     * @param mandatory whether the attribute is mandatory.
     */
    private static void copyStringAttribute(final AnnotationDescriptor annotation, final Element element,
            final String attributeName, final boolean mandatory) {
        copyStringAttribute(annotation, element, getJavaAttributeNameFromXMLOne(attributeName), attributeName,
                mandatory);
    }

    /**
     * Copy a string attribute from an XML element to an annotation descriptor. The name of the annotation attribute is
     * explicitely given.
     *
     * @param annotation annotation where to copy to the attribute.
     * @param element XML element from where to copy the attribute.
     * @param annotationAttributeName name of the annotation attribute where to copy.
     * @param attributeName name of the XML attribute to copy.
     * @param mandatory whether the attribute is mandatory.
     */
    private static void copyStringAttribute(final AnnotationDescriptor annotation, final Element element,
            final String annotationAttributeName, final String attributeName, boolean mandatory) {
        String attribute = element.attributeValue(attributeName);
        if (attribute != null) {
            annotation.setValue(annotationAttributeName, attribute);
        } else {
            if (mandatory) {
                throw new AnnotationException(element.getName() + "." + attributeName
                        + " is mandatory in XML overriding. " + SCHEMA_VALIDATION);
            }
        }
    }

    private static void copyIntegerAttribute(AnnotationDescriptor annotation, Element element,
            String attributeName) {
        String attribute = element.attributeValue(attributeName);
        if (attribute != null) {
            String annotationAttributeName = getJavaAttributeNameFromXMLOne(attributeName);
            annotation.setValue(annotationAttributeName, attribute);
            try {
                int length = Integer.parseInt(attribute);
                annotation.setValue(annotationAttributeName, length);
            } catch (NumberFormatException e) {
                throw new AnnotationException(element.getPath() + attributeName + " not parseable: " + attribute
                        + " (" + SCHEMA_VALIDATION + ")");
            }
        }
    }

    private static String getJavaAttributeNameFromXMLOne(String attributeName) {
        StringBuilder annotationAttributeName = new StringBuilder(attributeName);
        int index = annotationAttributeName.indexOf(WORD_SEPARATOR);
        while (index != -1) {
            annotationAttributeName.deleteCharAt(index);
            annotationAttributeName.setCharAt(index, Character.toUpperCase(annotationAttributeName.charAt(index)));
            index = annotationAttributeName.indexOf(WORD_SEPARATOR);
        }
        return annotationAttributeName.toString();
    }

    private static void copyStringElement(Element element, AnnotationDescriptor ad, String annotationAttribute) {
        String discr = element.getTextTrim();
        ad.setValue(annotationAttribute, discr);
    }

    private static void copyBooleanAttribute(AnnotationDescriptor descriptor, Element element, String attribute) {
        String attributeValue = element.attributeValue(attribute);
        if (StringHelper.isNotEmpty(attributeValue)) {
            String javaAttribute = getJavaAttributeNameFromXMLOne(attribute);
            descriptor.setValue(javaAttribute, Boolean.parseBoolean(attributeValue));
        }
    }

    private <T extends Annotation> T getPhysicalAnnotation(Class<T> annotationType) {
        return element.getAnnotation(annotationType);
    }

    private <T extends Annotation> boolean isPhysicalAnnotationPresent(Class<T> annotationType) {
        return element.isAnnotationPresent(annotationType);
    }

    private Annotation[] getPhysicalAnnotations() {
        return element.getAnnotations();
    }
}