/*******************************************************************************
 * Copyright (c) 1998, 2013 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:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/  
package org.eclipse.persistence.internal.jpa.weaving;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.persistence.internal.descriptors.VirtualAttributeMethodInfo;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass;

/**
 * Internal helper class that holds details of a persistent class.
 * Used by {@link PersistenceWeaver}
 */

public class ClassDetails {
    
    protected MetadataClass describedClass;    
    /** Name of this class. */
    protected String className;
    /** Superclass' name. */
    protected String superClassName;
    /** Superclass' ClassDetails - only populated if superclass is also persistent. */
    protected ClassDetails superClassDetails;
    /** Define if lazy value holders should be weaved in this class. */
    protected boolean shouldWeaveValueHolders = false;
    /** Define if change tracking should be weaved in this class. */    
    protected boolean shouldWeaveChangeTracking = false;
    /** Define if fetch groups should be weaved in this class. */    
    protected boolean shouldWeaveFetchGroups = false;
    /** Define if internal optimizations should be weaved in this class. */    
    protected boolean shouldWeaveInternal = false;
    /** Define if this class should be weaved for our REST support */
    protected boolean shouldWeaveRest = false;
    /** Map of this class' persistent attributes where the key is the Attribute name. */
    protected Map<String, AttributeDetails> attributesMap;
    /** Map of this class' persistent get methods where the key is the getMethod name. */
    protected Map<String, AttributeDetails> getterMethodToAttributeDetails;
    /** Map of this class' persistent set methods where the key is the setMethod name. */    
    protected Map<String, AttributeDetails> setterMethodToAttributeDetails;
    /** Determine if a JPA "mapped superclass". */
    protected boolean isMappedSuperClass = false;
    /** Determine if a JPA "embedable" (aggregate). */
    protected boolean isEmbedable = false;
    /** Determine if class uses attribute access, lazily initialized. */
    protected boolean usesAttributeAccess = false;
    /** Determine if this class specifically implements a clone method */
    protected boolean implementsCloneMethod = false;
    /** Determine if a new constructor can be used to bypass setting variables to default values. */
    protected boolean shouldWeaveConstructorOptimization = true;
    /** The methods that are used by virtual attributes as getter methods.  
     * These will be used by our weaver to properly weave those methods 
     * This list should be kept in sync with virtualSetMethodNames. Every time
     * a value is added, one should be added to virtualSetMethodNames so that at
     * a particular index, the virtualGetMethodName and the virtualSetMethodCoorespond*/
    protected List<VirtualAttributeMethodInfo> virtualAccessMethods = null;

    
    public ClassDetails() {
        virtualAccessMethods = new ArrayList<VirtualAttributeMethodInfo>();
    }
    
    public MetadataClass getDescribedClass(){
        return describedClass;
    }
    
    public String getClassName() {
        return className;
    }
    
    public void setDescribedClass(MetadataClass describedClass){
        this.describedClass = describedClass;
    }
    
    public void setClassName(String className) {
        this.className = className;
    }

    public String getSuperClassName() {
        return superClassName;
    }
    
    public void setSuperClassName(String superClassName) {
        this.superClassName = superClassName;
    }

    public ClassDetails getSuperClassDetails() {
        return superClassDetails;
    }
    
    public void setSuperClassDetails(ClassDetails superClassDetails) {
        this.superClassDetails = superClassDetails;
    }

    public boolean shouldWeaveValueHolders() {
        return shouldWeaveValueHolders;
    }
    
    public void setShouldWeaveValueHolders(boolean shouldWeaveValueHolders) {
        this.shouldWeaveValueHolders = shouldWeaveValueHolders;
    }
    
    public boolean shouldWeaveChangeTracking() {
        return shouldWeaveChangeTracking;
    }
    
    public void setShouldWeaveChangeTracking(boolean shouldWeaveChangeTracking) {
        this.shouldWeaveChangeTracking = shouldWeaveChangeTracking;
    }
    
    public void setShouldWeaveConstructorOptimization(boolean shouldWeaveConstructorOptimization) {
        this.shouldWeaveConstructorOptimization = shouldWeaveConstructorOptimization;
    }
    
    public boolean shouldWeaveFetchGroups() {
        return shouldWeaveFetchGroups;
    }
    
    public void setShouldWeaveFetchGroups(boolean shouldWeaveFetchGroups) {
        this.shouldWeaveFetchGroups = shouldWeaveFetchGroups;
    }
    
    public boolean shouldWeaveInternal() {
        return shouldWeaveInternal;
    }
    
    public void setShouldWeaveInternal(boolean shouldWeaveInternal) {
        this.shouldWeaveInternal = shouldWeaveInternal;
    }
    
    public boolean shouldWeaveREST(){
        return shouldWeaveRest;
    }
        
    public void setShouldWeaveREST(boolean shouldWeaveRest){
        this.shouldWeaveRest = shouldWeaveRest;
    }
    
    public Map<String, AttributeDetails> getAttributesMap() {
        return attributesMap;
    }

    public Map<String, AttributeDetails> getGetterMethodToAttributeDetails(){
        return getterMethodToAttributeDetails;
    }
    
    public Map<String, AttributeDetails> getSetterMethodToAttributeDetails(){
        return setterMethodToAttributeDetails;
    }
    
    public void setAttributesMap(Map<String, AttributeDetails> attributesMap) {
        this.attributesMap = attributesMap;
    }
    
    public void setGetterMethodToAttributeDetails(Map<String, AttributeDetails> map){
        this.getterMethodToAttributeDetails = map;
    }
    
    public boolean getImplementsCloneMethod(){
        return implementsCloneMethod;
    }
    
