/*******************************************************************************
 * 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:
 *     Oracle - initial API and implementation from Oracle TopLink
 ******************************************************************************/  
package org.eclipse.persistence.sdo;

import commonj.sdo.DataObject;
import commonj.sdo.Property;
import commonj.sdo.Type;
import commonj.sdo.helper.HelperContext;
import commonj.sdo.impl.HelperProvider;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.eclipse.persistence.sdo.helper.SDOClassLoader;
import org.eclipse.persistence.sdo.helper.SDOHelperContext;
import org.eclipse.persistence.sdo.helper.SDOTypeHelper;
import org.eclipse.persistence.sdo.helper.SDOXMLHelper;
import org.eclipse.persistence.sdo.helper.SDOXSDHelper;
import org.eclipse.persistence.sdo.helper.extension.SDOUtil;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.SDOException;
import org.eclipse.persistence.internal.descriptors.InstantiationPolicy;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.oxm.Namespace;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.logging.AbstractSessionLog;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.oxm.NamespaceResolver;
import org.eclipse.persistence.oxm.XMLConstants;
import org.eclipse.persistence.oxm.XMLDescriptor;
import org.eclipse.persistence.oxm.XMLField;
import org.eclipse.persistence.oxm.mappings.XMLAnyAttributeMapping;
import org.eclipse.persistence.oxm.mappings.XMLAnyCollectionMapping;
import org.eclipse.persistence.oxm.mappings.XMLDirectMapping;
import org.eclipse.persistence.oxm.schema.XMLSchemaClassPathReference;
import org.eclipse.persistence.oxm.schema.XMLSchemaReference;

public class SDOType implements Type, Serializable {
    private static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; 

    private QName qName;
    private boolean open;// if this Type is open
    private boolean isAbstract;// if this is an abstract Type
    protected boolean isDataType;// if this is a dataType
    private List baseTypes;// a list of this Type's base Types (added for now waitting for final decision)
    private List declaredProperties;// a list of Properties defined by this Type.(Optional, leave it for now)
    private transient Map declaredPropertiesMap;
    private List aliasNames;
    private boolean xsd;
    private boolean xsdList;
    private String xsdLocalName;
    private QName xsdType;
    protected transient XMLDescriptor xmlDescriptor;
    private Map propertyValues;
    private SDOProperty changeSummaryProperty;
    private List allProperties;
    private SDOProperty[] allPropertiesArr;
    private List subTypes;
    private boolean finalized;
    private Class javaClass;
    private String javaClassName;
    private String javaImplClassName;
    protected Class javaImplClass;
    private List<SDOProperty> nonFinalizedReferencingProps;
    private List nonFinalizedMappingURIs;

    /** hold a wrapper object for primitive numeric defaults */
    private Object pseudoDefault;

    // hold the context containing all helpers so that we can preserve inter-helper relationships
    protected HelperContext aHelperContext;
    private List appInfoElements;
    private Map appInfoMap;

    /** Open Content setOpen constants */
    private static final String ANY_MAPPING_ATTRIBUTE_NAME = "openContentProperties";
    private static final String ANY_MAPPING_GET_METHOD_NAME = "_getOpenContentPropertiesWithXMLRoots";
    private static final String ANY_MAPPING_SET_METHOD_NAME = "_setOpenContentPropertiesWithXMLRoots";
    private static final String SDO_REF_MAPPING_ATTRIBUTE_NAME = "sdoRef";

    public SDOType(HelperContext helperContext) {
        this((SDOTypeHelper)helperContext.getTypeHelper());
    }

    public SDOType(SDOTypeHelper sdoTypeHelper) {
        this(sdoTypeHelper, new XMLDescriptor());
        xmlDescriptor.setLazilyInitialized(true);
    }

    protected SDOType(SDOTypeHelper sdoTypeHelper, XMLDescriptor xmlDescriptor) {
        this.xmlDescriptor = xmlDescriptor;
        if(null != sdoTypeHelper) {
            aHelperContext = sdoTypeHelper.getHelperContext();
            if(null != xmlDescriptor) {
                this.xmlDescriptor.setNamespaceResolver(sdoTypeHelper.getNamespaceResolver());
            }
        }
    }

    /**
     * INTERNAL:
     * Build up a Type with given name and uri and a default static HelperContext.
     * Use  {@link #SDOType(String, String, org.eclipse.persistence.sdo.helper.SDOTypeHelper)} instead
     * @param uri           the URI of this type
     * @param type_name     the unique of this Type
     */
    public SDOType(String uri, String type_name) {
        // JIRA129 - default to static global context - Do Not use this convenience constructor outside of JUnit testing
        this(uri, type_name, (SDOTypeHelper) HelperProvider.getDefaultContext().getTypeHelper());
    }

    /**
     * Build up a Type with given name and uri
     * @param uri           the URI of this type
     * @param name          the unique of this Type
     * @param sdoTypeHelper the current HelperContext
     */
    public SDOType(String uri, String name, SDOTypeHelper sdoTypeHelper) {
        this(sdoTypeHelper);
        if(null != uri) {
            uri = uri.intern();
        }
        if(null != name) {
            name = name.intern();
        }
        this.qName = new QName(uri, name);
    }

