/*******************************************************************************
 * Copyright (c) 1998, 2014 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0
 * which accompanies this distribution.
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     05/16/2008-1.0M8 Guy Pelletier 
 *       - 218084: Implement metadata merging functionality between mapping files
 *     05/23/2008-1.0M8 Guy Pelletier 
 *       - 211330: Add attributes-complete support to the EclipseLink-ORM.XML Schema
 *     06/20/2008-1.0 Guy Pelletier 
 *       - 232975: Failure when attribute type is generic
 *     09/23/2008-1.1 Guy Pelletier 
 *       - 241651: JPA 2.0 Access Type support
 *     10/01/2008-1.1 Guy Pelletier 
 *       - 249329: To remain JPA 1.0 compliant, any new JPA 2.0 annotations should be referenced by name
 *     02/06/2009-2.0 Guy Pelletier 
 *       - 248293: JPA 2.0 Element Collections (part 2)
 *     03/27/2009-2.0 Guy Pelletier 
 *       - 241413: JPA 2.0 Add EclipseLink support for Map type attributes
 *     04/24/2009-2.0 Guy Pelletier 
 *       - 270011: JPA 2.0 MappedById support
 *     06/25/2009-2.0 Michael O'Brien 
 *       - 266912: change MappedSuperclass handling in stage2 to pre process accessors
 *          in support of the custom descriptors holding mappings required by the Metamodel
 *     03/08/2010-2.1 Guy Pelletier 
 *       - 303632: Add attribute-type for mapping attributes to EclipseLink-ORM
 *     08/11/2010-2.2 Guy Pelletier 
 *       - 312123: JPA: Validation error during Id processing on parameterized generic OneToOne Entity relationship from MappedSuperclass
 *     12/01/2010-2.2 Guy Pelletier 
 *       - 331234: xml-mapping-metadata-complete overriden by metadata-complete specification
 *     12/02/2010-2.2 Guy Pelletier 
 *       - 251554: ExcludeDefaultMapping annotation needed
 *     12/02/2010-2.2 Guy Pelletier 
 *       - 324471: Do not default to VariableOneToOneMapping for interfaces unless a managed class implementing it is found
 *     01/25/2011-2.3 Guy Pelletier 
 *       - 333488: Serializable attribute being defaulted to a variable one to one mapping and causing exception
 *     03/24/2011-2.3 Guy Pelletier 
 *       - 337323: Multi-tenant with shared schema support (part 1)
 *     07/16/2013-2.5.1 Guy Pelletier 
 *       - 412384: Applying Converter for parameterized basic-type for joda-time's DateTime does not work
 ******************************************************************************/
package org.eclipse.persistence.internal.jpa.metadata.accessors.objects;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.persistence.annotations.Array;
import org.eclipse.persistence.annotations.BasicCollection;
import org.eclipse.persistence.annotations.BasicMap;
import org.eclipse.persistence.annotations.ReadTransformer;
import org.eclipse.persistence.annotations.Structure;
import org.eclipse.persistence.annotations.TransientCompatibleAnnotations;
import org.eclipse.persistence.annotations.VariableOneToOne;
import org.eclipse.persistence.annotations.WriteTransformer;
import org.eclipse.persistence.annotations.WriteTransformers;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.jpa.metadata.MetadataDescriptor;
import org.eclipse.persistence.internal.jpa.metadata.MetadataLogger;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor;

import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ACCESS;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_BASIC;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ELEMENT_COLLECTION;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_EMBEDDED;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_EMBEDDED_ID;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_EMBEDDABLE;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ENUMERATED;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ID;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_LOB;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_MANY_TO_MANY;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_MANY_TO_ONE;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ONE_TO_MANY;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_ONE_TO_ONE;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_TEMPORAL;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_VERSION;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.ECLIPSELINK_OXM_PACKAGE_PREFIX;

/**
 * INTERNAL:
 * Parent object that is used to hold onto a valid JPA decorated method, field 
 * or class.
 * 
 * @author Guy Pelletier
 * @since EclipseLink 1.0
 */
