Package org.hibernate.cfg.annotations.reflection

Source Code of org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader

/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2011 by Red Hat Inc and/or its affiliates or by
* third-party contributors as indicated by either @author tags or express
* copyright attribution statements applied by the authors.  All
* third-party contributions are distributed under license by Red Hat Inc.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA  02110-1301  USA
*/
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.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", 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() ) );
        }
        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;
  }

  private static void copyStringAttribute(
      AnnotationDescriptor annotation, Element element, String attributeName, boolean mandatory
  ) {
    String attribute = element.attributeValue( attributeName );
    if ( attribute != null ) {
      String annotationAttributeName = getJavaAttributeNameFromXMLOne( attributeName );
      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();
  }
}
TOP

Related Classes of org.hibernate.cfg.annotations.reflection.JPAOverriddenAnnotationReader

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.