    protected SDOType(String uri, String name, SDOTypeHelper sdoTypeHelper, XMLDescriptor xmlDescriptor) {
        this(sdoTypeHelper, xmlDescriptor);
        if(null != uri) {
            uri = uri.intern();
        }
        if(null != name) {
            name = name.intern();
        }
        this.qName = new QName(uri, name);
    }

    public QName getQName() {
        return qName;
    }

    public void setQName(QName qName) {
        this.qName = qName;
    }

    public String getName() {
        return qName.getLocalPart();
    }

    public String getURI() {
        String uri = qName.getNamespaceURI();
        if("".equals(uri)) {
            return null;
        } else {
            return uri;
        }
    }

    public Class getInstanceClass() {
        if ((javaClass == null) && (javaClassName != null)) {
            try {
                SDOClassLoader loader = ((SDOXMLHelper)aHelperContext.getXMLHelper()).getLoader();
                Class clazz = loader.getParent().loadClass(javaClassName);
                if(isValidInstanceClass(clazz)) {
                    javaClass = clazz;
                } else {
                    javaClass = getClass();
                }
            } catch (ClassNotFoundException e) {
                javaClass = getClass();
            } catch (SecurityException e) {
                throw SDOException.classNotFound(e, getURI(), getName());
            }
        }
        if(javaClass == getClass()) {
            return null;
        }
        return javaClass;
    }

    /**
     * Verify that the class is a valid instance class. 
     */
    private boolean isValidInstanceClass(Class clazz) {
        if(isDataType) {
            return true;
        }
        if(!clazz.isInterface()) {
            return false;
        }
        return !isStrictTypeCheckingEnabled() || hasClassGetterForEachProperty(clazz);
    }
    
    /**
     * Checks that the given class has getter for each property of this type.
     * 
     * @param clazz class to be checked
     * @return {@code true} if given class has getter for each property
     */
    private boolean hasClassGetterForEachProperty(Class clazz) {
        for(Object object: this.getDeclaredProperties()) {
            SDOProperty sdoProperty = (SDOProperty) object;
            SDOType sdoPropertyType = sdoProperty.getType();
            if(!sdoPropertyType.isChangeSummaryType()) {
                String javaType = SDOUtil.getJavaTypeForProperty(sdoProperty);
                try {
                    // Verify get method
                    String getMethodName = SDOUtil.getMethodName(sdoProperty.getName(), javaType);
                    PrivilegedAccessHelper.getPublicMethod(clazz, getMethodName, EMPTY_CLASS_ARRAY, false);
                } catch(NoSuchMethodException e) {
                	//if the method isn't found and the type is boolean try looking for a "get" method instead of an "is" method
                	if(sdoPropertyType == SDOConstants.SDO_BOOLEAN || sdoPropertyType == SDOConstants.SDO_BOOLEANOBJECT ){
                        try{
                        	String booleanGetterMethodName = SDOUtil.getBooleanGetMethodName(sdoProperty.getName(), javaType);
                            PrivilegedAccessHelper.getPublicMethod(clazz, booleanGetterMethodName, EMPTY_CLASS_ARRAY, false);
                        } catch(NoSuchMethodException e2) {
                            return false;
                        }                    
                	}else{
                        return false;
                	}
                }
            }
        } 
        return true;
    }
    
    /**
     * Indicates whether to check instance class strictly.
     * 
     * @return boolean
     */
    private boolean isStrictTypeCheckingEnabled() {
        final HelperContext helperContext = getHelperContext();
        if (helperContext instanceof SDOHelperContext) {
            return ((SDOHelperContext)helperContext).isStrictTypeCheckingEnabled();
        }
        return true;
    }

    public boolean isInstance(Object object) {
        if ((!isDataType()) && (object instanceof DataObject)) {
            Type doType = ((DataObject)object).getType();
            if (doType != null) {
                return doType.equals(this);
            }
        }

        //this check is taken from page 77 of the spec
        Class instanceClass = getInstanceClass();
        if (instanceClass != null) {
            return instanceClass.isInstance(object);
        }

        return false;
    }

    public List getProperties() {
        if (allProperties == null) {
            allProperties = new ArrayList();
        }
        return allProperties;
    }

    public SDOProperty getProperty(String propertyName) {
        SDOProperty queriedProperty = (SDOProperty)getDeclaredPropertiesMap().get(propertyName);
        if (null == queriedProperty && isSubType()) {
            for (int i = 0; i < getBaseTypes().size(); i++) {
                queriedProperty = ((SDOType)getBaseTypes().get(i)).getProperty(propertyName);
                if (queriedProperty != null) {
                    break;
                }
            }
        }
        return queriedProperty;
    }

    public boolean isDataType() {
        return isDataType;
    }

    public boolean isOpen() {
        return open;
    }

    public boolean isSequenced() {
        return xmlDescriptor.isSequencedObject();
    }

    public boolean isAbstract() {
        return isAbstract;
    }