@SuppressWarnings("deprecation")
public class MetadataAnnotatedElement extends MetadataAccessibleObject {
    public static final String DEFAULT_RAW_CLASS = "java.lang.String";
    
    /** The name of the element, i.e. class name, field name, method name. */
    private String m_name;
    
    /** Defines elements modifiers, i.e. private/static/transient. */
    private int m_modifiers;
    
    /** Used to cache the type metadata class, but cannot be cached in the case of generics. */
    private MetadataClass m_rawClass;
    
    /** Used to cache the full type metadata class including some generic specifications. */
    private MetadataClass m_rawClassWithGenerics;
    
    /**
     * Defines the generic types of the elements type.
     * This is null if no generics are used.
     * This is a list of class/type names from the class/field/method signature.
     * The size of the list varies depending on how many generics are present,
     * i.e.
     * - Map<String, Long> -> ["java.util.Map", "java.lang.String", "java.lang.Long"]
     * - Beverage<T> extends Object -> [T, :, java.lang.Object, java.lang.Object]
     */
    private List<String> m_genericType;
    
    /** Defines the field type, or method return type class name. */
    private String m_type;
    
    /** 
     * Used with the APT processor. Stores the PrimitiveType if this annotated
     * element is a primitive. We can't make it a primitive type here because
     * it introduces a JDK 1.6 dependency. The APT processor will need to cast
     * it back.
     */
    Object m_primitiveType;

    /** Defines the attribute name of a field, or property name of a method. */
    private String m_attributeName;
    
    /** Stores any annotations defined for the element, keyed by annotation name. */
    private Map<String, MetadataAnnotation> m_annotations; 
    
    /**
     * INTERNAL:
     */
    public MetadataAnnotatedElement(MetadataFactory factory) {
        super(factory);

        m_annotations = new HashMap<String, MetadataAnnotation>();
    }

    /**
     * INTERNAL:
     */
    public void addAnnotation(MetadataAnnotation annotation) {
        m_annotations.put(annotation.getName(), annotation);
    }
    
    /**
     * INTERNAL:
     */
    public void addGenericType(String genericType) {
        if (m_genericType == null) {
            m_genericType = new ArrayList<String>();
        }
        
        m_genericType.add(genericType);
    }
    
    /**
     * INTERNAL:
     */
    public boolean equals(Object object) {
        if (object == null) {
            return false;
        }
        
        if (getName() == null) {
            return ((MetadataAnnotatedElement)object).getName() == null;
        }
        
        return (object.getClass() == getClass()) && getName().equals(((MetadataAnnotatedElement)object).getName());
    }
    
    /**
     * INTERNAL:
     * Return the annotated element for this accessor. Note: This method does 
     * not check against a metadata complete.
     */
    public MetadataAnnotation getAnnotation(Class annotation) {
        return getAnnotation(annotation.getName());
    }
    
    /**
     * INTERNAL:
     * Return the annotated element for this accessor. Note: This method does 
     * not check against a metadata complete.
     */
    public MetadataAnnotation getAnnotation(String annotation) {
        if (m_annotations == null) {
            return null;
        }
        
        return m_annotations.get(annotation);
    }
    
    /**
     * INTERNAL:
     * Return the annotated element for this accessor.
     */
    public MetadataAnnotation getAnnotation(String annotationClassName, ClassAccessor classAccessor) {
        if (m_annotations != null) {
            MetadataAnnotation annotation = m_annotations.get(annotationClassName);
        
            if (annotation != null && classAccessor.ignoreAnnotations()) {
                getLogger().logConfigMessage(MetadataLogger.IGNORE_ANNOTATION, annotation, this);
            } else {
                return annotation;
            }
        }
        
        return null;
    }
    
    /**
     * INTERNAL:
     * Return the annotations of this accessible object.
     */
    public Map<String, MetadataAnnotation> getAnnotations(){
        return m_annotations;
    }
    