    /**
     * INTERNAL:
     * Search the list of virtualAccessMethods for a VirtualAttributeMethodInfo with the given
     * getMethodName.  Return the VirtualAttributeMethodInfo if there is one, else return null
     * @param getMethodName
     * @return
     */
    public VirtualAttributeMethodInfo getInfoForVirtualGetMethod(String getMethodName){
        Iterator<VirtualAttributeMethodInfo> i = virtualAccessMethods.iterator();
        while (i.hasNext()){
            VirtualAttributeMethodInfo info = i.next();
            if (info.getGetMethodName() != null && info.getGetMethodName().equals(getMethodName)){
                return info;
            }
        }
        return null;
    }
    
    /**
     * INTERNAL:
     * Search the list of virtualAccessMethods for a VirtualAttributeMethodInfo with the given
     * setMethodName.  Return the VirtualAttributeMethodInfo if there is one, else return null
     * @param getMethodName
     * @return
     */
    public VirtualAttributeMethodInfo getInfoForVirtualSetMethod(String setMethodName){
        Iterator<VirtualAttributeMethodInfo> i = virtualAccessMethods.iterator();
        while (i.hasNext()){
            VirtualAttributeMethodInfo info = i.next();
            if (info.getGetMethodName() != null && info.getGetMethodName().equals(setMethodName)){
                return info;
            }
        }
        return null;
    }
    
    public void setImplementsCloneMethod(boolean implementsCloneMethod){
        this.implementsCloneMethod = implementsCloneMethod;
    }
    
    /**
     * Return the name of the most direct superclass that has a direct implementation of 
     * a clone method.
     * If there is not one, return null
     * @return
     */
    public String getNameOfSuperclassImplementingCloneMethod(){
        if (superClassDetails == null){
            return null;
        } else if (superClassDetails.getImplementsCloneMethod()){
            return superClassDetails.getClassName();
        } else {
            return superClassDetails.getNameOfSuperclassImplementingCloneMethod();
        }   
    }
    
    public List<VirtualAttributeMethodInfo> getVirtualAccessMethods() {
        return virtualAccessMethods;
    }

    public void setVirtualAccessMethods(List<VirtualAttributeMethodInfo> virtualAccessMethods) {
        this.virtualAccessMethods = virtualAccessMethods;
    }
    
    public boolean isMappedSuperClass(){
        return isMappedSuperClass;
    }
    
    public void setIsMappedSuperClass(boolean isMappedSuperClass){
        this.isMappedSuperClass = isMappedSuperClass;
    }
    
    public boolean isEmbedable(){
        return isEmbedable;
    }
    
    public void setIsEmbedable(boolean isEmbedable){
        this.isEmbedable = isEmbedable;
    }
    
    public void setSetterMethodToAttributeDetails(Map map){
        this.setterMethodToAttributeDetails = map;
    }
    
    /**
     * If one attribute of this class uses attribute access, by the JPA specification, all
     * attributes must use attribute access
     * 
     * This method assumes it is called when this class details is completely initialized.
     */
    public boolean usesAttributeAccess() {
        return usesAttributeAccess;
    }
    
    public void useAttributeAccess(){
        usesAttributeAccess = true;
    }
    
    public AttributeDetails getAttributeDetailsFromClassOrSuperClass(String attributeName){
        AttributeDetails attribute = attributesMap.get(attributeName);
        if (attribute == null && superClassDetails != null) {
            return superClassDetails.getAttributeDetailsFromClassOrSuperClass(attributeName);
        }
       return attribute; 
    }
    
    public boolean doesSuperclassWeaveChangeTracking(){
        if (getSuperClassDetails() == null){
            return false;
        }
        if (getSuperClassDetails().shouldWeaveChangeTracking()) {
            return true;
        }
        
        return getSuperClassDetails().doesSuperclassWeaveChangeTracking();
    }
    
    public boolean canWeaveChangeTracking(){
        if ((getSuperClassDetails() == null) || (!shouldWeaveChangeTracking())) {
            return shouldWeaveChangeTracking();
        }
        
        return getSuperClassDetails().canWeaveChangeTracking();
    }
    
    /**   
     * Returns true if 
     * Used with field access, and is set to false if transient variables are discovered
     */
    public boolean canWeaveConstructorOptimization(){
        if (!shouldWeaveConstructorOptimization || (getSuperClassDetails() == null)) {
            return shouldWeaveConstructorOptimization;
        }
        return getSuperClassDetails().canWeaveConstructorOptimization();
    }

    /**
     * Returns true if the given class name represents this class, or any
     * superclass that can be navigated to by recursively navigating up the
     * structure of superClassDetails stored in this class.
     * 
     * Assume java.lang.Object is in the hierarchy
     *
     * @param className
     * @return
     */
    public boolean isInMetadataHierarchy(String className){
        if (className.equals(Object.class.getName().replace('.', '/'))){
            return true;
        }            
        if (className.equals(this.className) || (superClassName != null && className.equals(superClassName))){
            return true;
        }
        if (superClassDetails != null){
            return superClassDetails.isInMetadataHierarchy(className);
        }
        return false;
    }
    
    /**
     * Returns true if the given class name represents this class, or any
     * superclass that can be navigated to by recursively navigating up the
     * structure of superClassDetails stored in this class.
     * 
     * Assume java.lang.Object is in the hierarchy
     *
     * @param className
     * @return
     */
    public boolean isInSuperclassHierarchy(String className){
        if (className.equals(Object.class.getName().replace('.', '/'))){
            return true;
        }            
        if (superClassName != null && className.equals(superClassName)){
            return true;
        }
        if (superClassDetails != null){
            return superClassDetails.isInMetadataHierarchy(className);
        }
        return false;
    }
    
}