    public List getBaseTypes() {
        if (baseTypes == null) {
            baseTypes = new ArrayList();
        }
        return baseTypes;
    }

    /**
     * INTERNAL:
     * Provide a means to determine if this type has base types without causing the base types property to be initialized.
     */
    public boolean isSubType() {
        return !(null == baseTypes || baseTypes.isEmpty());
    }

    public List getDeclaredProperties() {
        if (declaredProperties == null) {
            declaredProperties = new ArrayList();
        }
        return declaredProperties;
    }

    public List getAliasNames() {
        if (aliasNames == null) {
            aliasNames = new ArrayList();
        }
        return aliasNames;
    }

    /**
     * INTERNAL:
     * Assign a unique string to a type, which belongs to same uri, among types.
     * @param name  a unique string representing a type.
     */
    public void addAliasName(String name) {
        getAliasNames().add(name);
        QName qname = new QName(getURI(), name);
        ((SDOTypeHelper)aHelperContext.getTypeHelper()).getTypesHashMap().put(qname, this);
    }

    /**
     * INTERNAL:
     * Assign a list of alias names to a type, which are unique in URI.
     * @param names  a unique string representing a type.
     */
    public void setAliasNames(List names) {
        for (int i = 0; i < names.size(); i++) {
            addAliasName((String)names.get(i));
        }
    }

    /**
     * INTERNAL:
     * Make this Type an opened Type to allow open content by assigning true value
     * or a Type not to accept any additional properties by assigning false value,
     * {@link #isOpen()}.
     * @param bOpen  boolean value implying if this Type is open
     */
    public void setOpen(boolean bOpen) {
        if (isDataType() && bOpen) {
            throw SDOException.typeCannotBeOpenAndDataType(getURI(), getName());
        }
        if (open != bOpen) {
            open = bOpen;
            if (open) {
                if (isSubType()) {
                    Type baseType = (Type)getBaseTypes().get(0);
                    if (!baseType.isOpen()) {
                        addOpenMappings();
                    }
                } else {
                    addOpenMappings();
                }

                if(isBaseType()) {
                    for (int i = 0; i < getSubTypes().size(); i++) {
                        SDOType nextSubType = (SDOType)getSubTypes().get(i);
                        nextSubType.setOpen(bOpen);
                    }
                }
            }
        }
    }

    private void addOpenMappings() {
        XMLAnyCollectionMapping anyMapping = new XMLAnyCollectionMapping();
        anyMapping.setAttributeName(ANY_MAPPING_ATTRIBUTE_NAME);
        anyMapping.setGetMethodName(ANY_MAPPING_GET_METHOD_NAME);
        anyMapping.setSetMethodName(ANY_MAPPING_SET_METHOD_NAME);
        anyMapping.setUseXMLRoot(true);
        getXmlDescriptor().addMapping(anyMapping);

        XMLAnyAttributeMapping anyAttrMapping = new XMLAnyAttributeMapping();
        anyAttrMapping.setAttributeName("openContentPropertiesAttributes");
        anyAttrMapping.setGetMethodName("_getOpenContentPropertiesAttributesMap");
        anyAttrMapping.setSetMethodName("_setOpenContentPropertiesAttributesMap");
        getXmlDescriptor().addMapping(anyAttrMapping);
    }

    /**
     * INTERNAL:
     * Change this Type's abstract setting. If it is true, this Type can't be instantiated and typically serve as base Type.
     * @param makeAbstract    boolean value implying if this Type is abstract.
     */
    public void setAbstract(boolean makeAbstract) {
        isAbstract = makeAbstract;
    }

    /**
     * INTERNAL:
     * If set as true, this Type specifies Sequenced DataObjects.
     * @param sequenced     boolean value implying if this type is sequenced.
     */
    public void setSequenced(boolean sequenced) {
        xmlDescriptor.setSequencedObject(sequenced);
    }

    public void setMixed(boolean isMixed) {
        String textAttribute = "text";
        XMLDirectMapping textMapping = (XMLDirectMapping) xmlDescriptor.getMappingForAttributeName(textAttribute);
        if (isMixed) {
            if (null == textMapping) {
                textMapping = new XMLDirectMapping();
                textMapping.setAttributeName(textAttribute);
                textMapping.setXPath("text()");
                xmlDescriptor.addMapping(textMapping);
            }
        } else {
            xmlDescriptor.removeMappingForAttributeName(textAttribute);
        }
    }

    /**
     * INTERNAL:
     * Set this Type to a simple Type by passing in boolean value true. Otherwise,
     * If boolean value is passed in, instances of this type implement DataObject.
     * @param datatype      boolean value implying if it is a simple Type
     */
    public void setDataType(boolean datatype) {
        if (datatype && isOpen()) {
            throw SDOException.typeCannotBeOpenAndDataType(getURI(), getName());
        }
        isDataType = datatype;
        if (datatype) {
            setFinalized(true);
        }
    }