    /**
     * INTERNAL:
     */
    public String getAttributeName() {
        return m_attributeName;
    }
    
    /**
     * INTERNAL:
     */
    protected int getDeclaredAnnotationsCount(ClassAccessor classAccessor) {
        if (classAccessor.ignoreAnnotations() || m_annotations == null) {
            return 0;
        }
        
        return m_annotations.size(); 
    }
    
    /**
     * INTERNAL:
     */
    public List<String> getGenericType() {
        return m_genericType;
    }
    
    /**
     * INTERNAL:
     * This should only be called for accessor's of type Map. It will return
     * the map key type if generics are used, null otherwise.
     */
    public MetadataClass getMapKeyClass(MetadataDescriptor descriptor) {
        if (isGenericCollectionType()) {
            // The Map key may be a generic itself, or just the class value.
            String type = descriptor.getGenericType(m_genericType.get(2));
            if (type != null) {
                return getMetadataClass(type);
            }
            return getMetadataClass(m_genericType.get(1));
        } else {
            return null;
        }
    }
    
    /**
     * INTERNAL:
     */
    public int getModifiers() {
        return m_modifiers;
    }
    
    /**
     * INTERNAL:
     */
    public String getName() {
        return m_name;
    }
    
    /**
     * INTERNAL:
     * If this annotated element was built from java model elements and is
     * a primitive type, this primitive type will be set.
     */
    public Object getPrimitiveType() {
        return m_primitiveType;
    }
    
    /**
     * INTERNAL:
     * Return the raw class for this accessible object. E.g. For an 
     * accessible object with a type of java.util.Collection<Employee>, this 
     * method will return java.util.Collection. 
     * @see getReferenceClassFromGeneric() to get Employee.class back.
     * @see getRawClassWithGenerics() to get java.util.CollectionEmployee back.
     */
    public MetadataClass getRawClass(MetadataDescriptor descriptor) {
        if (m_rawClass == null) {
            if (isGenericType()) {
                // Seems that every generic annotated element has the T char 
                // in the 0 position. The actual generic type is therefore in
                // the 1 position.
                String type = descriptor.getGenericType(getGenericType().get(1));
                if (type == null) {
                    // If the generic type can not be resolved, take a stab 
                    // by returning a default class. One known case where this
                    // will hit is when processing generic accessors from a 
                    // mapped superclass for the internal meta model. I wonder
                    // if returning null here would be better? Forcing the  
                    // caller to have a plan B in place.
                    // @see e.g. RelationshipAccessor.getReferenceDescriptor()
                    return getMetadataClass(DEFAULT_RAW_CLASS);
                }
                return getMetadataClass(type);
            }
            return getMetadataClass(getType());
        }
        
        return m_rawClass;    
    }
    
    /**
     * INTERNAL:
     * Return the complete raw class with generics for this accessible object. 
     * E.g. For an accessible object with a type of 
     * java.util.Collection<Employee>, this method will return 
     * java.util.CollectionEmployee. Note, the generics are appended to the name
     * of the class. 
     * @see getReferenceClassFromGeneric() to get Employee.class back.
     * @see getRawClass() to get java.util.Collection back.
     */
    public MetadataClass getRawClassWithGenerics(MetadataDescriptor descriptor) {
        if (m_rawClassWithGenerics == null) {
            MetadataClass rawClass = getRawClass(descriptor);

            if (getGenericType() != null && (! getGenericType().isEmpty()) && getGenericType().size() > 1) {
                StringBuilder rawClassName = new StringBuilder(32);
                rawClassName.append(rawClass.getName());

                for (int i = 1; i < getGenericType().size(); i++) {
                    rawClassName.append(getGenericType().get(i));
                }

                m_rawClassWithGenerics = getMetadataClass(rawClassName.toString());
            } else {
                m_rawClassWithGenerics = rawClass;
            }
        }
        
        return m_rawClassWithGenerics;
    }
    