    /**
     * INTERNAL:
     * Set a list of Types as This Type's base Types.
     * @param bTypes    a list types to become this Type's base Type.
     */
    public void setBaseTypes(List bTypes) {
        if (bTypes != null) {
            for (int i = 0; i < bTypes.size(); i++) {
                addBaseType((SDOType)bTypes.get(i));
            }
        } else {
            baseTypes = null;
        }
    }

    /**
     * INTERNAL:
     * @param type    a  type to become this Type's base Type.
     */
    public void addBaseType(SDOType type) {
        if (!this.getBaseTypes().contains(type)) {
            getBaseTypes().add(type);

            updateSubtypes(type);

            if(!(type.isDataType() && (type.getQName().getNamespaceURI() == SDOConstants.SDO_URL || type.getQName().getNamespaceURI() == SDOConstants.SDOJAVA_URL))) {
                type.getSubTypes().add(this);
            }

            if (type.isOpen() && this.isOpen()) {
                //don't want any mappings on this descriptor
                DatabaseMapping anyCollectionMapping = getXmlDescriptor().getMappingForAttributeName(ANY_MAPPING_ATTRIBUTE_NAME);
                getXmlDescriptor().getMappings().remove(anyCollectionMapping);

                DatabaseMapping anyAttrMapping = getXmlDescriptor().getMappingForAttributeName("openContentPropertiesAttributes");
                getXmlDescriptor().getMappings().remove(anyAttrMapping);
            }

            //int increaseBy = type.getProperties().size();
            //increaseIndices(increaseBy);
        }
    }

    private void updateSubtypes(Type baseType) {
        getProperties().addAll(0, baseType.getProperties());
        if(isBaseType()) {
            for (int i = 0; i < getSubTypes().size(); i++) {
                SDOType nextSubType = (SDOType)getSubTypes().get(i);
                nextSubType.updateSubtypes(baseType);
            }
        }
    }

    /**
      * INTERNAL:
      * Sets the Java class that this type represents.
      * @param aClass the Java class that this type represents.
      */
    public void setInstanceClass(Class aClass) {
        javaClass = aClass;
        if (javaClass != null) {
            javaClassName = javaClass.getName();
        }
    }

    /**
      * INTERNAL:
      * Set if this property was declared in an XML schema.
      * @param bXsd a boolean representing if this property was declared in an XML schema
      */
    public void setXsd(boolean bXsd) {
        xsd = bXsd;
    }

    /**
      * INTERNAL:
      * Returns if this property was declared in an XML schema.  Defaults to false.
      * @return if this property was declared in an XML schema
      */
    public boolean isXsd() {
        return xsd;
    }

    /**
      * INTERNAL:
      * Set the local name of this property.
      * @param xsdLocalNameString a String representing the local name of this property if it was declared in an XML schema
      */
    public void setXsdLocalName(String xsdLocalNameString) {
        xsdLocalName = xsdLocalNameString;
    }

    /**
      * INTERNAL:
      * Returns the  local name of the Property.
      * @return the local name of the property.
      */
    public String getXsdLocalName() {
        return xsdLocalName;
    }

    /**
     * INTERNAL:
     * @param property
    */
    public void addDeclaredProperty(Property property) {
        addDeclaredProperty((SDOProperty)property);
    }

    /**
     * INTERNAL:
     * @param property
    */
    public void addDeclaredProperty(SDOProperty property) {
        int end = getDeclaredProperties().size();
        addDeclaredProperty(property, end);
    }

    /**
    * INTERNAL:
    * @param property
    */
    public void addDeclaredProperty(Property property, int index) {
        addDeclaredProperty((SDOProperty)property, index);
    }

    /**
    * INTERNAL:
    * @param property
    */
    public void addDeclaredProperty(SDOProperty property, int index) {
        if (!getDeclaredPropertiesMap().containsKey(property.getName())) {
            int currentSize = getDeclaredProperties().size();

            int allSize = getProperties().size();
            int insertPlace = allSize - currentSize + index;

            //updateSubTypesProps
            if(isBaseType()) {
                for (int i = 0; i < getSubTypes().size(); i++) {
                    SDOType nextSubType = (SDOType)getSubTypes().get(i);
                    nextSubType.updateIndices(insertPlace, property);
                }
            }

            getDeclaredProperties().add(index, property);
            getProperties().add(insertPlace, property);

            property.setContainingType(this);

            getDeclaredPropertiesMap().put(property.getName(), property);
            if (property.hasAliasNames()) {
                for (int j = 0; j < property.getAliasNames().size(); j++) {
                    getDeclaredPropertiesMap().put(property.getAliasNames().get(j), property);
                }
            }
            if ((property.getType() != null) && (property.getType().isChangeSummaryType())) {
                changeSummaryProperty = property;
            }
        }
    }

    /**
     * INTERNAL:
     */
    public void removeDeclaredProperties(Property p) {
        getDeclaredProperties().remove(p);
        getDeclaredPropertiesMap().remove(p.getName());
        getProperties().remove(p);
    }

    /**
     * INTERNAL:
     */
    public Map getDeclaredPropertiesMap() {
        if (null == declaredPropertiesMap) {
            declaredPropertiesMap = new HashMap();
        }
        return declaredPropertiesMap;
    }