    /**
     * INTERNAL:
     * Return the reference class from the generic specification on this 
     * accessible object.
     * Here is what you will get back from this method given the following
     * scenarios:
     * 1 - public Collection<String> getTasks() => String.class
     * 2 - public Map<String, Integer> getTasks() => Integer.class
     * 3 - public Employee getEmployee() => null
     * 4 - public Collection getTasks() => null
     * 5 - public Map getTasks() => null
     * 6 - public Collection<byte[]> getAudio() => byte[].class
     * 7 - public Map<X,Y> on a MappedSuperclass where Y is defined in the Entity superclass<T> => Void.class (in all bug 266912 cases)
     */
    public MetadataClass getReferenceClassFromGeneric(MetadataDescriptor descriptor) {
        // TODO: investigate multiple levels of generics. 
        if (isGenericCollectionType()) {
            // TODO: This is guessing, need to be more logical.
            // Collection<String> -> [Collection, String], get element class.
            String elementClass = m_genericType.get(1);
            if (m_genericType.size() > 2) {
                MetadataClass collectionClass = getMetadataClass(m_genericType.get(0));
                if (collectionClass.extendsInterface(Map.class)) {
                    // If size is greater than 4 then assume it is a double generic Map,
                    // Map<T, Phone> -> [Map, T, Z, T, X]
                    if (m_genericType.size() > 4) {
                        elementClass = m_genericType.get(4);
                    } else if (m_genericType.size() == 4) {
                        // If size is greater than 3 then assume it is a generic Map,
                        // Map<T, Phone> -> [Map, T, Z, Phone]
                        elementClass = m_genericType.get(3);
                    } else if (m_genericType.size() == 3) {
                        // If size is greater than 2 then assume it is a Map,
                        // Map<String, Phone> -> [Map, String, Phone]
                        elementClass = m_genericType.get(2);
                    }
                } else if (elementClass.length() == 1) {
                    // Assume Collection with a generic,
                    // Collection<T> -> [Collection T Z]
                    elementClass = m_genericType.get(2);
                }
            }
            if (elementClass.length() == 1) {
                // Assume is a generic type variable, find real type.
                elementClass = descriptor.getGenericType(elementClass);
            }            
            MetadataClass metadataClass = getMetadataClass(elementClass);
            // 266912: We do not currently handle resolution of the parameterized 
            // generic type when the accessor is a MappedSuperclass elementClass 
            // will be null in this case so a lookup of the metadataClass will 
            // also return null on our custom descriptor
            if (metadataClass == null && descriptor.isMappedSuperclass()) {
                // default to Void for all use case 7 instances above
                return new MetadataClass(getMetadataFactory(), Void.class);
            } else {
                return metadataClass;
            }
        } else {
            return null;
        }
    }
    
    /**
     * INTERNAL:
     */
    public String getType() {
        return m_type;
    }
    
    /**
     * INTERNAL:
     * Return true if this accessible object has 1 or more declared 
     * persistence annotations.
     */
    public boolean hasDeclaredAnnotations(ClassAccessor classAccessor) {
        return getDeclaredAnnotationsCount(classAccessor) > 0;
    }
    
    /**
     * INTERNAL:
     */
    public int hashCode() {
        return getName().hashCode();
    }
    
    /**
     * INTERNAL:
     * Return true if this accessible object has 2 or more declared 
     * persistence annotations.
     */
    public boolean areAnnotationsCompatibleWithTransient(ClassAccessor classAccessor) {
        List<String> annotations = TransientCompatibleAnnotations.getTransientCompatibleAnnotations();
        for (String key: m_annotations.keySet()){
            if (!key.startsWith(ECLIPSELINK_OXM_PACKAGE_PREFIX) && !annotations.contains(key)){
                return false;
            }
        }
        return true;
    }
    
    /** 
     * INTERNAL:
     * Indicates whether the specified annotation is actually not present on 
     * this accessible object. Used for defaulting. Need this check since the
     * isAnnotationPresent calls can return a false when true because of the
     * meta-data complete feature.
     */
    public boolean isAnnotationNotPresent(Class annotation, ClassAccessor accessor) {
        return isAnnotationNotPresent(annotation.getName(), accessor);
    }
    
    /** 
     * INTERNAL:
     * Indicates whether the specified annotation is actually not present on 
     * this accessible object. Used for defaulting. Need this check since the
     * isAnnotationPresent calls can return a false when true because of the
     * meta-data complete feature.
     */
    public boolean isAnnotationNotPresent(String annotation, ClassAccessor accessor) {
        return ! isAnnotationPresent(annotation, accessor);
    }
    
    /** 
     * INTERNAL:
     * Indicates whether the specified annotation is present on java class
     * for the given descriptor metadata. 
     */
    public boolean isAnnotationPresent(Class annotationClass, ClassAccessor accessor) {
        return isAnnotationPresent(annotationClass.getName(), accessor);
    }
    
    /** 
     * INTERNAL:
     * Indicates whether the specified annotation is present on this accessible
     * object. NOTE: Calling this method directly does not take any metadata
     * complete flag into consideration. Look at the other isAnnotationPresent
     * methods that takes a class accessor. 
     */
    public boolean isAnnotationPresent(String annotation) {
        return getAnnotation(annotation) != null;
    }
    
    /** 
     * INTERNAL:
     * Indicates whether the specified annotation is present on java class
     * for the given descriptor metadata. 
     */
    public boolean isAnnotationPresent(String annotationName, ClassAccessor accessor) {
        MetadataAnnotation annotation = getAnnotation(annotationName);
        
        if (annotation != null && accessor.ignoreAnnotations()) {
            getLogger().logConfigMessage(MetadataLogger.IGNORE_ANNOTATION, annotation, this);
            return false;
        } else {
            return annotation != null;
        }
    }
    