    /**
      * INTERNAL:
      * Sets the name of the Java class that this type represents.
      * @param instanceClassName the Java class that this type represents.
      */
    public void setInstanceClassName(String instanceClassName) {
        javaClassName = instanceClassName;
        javaClass = null;       
    }

    /**
      * INTERNAL:
      * Returns the Java class name that this type represents.
      * @return the Java class name.
      */
    public String getInstanceClassName() {
        if ((javaClassName == null) && (javaClass != null)) {
            javaClassName = javaClass.getName();
        }
        return javaClassName;
    }

    /**
      * INTERNAL:
      * Set if this type is an xsd:list in the schema
      * @param anXsdList a boolean representing if this type represents an xsd:list in the schema
      */
    public void setXsdList(boolean anXsdList) {
        xsdList = anXsdList;
    }

    /**
      * INTERNAL:
      * Indicates if this Type is an xsd:list in the schema
      * @return true if this Type represents an xsd:list in the schema
      */
    public boolean isXsdList() {
        return xsdList;
    }

    /**
      * INTERNAL:
      * Set the XMLDescriptor on this Type
      * @param anXMLDescriptor
      */
    public void setXmlDescriptor(XMLDescriptor anXMLDescriptor) {
        xmlDescriptor = anXMLDescriptor;
    }
   
    public XMLDescriptor getXmlDescriptor() {
        return xmlDescriptor;
    }