    /**
     * INTERNAL:
     * Return true if this field accessor represents an array relationship.
     */
    public boolean isArray(ClassAccessor classAccessor) {
        return isAnnotationPresent(Array.class, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a basic mapping.
     */
    public boolean isBasic(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_BASIC, classAccessor) ||
               isAnnotationPresent(JPA_LOB, classAccessor) ||
               isAnnotationPresent(JPA_TEMPORAL, classAccessor) ||
               isAnnotationPresent(JPA_ENUMERATED, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a basic collection mapping.
     */
    public boolean isBasicCollection(ClassAccessor classAccessor) {
        return isAnnotationPresent(BasicCollection.class, classAccessor);
    }
    
    /**
     * INTERNAL: 
     * Return true if this accessor represents a basic collection mapping.
     */
    public boolean isBasicMap(ClassAccessor classAccessor) {
        return isAnnotationPresent(BasicMap.class, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an id mapping.
     */
    public boolean isDerivedId(ClassAccessor classAccessor) {
        return isId(classAccessor) && (isOneToOne(classAccessor) || isManyToOne(classAccessor));
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an id mapping.
     */
    public boolean isDerivedIdClass(ClassAccessor classAccessor) {
        return classAccessor.getDescriptor().isEmbeddable() && classAccessor.getProject().isIdClass(getRawClass(classAccessor.getDescriptor()));
    }
    
    /**
     * INTERNAL: 
     * Return true if this accessor represents an element collection mapping.
     */
    public boolean isElementCollection(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_ELEMENT_COLLECTION, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an aggregate mapping. True is
     * returned if an Embedded annotation is found or if an Embeddable 
     * annotation is found on the raw/reference class.
     */
    public boolean isEmbedded(ClassAccessor classAccessor) {
        if (isAnnotationNotPresent(JPA_EMBEDDED, classAccessor) && isAnnotationNotPresent(JPA_EMBEDDED_ID, classAccessor) && ! classAccessor.excludeDefaultMappings()) {
            MetadataClass rawClass = getRawClass(classAccessor.getDescriptor());
            return (rawClass.isAnnotationPresent(JPA_EMBEDDABLE) || classAccessor.getProject().hasEmbeddable(rawClass));
        } else {
            // Still need to make the call since we may need to ignore it
            // because of meta-data complete.
            return isAnnotationPresent(JPA_EMBEDDED, classAccessor);
        }
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an aggregate id mapping.
     */
    public boolean isEmbeddedId(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_EMBEDDED_ID, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Method to return whether a collection type is a generic.
     */
    public boolean isGenericCollectionType() {
        return (m_genericType != null) && (m_genericType.size() > 1);
    }
    
    /**
     * INTERNAL:
     * Method to return whether a type is a generic.
     */
    public boolean isGenericType() {
        return (m_genericType != null) && (m_genericType.size() > 1) && (m_genericType.get(0).length() == 1);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents an id mapping.
     */
    public boolean isId(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_ID, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this field accessor represents a m-m relationship.
     */
    public boolean isManyToMany(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_MANY_TO_MANY, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a m-1 relationship.
     */
    public boolean isManyToOne(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_MANY_TO_ONE, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a 1-m relationship.
     */
    public boolean isOneToMany(ClassAccessor classAccessor) {
        if (isAnnotationNotPresent(JPA_ONE_TO_MANY, classAccessor) && ! classAccessor.excludeDefaultMappings()) {
            if (isGenericCollectionType() && isSupportedToManyCollectionClass(getRawClass(classAccessor.getDescriptor())) && classAccessor.getProject().hasEntity(getReferenceClassFromGeneric(classAccessor.getDescriptor()))) {
                getLogger().logConfigMessage(MetadataLogger.ONE_TO_MANY_MAPPING, this);
                return true;
            }
            
            return false;
        } else {
            // Still need to make the call since we may need to ignore it
            // because of meta-data complete.
            return isAnnotationPresent(JPA_ONE_TO_MANY, classAccessor);
        }
    }
    
    /**
     * INTERNAL: 
     * Return true if this accessor represents a 1-1 relationship.
     */
    public boolean isOneToOne(ClassAccessor classAccessor) {
        if (isAnnotationNotPresent(JPA_ONE_TO_ONE, classAccessor) && ! classAccessor.excludeDefaultMappings()) {    
            if (classAccessor.getProject().hasEntity(getRawClass(classAccessor.getDescriptor())) && ! isEmbedded(classAccessor)) {
                getLogger().logConfigMessage(MetadataLogger.ONE_TO_ONE_MAPPING, this);
                return true;
            } else {
                return false;
            }
        } else {
            // Still need to make the call since we may need to ignore it
            // because of meta-data complete.
            return isAnnotationPresent(JPA_ONE_TO_ONE, classAccessor);
        }
    }
    
    /**
     * INTERNAL:
     * Return true if this field accessor represents a structure relationship.
     */
    public boolean isStructure(ClassAccessor classAccessor) {
        return isAnnotationPresent(Structure.class, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Method to return whether the given class is a supported collection class.
     */
    public boolean isSupportedCollectionClass(MetadataClass metadataClass) {
        return metadataClass.isCollection();
    }
    
    /**
     * INTERNAL:
     * Method to return whether the given class is a supported map class. 
     */
    public boolean isSupportedMapClass(MetadataClass metadataClass) {
        return metadataClass.isMap(); 
    }
    
    /**
     * INTERNAL:
     * Method to return whether the given class is a supported to many 
     * collection class.  
     */
    public boolean isSupportedToManyCollectionClass(MetadataClass metadataClass) {
        return isSupportedCollectionClass(metadataClass) || isSupportedMapClass(metadataClass);
    }

    /**
     * INTERNAL:
     * Return true if this accessor represents an transformation mapping.
     */
    public boolean isTransformation(ClassAccessor classAccessor) {
        return isAnnotationPresent(ReadTransformer.class, classAccessor) ||
               isAnnotationPresent(WriteTransformers.class, classAccessor) ||
               isAnnotationPresent(WriteTransformer.class, classAccessor);
    }
    
    /**
     * INTERNAL:
     * When processing the inverse accessors to an explicit access setting,
     * their must be an Access(FIELD) or Access(PROPERTY) present for the
     * element to be processed. Otherwise, it is ignored.
     */
    protected boolean isValidPersistenceElement(boolean mustBeExplicit, String explicitType, ClassAccessor classAccessor) {
        if (mustBeExplicit) {
            MetadataAnnotation annotation = getAnnotation(JPA_ACCESS, classAccessor);
            
            if (annotation == null) {
                return false;
            } else {
                String access = annotation.getAttributeString("value");
                
                if (! access.equals(explicitType)) {
                    throw ValidationException.invalidExplicitAccessTypeSpecified(this, classAccessor.getDescriptorJavaClass(), explicitType);
                }
            }
        }
        
        return true;
    }
    
    /**
     * INTERNAL:
     * Return true if the modifiers are not transient, static or abstract.
     */
    protected boolean isValidPersistenceElement(int modifiers) {
        return ! (Modifier.isTransient(modifiers) || Modifier.isStatic(modifiers) || Modifier.isAbstract(modifiers));
    }
    
    /**
     * INTERNAL:
     * Return true if this accessor represents a variable 1-1 relationship.
     * The method will return true if one of the following conditions is met:
     *  - There is a VariableOneToOne annotation present, or
     *  - The raw class is an interface and not a collection or map, nor a 
     *    ValueHolderInterface (and an exclude default mappings flag is not set)
     */
    public boolean isVariableOneToOne(ClassAccessor classAccessor) {
        if (isAnnotationNotPresent(VariableOneToOne.class, classAccessor) && ! classAccessor.excludeDefaultMappings()) {
            MetadataClass rawClass = getRawClass(classAccessor.getDescriptor());
            
            if (rawClass.isInterface() && 
                    ! rawClass.isMap() && 
                    ! rawClass.isCollection() &&
                    ! rawClass.isSerializableInterface() &&
                    ! rawClass.extendsInterface(ValueHolderInterface.class) &&
                    classAccessor.getProject().hasEntityThatImplementsInterface(rawClass.getName())) {
                getLogger().logConfigMessage(MetadataLogger.VARIABLE_ONE_TO_ONE_MAPPING, this);
                return true;
            }
            
            return false;
        } else {
            // Still need to make the call since we may need to ignore it
            // because of meta-data complete.
            return isAnnotationPresent(VariableOneToOne.class, classAccessor);
        }
    }
    
    /**
     * INTERNAL: 
     * Return true if this accessor represents a version mapping.
     */
    public boolean isVersion(ClassAccessor classAccessor) {
        return isAnnotationPresent(JPA_VERSION, classAccessor);
    }
    
    /**
     * INTERNAL:
     * Set the annotations of this accessible object.
     */
    public void setAnnotations(Map<String, MetadataAnnotation> annotations) {
        m_annotations = annotations;
    }
    
    /**
     * INTERNAL:
     */
    public void setAttributeName(String attributeName) {
        m_attributeName = attributeName;
    }
    
    /**
     * INTERNAL:
     */
    public void setGenericType(List<String> genericType) {
        m_genericType = genericType;
    }
    
    /**
     * INTERNAL:
     */
    public void setModifiers(int modifiers) {
        m_modifiers = modifiers;
    }
    
    /**
     * INTERNAL:
     */
    public void setName(String name) {
        m_name = name;
    }
    
    /**
     * INTERNAL:
     * If this annotated element was built from java model elements and is
     * a primitive type this method will be called.
     */
    public void setPrimitiveType(Object primitiveType) {
        m_primitiveType = primitiveType;
        m_type = primitiveType.toString();
    }
    
    /**
     * INTERNAL: 
     */
    public void setType(String type) {
        m_type = type;
    }
    
    /**
     * INTERNAL:
     */
    public String toString() {
        String className = getClass().getSimpleName();
        return className.substring("Metadata".length(), className.length()).toLowerCase() + " " + getName();
    }
}