    /**
      * INTERNAL:
      * Get the XMLDescriptor associated with this Type or generate a new one.
      */
    public void initializeNamespaces(List namespaceResolvers) {
        if (!isDataType()) {
            NamespaceResolver nr = new NamespaceResolver();

            // copy namespaces between resolvers for well known and SDO namespaces
            if (namespaceResolvers != null) {
                for (int i = 0; i < namespaceResolvers.size(); i++) {
                    NamespaceResolver nextNR = (NamespaceResolver)namespaceResolvers.get(i);
                    if (nextNR != null) {
                        for (int j = 0, size = nextNR.getNamespaces().size(); j < size; j++) {
                            Namespace nextNamespace = (Namespace)nextNR.getNamespaces().get(j);
                            if ((!nextNamespace.getPrefix().equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) && (!nextNamespace.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) &&
                                (!nextNamespace.getNamespaceURI().equals(SDOConstants.SDOJAVA_URL)) && (!nextNamespace.getNamespaceURI().equals(SDOConstants.SDOXML_URL)) &&
                                (!nextNamespace.getNamespaceURI().equals(SDOConstants.SDO_URL))) {
                                String newPrefix = ((SDOTypeHelper)aHelperContext.getTypeHelper()).addNamespace(nextNamespace.getPrefix(), nextNamespace.getNamespaceURI());
                                nr.put(newPrefix, nextNamespace.getNamespaceURI());
                            }
                        }
                    }
                }
            }
            xmlDescriptor.setNamespaceResolver(nr);
            if (getURI() != null) {
                String prefix = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getPrefix(getURI());
                xmlDescriptor.getNamespaceResolver().put(prefix, getURI());
            }
            xmlDescriptor.getNamespaceResolver().put(XMLConstants.SCHEMA_INSTANCE_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
        }
    }

    /**
     * INTERNAL:
     * Convenience method that sets up class indicator and @sdoRef
     * attribute.
     * 
     * @param xdesc
     * @param pCls
     */
    private void addClassIndicator(XMLDescriptor xdesc, Class pCls, boolean isInheritanceRoot) {
        XMLField field = (XMLField)getXmlDescriptor().buildField("@xsi:type");
        field.initialize();
        xdesc.getInheritancePolicy().setClassIndicatorField(field);

        String parentIndicator = getName();
        String parentPrefix = xdesc.getNamespaceResolver().resolveNamespaceURI(getURI());
        if (parentPrefix != null) {
            parentIndicator = parentPrefix + SDOConstants.SDO_XPATH_NS_SEPARATOR_FRAGMENT + parentIndicator;
        }
        xdesc.getInheritancePolicy().addClassIndicator(pCls, parentIndicator);
        
        
        // only add the @sdoRef attribute if necessary
        if (xdesc.getMappingForAttributeName(SDO_REF_MAPPING_ATTRIBUTE_NAME) == null) {
            String sdoPrefix = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getPrefix(SDOConstants.SDO_URL);
            XMLDirectMapping sdoRefMapping = new XMLDirectMapping();
            sdoRefMapping.setAttributeName(SDO_REF_MAPPING_ATTRIBUTE_NAME);

            XMLField xmlField = new XMLField("@" + sdoPrefix + SDOConstants.SDO_XPATH_NS_SEPARATOR_FRAGMENT + SDOConstants.CHANGESUMMARY_REF);
            xmlField.getXPathFragment().setNamespaceURI(SDOConstants.SDO_URL);
            xmlField.getLastXPathFragment().setNamespaceURI(SDOConstants.SDO_URL);

            sdoRefMapping.setField(xmlField);
            xdesc.addMapping(sdoRefMapping);
        }
    }

    public void setupInheritance(SDOType parentType) {
        if (parentType == null) {
            // root of inheritance
            addClassIndicator(getXmlDescriptor(), getImplClass(), true);
        } else {
            if ((parentType.getURI() != null) && (!parentType.getURI().equals(SDOConstants.SDO_URL))) {
                // set parent descriptor indicator if necessary
                if (!parentType.getXmlDescriptor().hasInheritance()) {
                    addClassIndicator(parentType.getXmlDescriptor(), parentType.getImplClass(), false);
                }

                XMLDescriptor parentDescriptor = (XMLDescriptor)parentType.getXmlDescriptor().getInheritancePolicy().getRootParentDescriptor();
                NamespaceResolver parentNR = parentDescriptor.getNonNullNamespaceResolver();
                if (parentNR != null) {
                    for (int i = 0; i < parentNR.getNamespaces().size(); i++) {
                        Namespace nextNamespace = (Namespace)parentNR.getNamespaces().get(i);
                        if ((!nextNamespace.getPrefix().equals(javax.xml.XMLConstants.XMLNS_ATTRIBUTE)) && (!nextNamespace.getNamespaceURI().equals(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI)) &&
                            (!nextNamespace.getNamespaceURI().equals(SDOConstants.SDOJAVA_URL)) && (!nextNamespace.getNamespaceURI().equals(SDOConstants.SDOXML_URL)) &&
                            (!nextNamespace.getNamespaceURI().equals(SDOConstants.SDO_URL))) {
                            getXmlDescriptor().getNonNullNamespaceResolver().put(nextNamespace.getPrefix(), nextNamespace.getNamespaceURI());
                        }
                    }
                }
                if (getInstanceClassName() != null) {
                    String indicator = getName();
                    String prefix = parentDescriptor.getNamespaceResolver().resolveNamespaceURI(getURI());
                    if (prefix == null) {
                        prefix = getXmlDescriptor().getNamespaceResolver().resolveNamespaceURI(getURI());
                        if (prefix != null) {
                            parentDescriptor.getNamespaceResolver().put(prefix, getURI());
                        }
                    }
                    if (prefix != null) {
                        indicator = prefix + SDOConstants.SDO_XPATH_NS_SEPARATOR_FRAGMENT + indicator;
                    }
                    Class implClass = getImplClass();
                    parentDescriptor.getInheritancePolicy().addClassIndicator(implClass, indicator);
                    parentDescriptor.getInheritancePolicy().setShouldReadSubclasses(true);
                    Class parentClass = parentType.getImplClass();
                    getXmlDescriptor().getInheritancePolicy().setParentClass(parentClass);
                    getXmlDescriptor().getInheritancePolicy().setParentDescriptor(parentType.getXmlDescriptor());
                    parentType.getXmlDescriptor().getNamespaceResolver().put(XMLConstants.SCHEMA_INSTANCE_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
                    getXmlDescriptor().getNamespaceResolver().put(XMLConstants.SCHEMA_INSTANCE_PREFIX, javax.xml.XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
                }
            }
        }
        // now setup inheritance for any subtypes
        if(isBaseType()) {
            for (int i = 0; i < subTypes.size(); i++) {
                SDOType nextSubType = (SDOType)subTypes.get(i);
                if (!nextSubType.isDataType() && nextSubType.isSubType()) {
                    nextSubType.setupInheritance(this);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * For this Type generate classes
     * @param packageName
     * @param namespaceResolvers
     */
    public void preInitialize(String packageName, List namespaceResolvers) {
        String instanceClassName = getInstanceClassName();
        if (null == instanceClassName) {
            if (null == packageName) {
                String uri = getURI();
                if (null == uri) {
                    packageName = SDOUtil.getDefaultPackageName() + SDOConstants.JAVA_PACKAGE_NAME_SEPARATOR;
                } else {
                    packageName = SDOUtil.getPackageNameFromURI(uri) + SDOConstants.JAVA_PACKAGE_NAME_SEPARATOR;
                }
            }

            // Verify and fix any Class name that does not conform to conventions
            // run the class name through the JAXB mangler
            String mangledClassName = SDOUtil.className(getName(), false, true, true);

            // we will not fix any type collision at this time as a result of class renaming
            // write fully qualified java class name
            StringBuffer fullClassName = new StringBuffer(packageName);
            fullClassName.append(mangledClassName);
            setInstanceClassName(fullClassName.toString());
        }
        AbstractSessionLog.getLog().log(AbstractSessionLog.FINER,//
                                        "sdo_type_generation_processing_type", //
                                        new Object[] { Helper.getShortClassName(getClass()), getInstanceClassName() });

        initializeNamespaces(namespaceResolvers);
        getXmlDescriptor().setJavaClassName(getImplClassName());

        // See SDOResolvable enhancement
        String schemaContext = getName();
        if (getXmlDescriptor().getNamespaceResolver() != null) {
            String prefix = getXmlDescriptor().getNamespaceResolver().resolveNamespaceURI(getURI());
            if ((prefix != null) && !prefix.equals(SDOConstants.EMPTY_STRING)) {
                schemaContext = prefix + SDOConstants.SDO_XPATH_NS_SEPARATOR_FRAGMENT + schemaContext;
            }
        }
        String schemaContextWithSlash = SDOConstants.SDO_XPATH_SEPARATOR_FRAGMENT + schemaContext;

        XMLSchemaReference schemaRef = new XMLSchemaClassPathReference();
        schemaRef.setSchemaContext(schemaContextWithSlash);
        schemaRef.setType(XMLSchemaReference.COMPLEX_TYPE);
        getXmlDescriptor().setSchemaReference(schemaRef);

    }

    /**
      * INTERNAL:
      */
    public void postInitialize() {
        String idPropName = (String)get(SDOConstants.ID_PROPERTY);
        if (idPropName != null) {
            SDOProperty idProp = getProperty(idPropName);
            if (idProp != null) {
                String targetxpath = idProp.getQualifiedXPath(getURI(), true);
                getXmlDescriptor().addPrimaryKeyFieldName(targetxpath);
            }
        }
        setFinalized(true);
        if(null != nonFinalizedReferencingProps) {
            for (int i = 0; i < nonFinalizedReferencingProps.size(); i++) {
                SDOProperty nextProp = nonFinalizedReferencingProps.get(i);
                String nextURI = (String)getNonFinalizedMappingURIs().get(i);
                nextProp.buildMapping(nextURI, nextProp.getIndexInType());
            }
            nonFinalizedReferencingProps = null;
            nonFinalizedMappingURIs = null;
        }
        // set @sdoRef attribute mapping for complex types that are not involved in inheritance
        if (!isDataType() && !isSubType() && !isBaseType()) {
            String sdoPrefix = ((SDOTypeHelper)aHelperContext.getTypeHelper()).getPrefix(SDOConstants.SDO_URL);
            XMLDirectMapping sdoRefMapping = new XMLDirectMapping();
            sdoRefMapping.setAttributeName(SDO_REF_MAPPING_ATTRIBUTE_NAME);

            XMLField xmlField = new XMLField("@" + sdoPrefix + SDOConstants.SDO_XPATH_NS_SEPARATOR_FRAGMENT + SDOConstants.CHANGESUMMARY_REF);
            xmlField.getXPathFragment().setNamespaceURI(SDOConstants.SDO_URL);
            xmlField.getLastXPathFragment().setNamespaceURI(SDOConstants.SDO_URL);
            sdoRefMapping.setField(xmlField);
            xmlDescriptor.addMapping(sdoRefMapping);
        }
        if(!isDataType()) {
            getImplClass();
            if(!isAbstract() && !isWrapperType()) {
                TypeInstantiationPolicy tip = new TypeInstantiationPolicy(this);
                this.xmlDescriptor.setInstantiationPolicy(tip);
            }
        }
    }

    /**
      * INTERNAL:
      */
    public void setImplClassName(String implClassName) {
        javaImplClassName = implClassName;
        javaImplClass = null;
    }

    /**
      * INTERNAL:
      */
    public String getImplClassName() {
        if ((javaImplClassName == null) && (javaClassName != null)) {
            javaImplClassName = javaClassName + SDOConstants.SDO_IMPL_NAME;
        }
        return javaImplClassName;
    }

    /**
      * INTERNAL:
      */
    public Class getImplClass() {
        if ((javaImplClass == null) && (getImplClassName() != null)) {
            try {
                SDOClassLoader loader = ((SDOXMLHelper)aHelperContext.getXMLHelper()).getLoader();              
                javaImplClass = loader.loadClass(getImplClassName(), this);
                xmlDescriptor.setJavaClass(javaImplClass);
            } catch (ClassNotFoundException e) {
                throw SDOException.classNotFound(e, getURI(), getName());
            } catch (SecurityException e) {
                throw SDOException.classNotFound(e, getURI(), getName());
            }
        }
        return javaImplClass;
    }

    public Object get(Property property) {
        if(null == propertyValues) {
            return null;
        }
        return propertyValues.get(property);
    }

    public List getInstanceProperties() {
        if(null == propertyValues) {
            return Collections.EMPTY_LIST;
        }
        return new ArrayList(getPropertyValues().keySet());
    }

    /**
      * INTERNAL:
      */
    public void setPropertyValues(Map properties) {
        this.propertyValues = properties;
    }

    /**
      * INTERNAL:
      */
    public Map getPropertyValues() {
        if (propertyValues == null) {
            propertyValues = new HashMap();
        }
        return propertyValues;
    }

    /**
     * INTERNAL:
     *
     * @param property
     * @param value
     */
    public void setInstanceProperty(Property property, Object value) {
        if (property.equals(SDOConstants.JAVA_CLASS_PROPERTY) && value instanceof String) {
            setInstanceClassName((String)value);
        }      
        getPropertyValues().put(property, value);
    }

    /**
      * INTERNAL:
      */
    public SDOProperty getChangeSummaryProperty() {
        return changeSummaryProperty;
    }

    /**
     * INTERNAL:
     */
    public SDOProperty[] getPropertiesArray() {
        if ((allPropertiesArr == null) || (allPropertiesArr.length != getProperties().size())) {
            List l = getProperties();
            int s = (l == null) ? 0 : l.size();
            if (s > 0) {
                allPropertiesArr = (SDOProperty[])l.toArray(new SDOProperty[s]);
            } else {
                // initialize an empty array
                allPropertiesArr = new SDOProperty[0];
            }
        }
        return allPropertiesArr;
    }

    /**
      * INTERNAL:
      */
    public void setSubTypes(List subTypesList) {
        subTypes = subTypesList;
    }

    /**
      * INTERNAL:
      */
    public List getSubTypes() {
        if (subTypes == null) {
            subTypes = new ArrayList();
        }
        return subTypes;
    }

    /**
     * INTERNAL:
     * Provide a means to determine if this type has sub types without causing the sub types property to be initialized.
     */
    public boolean isBaseType() {
        return !(null == subTypes || subTypes.isEmpty());
    }

    private void updateIndices(int insertPosition, Property property) {
        int declaredSize = getDeclaredProperties().size();
        SDOProperty nextProp = null;
        for (int i = 0; i < declaredSize; i++) {
            nextProp = (SDOProperty)getDeclaredProperties().get(i);
            nextProp.incrementIndexInType();
        }
        getProperties().add(insertPosition, property);
        if(isBaseType()) {
            int subTypesSize = getSubTypes().size();
    
            SDOType nextSubType = null;
            for (int i = 0; i < subTypesSize; i++) {
                nextSubType = (SDOType)getSubTypes().get(i);
                nextSubType.updateIndices(insertPosition, property);
            }
        }
    }

    /**
      * INTERNAL:
      */
    public void setXsdType(QName xsdTypeQName) {
        xsdType = xsdTypeQName;
    }

    /**
      * INTERNAL:
      */
    public QName getXsdType() {
        return xsdType;
    }

    /**
      * INTERNAL:
      */
    public void setFinalized(boolean bFinalized) {
        finalized = bFinalized;
    }

    /**
      * INTERNAL:
      */
    public boolean isFinalized() {
        return finalized;
    }

    /**
      * INTERNAL:
      */
    public void setNonFinalizedReferencingProps(List nonFinalizedReferencingProps) {
        this.nonFinalizedReferencingProps = nonFinalizedReferencingProps;
    }

    /**
      * INTERNAL:
      */
    public List getNonFinalizedReferencingProps() {
        if (nonFinalizedReferencingProps == null) {
            nonFinalizedReferencingProps = new ArrayList();
        }
        return nonFinalizedReferencingProps;
    }

    /**
      * INTERNAL:
      */
    public void setNonFinalizedMappingURIs(List nonFinalizedMappingURIsList) {
        nonFinalizedMappingURIs = nonFinalizedMappingURIsList;
    }

    /**
      * INTERNAL:
      */
    public List getNonFinalizedMappingURIs() {
        if (nonFinalizedMappingURIs == null) {
            nonFinalizedMappingURIs = new ArrayList();
        }
        return nonFinalizedMappingURIs;
    }

    /**
      * INTERNAL:
      */
    public void setAppInfoElements(List appInfoElementsList) {
        appInfoElements = appInfoElementsList;
    }

    /**
      * INTERNAL:
      */
    public List getAppInfoElements() {
        return appInfoElements;
    }

    /**
      * INTERNAL:
      */
    public Map getAppInfoMap() {
        if (appInfoMap == null) {
            appInfoMap = ((SDOXSDHelper)aHelperContext.getXSDHelper()).buildAppInfoMap(appInfoElements);
        }
        return appInfoMap;
    }

    /**
     * INTERNAL:
     * Return the wrapped initial value for the primitive numeric (when not defined)
     * See p.45 of Java Spec 4th edition.
     * See p.85 Sect 9.3 of the SDO Spec.
     * @return aDefault Object (primitive numerics) or null (DataObjects, String, Lists)
     */
    public Object getPseudoDefault() {
        return pseudoDefault;
    }

    /**
     * INTERNAL:
     * Set an Object wrapper around primitive numeric types
     * @param anObject
     */
    public void setPseudoDefault(Object anObject) {
        pseudoDefault = anObject;
    }
    
    /**
     * Return the HelperContext object associated with this type.
     * For example, the typeHelper associated with the returned HelperContext would contain this type
     */
    public HelperContext getHelperContext(){
      return aHelperContext;
    }

    public boolean isChangeSummaryType() {
        return false;
    }
    
    public boolean isDataObjectType() {
        return false;
    }
    
    public boolean isTypeType() {
        return false;
    }

    public boolean isOpenSequencedType() {
        return false;
    }

    public boolean isWrapperType() {
        return false;
    }

    public static class TypeInstantiationPolicy extends InstantiationPolicy {

        SDOType sdoType;

        public TypeInstantiationPolicy(SDOType type) {
            sdoType = type;
        }

        public Object buildNewInstance() throws DescriptorException {
            return sdoType.getHelperContext().getDataFactory().create(sdoType);
        }

    }

}
