persistence@glassfish.java.net

Re: Configuration of Client vs. Shared Cache

From: Adam Bien <abien_at_adam-bien.com>
Date: Tue, 22 Jan 2008 22:02:31 +0100

Hi Gordon,

I changed the code as you suggested.

  1. I renamed the property to: toplink.persistence.context.reference.mode
  2. In UnitOfWork the deleteObjects is untouched - it uses the "hard
     references"
  3. newAggregates uses now WeakKeyIdentityHashtable (with weak keys,
     and hard values)
  4. The referenceMode reference was changed from private to protected
  5. IdentityHashtable is abstract now. HardIdentityHashtable,
     WeakKeyIdentityHashtable and WeakKeyAndValueIdentityHashtable
     inherit from it. However, because each Hashtable accesses the
     Entries directly I just replicated the code to the subclasses.
     Each of the Hashtable accesses the Entry either directly , or
     using the WeakReference (then with accessors).
  6. newObjectsOriginalToClone is realized with weak keys and values.
  7. I just reused your implementation of the method buildIdentityMap
     in the IdentityMapManager in the hope it is enough :-)
  8. I'm not sure whether in the class InheritancePolicy always the
     Hashtable with the hard references has to be used:

public SQLSelectStatement
buildClassIndicatorSelectStatement(ObjectLevelReadQuery query) {

     // ...

       // 2612538 - the default size of HardIdentityHashtable (32) is
appropriate

//Hard, or potentially Weak?

       HardIdentityHashtable clonedExpressions = new
HardIdentityHashtable();

  9. Because IdentityHashtable is abstract now, I had to changed some
     additional classes to instantiate the HardIdentityHashtable instead.
 10. I extended the basic test with some identity assertions, it seems
     to work so far.


Please let me know, whether the changes are o.k. - then I would like to
better document them.

regards,

adam

Gordon Yorke schrieb:
> Hello Adam,
> Honestly I took the easy route and changed only the two classes that took little effort. The UnitOfWork still needs to be updated based on my feedback as will the WeakIdentityHashtable class.
> --Gordon
>
> -----Original Message-----
> From: Adam Bien [mailto:abien_at_adam-bien.com]
> Sent: Wednesday, January 16, 2008 2:08 PM
> To: gordon.yorke_at_oracle.com
> Cc: persistence_at_glassfish.dev.java.net
> Subject: Re: Configuration of Client vs. Shared Cache
>
>
> Hi Yordon,
>
> thank you very much for your detailed feedback. As I understand, you
> already changed my code. Do you expect some actions from my side?
> I will submit the ETA to the V3 stream. What is the exact link?
>
> thank you,
>
> regards,
>
> adam
>
> Gordon Yorke schrieb:
>
>> Hello Adam,
>> I apologize for not getting back to you sooner. Generally
>> everything looks good but I have a few comments.
>> The UnitOfWork (Persistence Context) is more than a first level
>> cache and that should be reflected in the property name. Otherwise the
>> property may be used without an appreciation for the hard to detect
>> side-effect of change loss. The property should be moved to the
>> config package with the other properties as we will want customers to
>> be able to browse the javaDocs and see these properties as an option.
>> In the UnitOfWork the deletedObjects should not be touched as a
>> deleted object will probably be dereferenced by the client and may be
>> lost if garbage collected. The 'newAggregates' list should also
>> have weak references in the case the parent of an embeddable object is
>> released.
>> Private visibility is generally not used in TopLink code for class
>> attributes. Protected provides similar restrictions but does not
>> restrict extension through subclassing which has been quite useful in
>> the past.
>> With the IdentityHashtable it is the keys that should be weak in
>> most cases.with the newObjectsOriginalToClone both the key and the
>> value should be weak. Also for performance reasons it would be
>> better to have the weak IdentityHashtable be a subclass. That way
>> the Hashtable code will remain micro-optimized. At some point in V3
>> there is a plan to migrate to the JDK collections and having a
>> separate type will make that easier.
>> The IdentityMapManager.buildNewIdentityMap() will need to be
>> updated as well. Otherwise the 'cache' in the UnitOfWork will not
>> have week references.
>>
>> I have attached some changed classes with my feedback.
>>
>> There is still no ETA on when the entity-persistence module will be
>> available in the V3 stream. If you can submit these changes to the
>> EclipseLink bug I can get them integrated into the code base so they
>> do not get lost. ( https://bugs.eclipse.org/bugs/show_bug.cgi?id=214661 )
>> --Gordon
>>
>> Adam Bien wrote:
>>
>>> Patch for the problem below is submitted:
>>>
>>> https://glassfish.dev.java.net/issues/show_bug.cgi?id=3985
>>>
>>> Please review the enhancements. Thank you in advance!,
>>>
>>> regards,
>>>
>>> adam
>>>
>>> Gordon Yorke schrieb:
>>>
>>>> Hello Adam,
>>>> I can appreciate the usefulness of what you are requesting but
>>>> there is no means by which the provider can guarantee predictable
>>>> behaviour. The onus would be on the application developer to ensure
>>>> that the application forcefully keep the required objects from being
>>>> garbage collected and that no dependency was place on the garbage
>>>> collection for making objects un-managed (the transaction would
>>>> still have to complete successfully even if the garbage collector
>>>> was disabled).
>>>> I could see this functionality being added as an advanced feature
>>>> for users with specific needs but we would need to be careful in
>>>> presenting this feature without due warnings.
>>>> If you would like to attempt to produce this functionality within
>>>> TopLink I would recommend that you update the UnitOfWork's
>>>> collections "cloneMapping", "clonesToOriginals",
>>>> "newObjectsCloneToOriginal", "newObjectsOriginalToClone" and
>>>> "deletedObjects" to have weak references used within the collections
>>>> when a weak identity map is set on the UnitOfWork. Perhaps have the
>>>> member variables initialized to Maps that use only weak references.
>>>> If you have any questions on the internals of the UnitOfWork and its
>>>> place within the TopLink architecture I would be happy to help.
>>>> --Gordon
>>>>
>>>> Adam Bien wrote:
>>>>
>>>>> Hi Gordon,
>>>>> Gordon Yorke schrieb:
>>>>>
>>>>>> "even if the object is no longer referenced by the application" ->
>>>>>> why
>>>>>> this? In case an object isn't referenced by the application any
>>>>>> more, it
>>>>>> could simply disappear... Especially in an unmanaged environment.
>>>>>> This
>>>>>> extension would be really valuable for rich clients ("rias").
>>>>>> Actually
>>>>>> this problem will have every rich client application. How hard
>>>>>> would it
>>>>>> be to provide a switch to be able to choose between hard and weak
>>>>>> references?"
>>>>>>
>>>>>> Adam,
>>>>>> Having changed objects disappear from the PersistenceContext
>>>>>> based on weak references would be unpredictable. The application
>>>>>> would have no way of knowing what changes would be written out and
>>>>>> which ones would have been thrown away because of garbage
>>>>>> collection. In your case perhaps the application is structured in
>>>>>> such a way that only objects referenced by the application have
>>>>>> the potential to be changed but in many applications that is not
>>>>>> the case.
>>>>>>
>>>>> The approach with weak L1 cache should actually work for every
>>>>> application managed entity manager, which is not shared between
>>>>> threads and transactions.
>>>>> This is the case in most RCP (Netbeans, Eclipse) applications,
>>>>> where the Entity Manager is executed inside the same JVM as the
>>>>> presentation tier.
>>>>>
>>>>>
>>>>>> Particularly in service based applications the changes could be
>>>>>> applied through the merge() api without a reference ever existing
>>>>>> from the application to the managed entity.
>>>>>>
>>>>>>
>>>>> Absolutely. In service based applications you are right. Merge
>>>>> would be the option then. However in real service based application
>>>>> I would even use DTOs :-). However we are working with a rich
>>>>> domain model
>>>>>
>>>>>> Having a GUI use the merge() api to have only the changed
>>>>>> objects registered within the PersistenceContext and processed for
>>>>>> changes is a much more effecient and scalable architecture.
>>>>>>
>>>>> Yes. In our case it would be a huge disadvantage. Until now we can
>>>>> rely on the transparent persistence - which is great. Be forced to
>>>>> detach the objects and invoke "merge" isn't a elegant solution.
>>>>>
>>>>>> The complexity of the object model should not hamper the use of
>>>>>> the merge() api as the GUI's update events could merge the root of
>>>>>> the tree of Entities being changed and TopLink, with correct
>>>>>> cascade merge settings, would handle the rest.
>>>>>>
>>>>>>
>>>>> We have many UIs, Use Cases, reports etc. So there are many root
>>>>> objects in place. This makes your suggested approach more difficult...
>>>>>
>>>>> regards -
>>>>>
>>>>> adam
>>>>>
>>>>>> --Gordon
>>>>>>
>>>>>> -----Original Message-----
>>>>>> From: Adam Bien [mailto:abien_at_adam-bien.com]
>>>>>> Sent: Monday, November 19, 2007 5:08 PM
>>>>>> To: persistence_at_glassfish.dev.java.net
>>>>>> Subject: Re: Configuration of Client vs. Shared Cache
>>>>>>
>>>>>>
>>>>>> Hello Gordon,
>>>>>>
>>>>>> thank you for the fast answer.
>>>>>> Gordon Yorke schrieb:
>>>>>>
>>>>>>
>>>>>>> Hello Adam,
>>>>>>> An application managed EntityManager always wraps an Extended
>>>>>>> Persistence Context. An Extended Persistence Context is not
>>>>>>> released until the EntityManager is closed unlike a transactional
>>>>>>> Container Managed Entity Manager which releases the Persistence
>>>>>>> Context at the end of each transaction.
>>>>>>>
>>>>>>> If you need you can simulate the transactional EntityManager by
>>>>>>> calling flush(), clear() before each commit() but this would
>>>>>>> still leave you with the detached issue.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>> This is our issue. Detached entities are a real problem in more
>>>>>> complex
>>>>>> applications...
>>>>>>
>>>>>>
>>>>>>> The problem with holding the objects in a weak reference is the
>>>>>>> change detection requirement of the PersistenceContext. Any
>>>>>>> object touched by the PersistenceContext must be tracked by the
>>>>>>> PersistenceContext and any changes to that object must be written
>>>>>>> to the database on request, even if the object is no longer
>>>>>>> referenced by the application. With weak references unwritten
>>>>>>> changes could potentially be lost.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>> "even if the object is no longer referenced by the application" ->
>>>>>> why
>>>>>> this? In case an object isn't referenced by the application any
>>>>>> more, it
>>>>>> could simply disappear... Especially in an unmanaged environment.
>>>>>> This
>>>>>> extension would be really valuable for rich clients ("rias").
>>>>>> Actually
>>>>>> this problem will have every rich client application. How hard
>>>>>> would it
>>>>>> be to provide a switch to be able to choose between hard and weak
>>>>>> references?
>>>>>>
>>>>>>
>>>>>>> There a multiple ways you could manage the volume of object
>>>>>>> registered within a Persistence Context. On screen changes you
>>>>>>> could close and create a new EntityManager but my recomended
>>>>>>> approach would be to detach the display objects from the managed
>>>>>>> objects. No need to create seperate classes but you could use
>>>>>>> the merge facility of the EntityManager to merge the objects from
>>>>>>> the GUI to the PersistenceContext for any object updates or
>>>>>>> specific save events from the GUI.
>>>>>>>
>>>>>>>
>>>>>> The problem here: the object graph is really complicated with
>>>>>> recursive
>>>>>> algorithms (about 700 entities...). Therefore we would rely on
>>>>>> "transparent persistence". Actually it works great with TopLink on
>>>>>> Smalltalk :-). We have only the issue in Java... However TopLink
>>>>>> is the
>>>>>> only OR-framework, which can handle this complexity. The only
>>>>>> issue are
>>>>>> the "hard" references.
>>>>>>
>>>>>>
>>>>>>
>>>>>>> This would limit the number of objects in your PersistenceContext
>>>>>>> and mostlikly provide for a performance improvement from your
>>>>>>> current design as less objects are being checked for changes. I
>>>>>>> would also recommend that if you took this approach that you
>>>>>>> switch the shared cache to be a softcache weak identity map.
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>> We switched the shared cache already. The L1-cache is our problem...
>>>>>>
>>>>>> thank you for the detailed answer,
>>>>>>
>>>>>> adam
>>>>>>
>>>>>>
>>>>>>> --Gordon
>>>>>>>
>>>>>>> -----Original Message-----
>>>>>>> From: Adam Bien [mailto:abien_at_adam-bien.com]
>>>>>>> Sent: Monday, November 19, 2007 11:46 AM
>>>>>>> To: gordon.yorke_at_oracle.com
>>>>>>> Cc: persistence_at_glassfish.dev.java.net
>>>>>>> Subject: Re: Configuration of Client vs. Shared Cache
>>>>>>>
>>>>>>>
>>>>>>> Hi Gordon,
>>>>>>>
>>>>>>> thank you very much for the fast reply. I'm thinking of the
>>>>>>> Persistence
>>>>>>> Context as an Identity HashMap, which primary goal is to keep the
>>>>>>> consistency of the entities in a transaction.
>>>>>>> However in our application (it is a rich client, which connects
>>>>>>> directly
>>>>>>> to the database without an application server or middleware), it
>>>>>>> seems
>>>>>>> like once loaded objects are hold
>>>>>>> by the EntityManager until the method clear is invoked or the Entity
>>>>>>> Manager is closed (we are using the current build - TopLink
>>>>>>> v2b58). The
>>>>>>> Entity Manager remains open between the transactions - it is even
>>>>>>> not
>>>>>>> cleared. We would like to work with the entities, without loading
>>>>>>> them
>>>>>>> from the database every time.
>>>>>>>
>>>>>>> We are using data binding between the domain objects and the UI - so
>>>>>>> clearing the entity manager makes the entities detached (and
>>>>>>> makes the
>>>>>>> GC run) - the whole app has to be refreshed then. This in turn is
>>>>>>> not
>>>>>>> wished by the end users :-).
>>>>>>>
>>>>>>> I'm wondering, whether it would be possible to hold the objects
>>>>>>> using
>>>>>>> weak-reference in the L1 cache / transactional cache. Then all
>>>>>>> unneeded
>>>>>>> objects would be automatically garbage collected. The
>>>>>>> object-identity
>>>>>>> would be still provided with this setting... Now the memory
>>>>>>> consumption
>>>>>>> increases - until Out Of Memory occurs...
>>>>>>>
>>>>>>> any thoughts?
>>>>>>>
>>>>>>> thank you in advance,
>>>>>>>
>>>>>>>
>>>>>>> regards,
>>>>>>>
>>>>>>> adam
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> Gordon Yorke schrieb:
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>> Hello Adam,
>>>>>>>> You may be confusing the Persistence Context with a cache?
>>>>>>>> Sometimes the UnitOfWork which is the implementation of the
>>>>>>>> Persistence Context in TopLink is described as a cache to
>>>>>>>> explain some
>>>>>>>> of its behaviours but its behaviour is beyond that of a cache.
>>>>>>>> Because the Unit0fWork must track all objects that were ever read
>>>>>>>> through or registered by the UnitOfWork to full its behavioural
>>>>>>>> requirements the TopLink cache settings do not apply.
>>>>>>>> --Gordon
>>>>>>>>
>>>>>>>>
>>>>>>>> Adam Bien wrote:
>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>>> Hi All,
>>>>>>>>>
>>>>>>>>> TopLink cache consists of two parts:
>>>>>>>>> 1. Client/L1
>>>>>>>>> 2. Shared/L2
>>>>>>>>>
>>>>>>>>> Is it possible to configure them independently? In the TopLink
>>>>>>>>> reference there are two sections:
>>>>>>>>>
>>>>>>>>> http://www.oracle.com/technology/products/ias/toplink/jpa/resources/toplink-jpa-extensions.html#BABGDJBC
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> It seems like the @Cache annotation addresses the Client/L1 cache.
>>>>>>>>> The properties (
>>>>>>>>>
>>>>>>>>> eg. <property name="toplink.cache.type.Order" value="Full"/>)
>>>>>>>>> the L2.
>>>>>>>>>
>>>>>>>>> Is it possible to configure both caches using the persistence.xml
>>>>>>>>> properties?
>>>>>>>>>
>>>>>>>>> I would like to configure Weak Caches for both levels, however it
>>>>>>>>> seems like the L1 cache is still "Full" (or it uses "hard"
>>>>>>>>> references
>>>>>>>>> to entities)
>>>>>>>>>
>>>>>>>>> thank you in advance!,
>>>>>>>>>
>>>>>>>>> adam bien
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>> --
>>>>>>> Consultant, Author, Java Champion
>>>>>>>
>>>>>>> Homepage: www.adam-bien.com
>>>>>>> Weblog: blog.adam-bien.com
>>>>>>> eMail: abien_at_adam-bien.com
>>>>>>> Mobile: 0049(0)170 280 3144
>>>>>>>
>>>>>>> Books: Enterprise Architekturen (ISBN: 393504299X),
>>>>>>> Java EE 5 Architekturen (ISBN: 3939084247),
>>>>>>> J2EE Patterns, J2EE Hotspots, Enterprise Frameworks and
>>>>>>> Struts
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>> --
>>>>>> Consultant, Author, Java Champion
>>>>>>
>>>>>> Homepage: www.adam-bien.com
>>>>>> Weblog: blog.adam-bien.com
>>>>>> eMail: abien_at_adam-bien.com
>>>>>> Mobile: 0049(0)170 280 3144
>>>>>>
>>>>>> Books: Enterprise Architekturen (ISBN: 393504299X),
>>>>>> Java EE 5 Architekturen (ISBN: 3939084247),
>>>>>> J2EE Patterns, J2EE Hotspots, Enterprise Frameworks and
>>>>>> Struts
>>>>>>
>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>
>
>
>
>
>
>


-- 
 Consultant, Author, Java Champion
 
 Homepage: www.adam-bien.com
 Weblog: blog.adam-bien.com
 eMail:  abien_at_adam-bien.com
 Mobile: 0049(0)170 280 3144
 Books: Enterprise Architekturen (ISBN: 393504299X),
        Java EE 5 Architekturen  (ISBN: 3939084247),
        J2EE Patterns, J2EE Hotspots, Enterprise Frameworks and Struts
   


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.queryframework.JoinedAttributeManager;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.internal.descriptors.ObjectBuilder;
import oracle.toplink.essentials.sessions.DatabaseRecord;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>:Two objects can be considered to be related by aggregation if there is a strict
 * 1:1 relationship between the objects. This means that if the source (parent)
 * object exists, then the target (child or owned) object must exist.
 *
 * In TopLink, it also means the data for the owned object is in the same table as
 * the parent.
 *
 * @author Sati
 * @since TOPLink/Java 1.0
 */
public class AggregateObjectMapping extends AggregateMapping implements RelationalMapping {

    /**
     * If <em>all</em> the fields in the database row for the aggregate object are NULL,
     * then, by default, TopLink will place a null in the appropriate source object
     * (as opposed to an aggregate object filled with nulls).
     * To change this behavior, set the value of this variable to false. Then TopLink
     * will build a new instance of the aggregate object that is filled with nulls
     * and place it in the source object.
     */
    protected boolean isNullAllowed;

    /** Map the name of a field in the aggregate descriptor to a field in the source table. */
    protected transient Map<String, String> aggregateToSourceFieldNames;

    /**
     * Default constructor.
     */
    public AggregateObjectMapping() {
        aggregateToSourceFieldNames = new HashMap(5);
        isNullAllowed = true;
    }

    /**
     * INTERNAL:
     */
    public boolean isRelationalMapping() {
        return true;
    }

    /**
     * PUBLIC:
     * Add a field name translation that maps from a field name in the
     * source table to a field name in the aggregate descriptor.
     */
    public void addFieldNameTranslation(String sourceFieldName, String aggregateFieldName) {
        String unQualifiedAggregateFieldName = aggregateFieldName.substring(aggregateFieldName.lastIndexOf('.') + 1);// -1 is returned for no ".".
        getAggregateToSourceFieldNames().put(unQualifiedAggregateFieldName, sourceFieldName);
    }

    /**
     * INTERNAL:
     * Return whether all the aggregate fields in the specified
     * row are NULL.
     */
    protected boolean allAggregateFieldsAreNull(AbstractRecord databaseRow) {
        for (Enumeration fieldsEnum = getReferenceFields().elements();
                 fieldsEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)fieldsEnum.nextElement();
            Object value = databaseRow.get(field);
            if (value != null) {
                return false;
            }
        }
        return true;
    }

    /**
     * PUBLIC:
     * If <em>all</em> the fields in the database row for the aggregate object are NULL,
     * then, by default, TopLink will place a null in the appropriate source object
     * (as opposed to an aggregate object filled with nulls). This behavior can be
     * explicitly set by calling #allowNull().
     * To change this behavior, call #dontAllowNull(). Then TopLink
     * will build a new instance of the aggregate object that is filled with nulls
     * and place it in the source object.
     * In either situation, when writing, TopLink will place a NULL in all the
     * fields in the database row for the aggregate object.
     */
    public void allowNull() {
        setIsNullAllowed(true);
    }

    /**
     * INTERNAL:
     * Return whether the query's backup object has an attribute
     * value of null.
     */
    protected boolean backupAttributeValueIsNull(WriteObjectQuery query) {
        if (query.getSession().isUnitOfWork()) {
            Object backupAttributeValue = getAttributeValueFromObject(query.getBackupClone());
            if (backupAttributeValue == null) {
                return true;
            }
        }
        return false;
    }

    /**
     * INTERNAL:
     * Build and return an aggregate object from the specified row.
     * If a null value is allowed and
     * all the appropriate fields in the row are NULL, return a null.
     * Otherwise, simply create a new aggregate object and return it.
     */
    protected Object buildAggregateFromRow(AbstractRecord databaseRow, Object targetObject, JoinedAttributeManager joinManager, boolean buildShallowOriginal, AbstractSession session) throws DatabaseException {
        // check for all NULLs
        if (isNullAllowed() && allAggregateFieldsAreNull(databaseRow)) {
            return null;
        }

        // If refreshing, maintain object identity;
        // otherwise construct a new aggregate object.
        Object aggregate = null;
        ClassDescriptor descriptor = getReferenceDescriptor();
        boolean refreshing = true;
        if (descriptor.hasInheritance()) {
            Class newAggregateClass = descriptor.getInheritancePolicy().classFromRow(databaseRow, session);
            descriptor = getReferenceDescriptor(newAggregateClass, session);

            if (joinManager.getBaseQuery().shouldRefreshIdentityMapResult()) {
                aggregate = getMatchingAttributeValueFromObject(databaseRow, targetObject, session, descriptor);
                if ((aggregate != null) && (aggregate.getClass() != newAggregateClass)) {
                    // if the class has changed out from underneath us, we cannot preserve object identity
                    // build a new instance of the *new* class
                    aggregate = descriptor.getObjectBuilder().buildNewInstance();
                    refreshing = false;
                }
            }
        } else {
            if (joinManager.getBaseQuery().shouldRefreshIdentityMapResult()) {
                aggregate = getMatchingAttributeValueFromObject(databaseRow, targetObject, session, descriptor);
            }
        }

        if (aggregate == null) {
            aggregate = descriptor.getObjectBuilder().buildNewInstance();
            refreshing = false;
        }

        ObjectBuildingQuery nestedQuery = joinManager.getBaseQuery();
        nestedQuery.setSession(session); //ensure the correct session is set on the query.
        if (joinManager.getBaseQuery().isObjectLevelReadQuery()){
            if (joinManager.isAttributeJoined(getDescriptor(), getAttributeName()) ){
                // A nested query must be built to pass to the descriptor that looks like the real query execution would.
                nestedQuery = (ObjectLevelReadQuery)((ObjectLevelReadQuery)joinManager.getBaseQuery()).deepClone();
                // Must cascade the nested partial/join expression and filter the nested ones.
                ((ObjectLevelReadQuery)nestedQuery).getJoinedAttributeManager().setJoinedAttributeExpressions_(extractNestedExpressions(joinManager.getJoinedAttributeExpressions(), joinManager.getBaseExpressionBuilder(), false));
                nestedQuery.setDescriptor(descriptor);
            }
        }
        if (buildShallowOriginal) {
            descriptor.getObjectBuilder().buildAttributesIntoShallowObject(aggregate, databaseRow, nestedQuery);
        } else if (session.isUnitOfWork()) {
            descriptor.getObjectBuilder().buildAttributesIntoWorkingCopyClone(aggregate, nestedQuery, joinManager, databaseRow, (UnitOfWorkImpl)session, refreshing);
        } else {
            descriptor.getObjectBuilder().buildAttributesIntoObject(aggregate, databaseRow, nestedQuery, joinManager, refreshing);
        }
        return aggregate;
    }

    /**
     * INTERNAL:
     * Build and return a database row with all the reference
     * fields set to nulls.
     */
    protected AbstractRecord buildNullReferenceRow() {
        AbstractRecord result = new DatabaseRecord(getReferenceFields().size());
        for (Enumeration stream = getReferenceFields().elements(); stream.hasMoreElements();) {
            result.put((DatabaseField)stream.nextElement(), null);
        }
        return result;
    }

    /**
     * INTERNAL:
     * Used to allow object level comparisons.
     * In the case of an Aggregate which has no primary key must do an attribute
     * by attribute comparison.
     */
    public Expression buildObjectJoinExpression(Expression expression, Object value, AbstractSession session) {
        Expression attributeByAttributeComparison = null;
        Expression join = null;
        Object attributeValue = null;

        // value need not be unwrapped as it is an aggregate, nor should it
        // influence a call to getReferenceDescriptor.
        ClassDescriptor referenceDescriptor = getReferenceDescriptor();
        if ((value != null) && !referenceDescriptor.getJavaClass().isInstance(value)) {
            throw QueryException.incorrectClassForObjectComparison(expression, value, this);
        }
        Enumeration mappings = referenceDescriptor.getMappings().elements();
        for (; mappings.hasMoreElements();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement();
            if (value == null) {
                attributeValue = null;
            } else {
                attributeValue = mapping.getAttributeValueFromObject(value);
            }
            join = expression.get(mapping.getAttributeName()).equal(attributeValue);
            if (attributeByAttributeComparison == null) {
                attributeByAttributeComparison = join;
            } else {
                attributeByAttributeComparison = attributeByAttributeComparison.and(join);
            }
        }
        return attributeByAttributeComparison;
    }

    /**
     * INTERNAL:
     * Used to allow object level comparisons.
     */
    public Expression buildObjectJoinExpression(Expression expression, Expression argument, AbstractSession session) {
        Expression attributeByAttributeComparison = null;

        //Enumeration mappingsEnum = getSourceToTargetKeyFields().elements();
        Enumeration mappingsEnum = getReferenceDescriptor().getMappings().elements();
        for (; mappingsEnum.hasMoreElements();) {
            DatabaseMapping mapping = (DatabaseMapping)mappingsEnum.nextElement();
            String attributeName = mapping.getAttributeName();
            Expression join = expression.get(attributeName).equal(argument.get(attributeName));
            if (attributeByAttributeComparison == null) {
                attributeByAttributeComparison = join;
            } else {
                attributeByAttributeComparison = attributeByAttributeComparison.and(join);
            }
        }
        return attributeByAttributeComparison;
    }

    /**
     * INTERNAL:
     * Build and return a database row built with the values from
     * the specified attribute value.
     */
    protected AbstractRecord buildRowFromAggregate(Object object, Object attributeValue, AbstractSession session) throws DescriptorException {
        return buildRowFromAggregate(object, attributeValue, session, false);
    }

    /**
     * INTERNAL:
     * Build and return a database row built with the values from
     * the specified attribute value.
     */
    protected AbstractRecord buildRowFromAggregate(Object object, Object attributeValue, AbstractSession session, boolean forceWriteOfReadOnlyClasses) throws DescriptorException {
        if (attributeValue == null) {
            if (isNullAllowed()) {
                return buildNullReferenceRow();
            } else {
                throw DescriptorException.nullForNonNullAggregate(object, this);
            }
        } else {
            if ((!forceWriteOfReadOnlyClasses) && (session.isClassReadOnly(attributeValue.getClass()))) {
                return new DatabaseRecord(1);
            } else {
                return getObjectBuilder(attributeValue, session).buildRow(attributeValue, session);
            }
        }
    }

    /**
     * INTERNAL:
     * Build and return a database row built with the values from
     * the specified attribute value.
     */
    protected AbstractRecord buildRowFromAggregateWithChangeRecord(ChangeRecord changeRecord, ObjectChangeSet objectChangeSet, AbstractSession session) throws DescriptorException {
        return buildRowFromAggregateWithChangeRecord(changeRecord, objectChangeSet, session, false);
    }

    /**
     * INTERNAL:
     * Build and return a database row built with the values from
     * the specified attribute value.
     */
    protected AbstractRecord buildRowFromAggregateWithChangeRecord(ChangeRecord changeRecord, ObjectChangeSet objectChangeSet, AbstractSession session, boolean forceWriteOfReadOnlyClasses) throws DescriptorException {
        if (objectChangeSet == null) {
            if (isNullAllowed()) {
                return buildNullReferenceRow();
            } else {
                Object object = ((ObjectChangeSet)changeRecord.getOwner()).getUnitOfWorkClone();
                throw DescriptorException.nullForNonNullAggregate(object, this);
            }
        } else {
            if ((!forceWriteOfReadOnlyClasses) && (session.isClassReadOnly(objectChangeSet.getClassType(session)))) {
                return new DatabaseRecord(1);
            } else {
                return getReferenceDescriptor(objectChangeSet.getClassType(session), session).getObjectBuilder().buildRowWithChangeSet(objectChangeSet, session);
            }
        }
    }

    /**
     * INTERNAL:
     * Build and return a database row built with the changed values from
     * the specified attribute value.
     */
    protected AbstractRecord buildRowFromAggregateForUpdate(WriteObjectQuery query, Object attributeValue) throws DescriptorException {
        if (attributeValue == null) {
            if (isNullAllowed()) {
                if (backupAttributeValueIsNull(query)) {
                    return new DatabaseRecord(1);// both attributes are null - no update required
                } else {
                    return buildNullReferenceRow();
                }
            } else {
                throw DescriptorException.nullForNonNullAggregate(query.getObject(), this);
            }
        } else if ((query.getBackupClone() != null) && ((getMatchingBackupAttributeValue(query, attributeValue) == null) || !(attributeValue.getClass().equals(getMatchingBackupAttributeValue(query, attributeValue).getClass())))) {
            return getObjectBuilder(attributeValue, query.getSession()).buildRow(attributeValue, query.getSession());
        } else {
            if (query.getSession().isClassReadOnly(attributeValue.getClass())) {
                return new DatabaseRecord(1);
            }
            WriteObjectQuery clonedQuery = (WriteObjectQuery)query.clone();
            clonedQuery.setObject(attributeValue);
            if (query.getSession().isUnitOfWork()) {
                Object backupAttributeValue = getMatchingBackupAttributeValue(query, attributeValue);
                if (backupAttributeValue == null) {
                    backupAttributeValue = getObjectBuilder(attributeValue, query.getSession()).buildNewInstance();
                }
                clonedQuery.setBackupClone(backupAttributeValue);
            }
            return getObjectBuilder(attributeValue, query.getSession()).buildRowForUpdate(clonedQuery);
        }
    }

    /**
     * INTERNAL:
     * Clone the attribute from the original and assign it to the clone.
     */
    public void buildClone(Object original, Object clone, UnitOfWorkImpl unitOfWork) {
        Object attributeValue = getAttributeValueFromObject(original);
        Object aggregateClone = buildClonePart(original, attributeValue, unitOfWork);

        if (aggregateClone != null) {
            ClassDescriptor descriptor = getReferenceDescriptor(aggregateClone, unitOfWork);
            descriptor.getObjectChangePolicy().setAggregateChangeListener(clone, aggregateClone, unitOfWork, descriptor, getAttributeName());
        }

        setAttributeValueInObject(clone, aggregateClone);
    }

    /**
     * INTERNAL:
     * A combination of readFromRowIntoObject and buildClone.
     * <p>
     * buildClone assumes the attribute value exists on the original and can
     * simply be copied.
     * <p>
     * readFromRowIntoObject assumes that one is building an original.
     * <p>
     * Both of the above assumptions are false in this method, and actually
     * attempts to do both at the same time.
     * <p>
     * Extract value from the row and set the attribute to this value in the
     * working copy clone.
     * In order to bypass the shared cache when in transaction a UnitOfWork must
     * be able to populate working copies directly from the row.
     */
    public void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) {
        // This method is a combination of buildggregateFromRow and
        // buildClonePart on the super class.
        // none of buildClonePart used, as not an orignal new object, nor
        // do we worry about creating heavy clones for aggregate objects.
        Object clonedAttributeValue = buildAggregateFromRow(databaseRow, clone, joinManager, false, executionSession);
        ClassDescriptor descriptor = getReferenceDescriptor(clonedAttributeValue, unitOfWork);
        descriptor.getObjectChangePolicy().setAggregateChangeListener(clone, clonedAttributeValue, unitOfWork, descriptor, getAttributeName());
        setAttributeValueInObject(clone, clonedAttributeValue);
        return;
    }

    /**
     * INTERNAL:
     * Builds a shallow original object. Only direct attributes and primary
     * keys are populated. In this way the minimum original required for
     * instantiating a working copy clone can be built without placing it in
     * the shared cache (no concern over cycles).
     */
    public void buildShallowOriginalFromRow(AbstractRecord databaseRow, Object original, JoinedAttributeManager joinManager, AbstractSession executionSession) {
        Object aggregate = buildAggregateFromRow(databaseRow, original, joinManager, true, executionSession);// shallow only.
        setAttributeValueInObject(original, aggregate);
    }

    /**
     * INTERNAL:
     * Build and return a "template" database row with all the fields
     * set to null.
     */
    protected AbstractRecord buildTemplateInsertRow(AbstractSession session) {
        AbstractRecord result = getReferenceDescriptor().getObjectBuilder().buildTemplateInsertRow(session);
        List processedMappings = (List)getReferenceDescriptor().getMappings().clone();
        if (getReferenceDescriptor().hasInheritance()) {
            Enumeration children = getReferenceDescriptor().getInheritancePolicy().getChildDescriptors().elements();
            while (children.hasMoreElements()) {
                Enumeration mappings = ((ClassDescriptor)children.nextElement()).getMappings().elements();
                while (mappings.hasMoreElements()) {
                    DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement();

                    // Only write mappings once.
                    if (!processedMappings.contains(mapping)) {
                        mapping.writeInsertFieldsIntoRow(result, session);
                        processedMappings.add(mapping);
                    }
                }
            }
        }
        return result;
    }

    /**
     * INTERNAL:
     * Cascade perform delete through mappings that require the cascade
     */
    public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){
        //objects referenced by this mapping are not registered as they have
        // no identity, however mappings from the referenced object may need cascading.
        Object objectReferenced = getRealAttributeValueFromObject(object, uow);
        if ((objectReferenced == null)){
            return ;
        }
        if ( ! visitedObjects.contains(objectReferenced)){
            visitedObjects.put(objectReferenced, objectReferenced);
            ObjectBuilder builder = getReferenceDescriptor(objectReferenced.getClass(), uow).getObjectBuilder();
            builder.cascadePerformRemove(objectReferenced, uow, visitedObjects);
        }
        
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){
        //aggregate objects are not registered but their mappings should be.
        Object objectReferenced = getRealAttributeValueFromObject(object, uow);
        if ( (objectReferenced == null) ){
            return ;
        }
        if ( ! visitedObjects.contains(objectReferenced)){
            visitedObjects.put(objectReferenced, objectReferenced);
            ObjectBuilder builder = getReferenceDescriptor(objectReferenced.getClass(), uow).getObjectBuilder();
            builder.cascadeRegisterNewForCreate(objectReferenced, uow, visitedObjects);
        }
    }

    /**
     * INTERNAL:
     * Return the fields handled by the mapping.
     */
    protected Vector<DatabaseField> collectFields() {
        return getReferenceFields();
    }

    /**
     * PUBLIC:
     * If <em>all</em> the fields in the database row for the aggregate object are NULL,
     * then, by default, TopLink will place a null in the appropriate source object
     * (as opposed to an aggregate object filled with nulls). This behavior can be
     * explicitly set by calling #allowNull().
     * To change this behavior, call #dontAllowNull(). Then TopLink
     * will build a new instance of the aggregate object that is filled with nulls
     * and place it in the source object.
     * In either situation, when writing, TopLink will place a NULL in all the
     * fields in the database row for the aggregate object.
     */
    public void dontAllowNull() {
        setIsNullAllowed(false);
    }

    /**
     * INTERNAL:
     * Return a collection of the aggregate to source field name associations.
     */
    public Vector<Association> getAggregateToSourceFieldNameAssociations() {
        Vector<Association> associations = new Vector(getAggregateToSourceFieldNames().size());
        Iterator aggregateEnum = getAggregateToSourceFieldNames().keySet().iterator();
        Iterator sourceEnum = getAggregateToSourceFieldNames().values().iterator();
        while (aggregateEnum.hasNext()) {
            associations.addElement(new Association(aggregateEnum.next(), sourceEnum.next()));
        }

        return associations;
    }

    /**
     * INTERNAL:
     * Return the hashtable that stores aggregate field name to source field name.
     */
    public Map<String, String> getAggregateToSourceFieldNames() {
        return aggregateToSourceFieldNames;
    }

    /**
     * INTERNAL:
     * Return the classification for the field contained in the mapping.
     * This is used to convert the row value to a consistent Java value.
     */
    public Class getFieldClassification(DatabaseField fieldToClassify) {
        DatabaseMapping mapping = getReferenceDescriptor().getObjectBuilder().getMappingForField(fieldToClassify);
        if (mapping == null) {
            return null;// Means that the mapping is read-only
        }
        return mapping.getFieldClassification(fieldToClassify);
    }

    /**
     * INTERNAL:
     * This is used to preserve object identity during a refreshObject()
     * query. Return the object corresponding to the specified database row.
     * The default is to simply return the attribute value.
     */
    protected Object getMatchingAttributeValueFromObject(AbstractRecord row, Object targetObject, AbstractSession session, ClassDescriptor descriptor) {
        return getAttributeValueFromObject(targetObject);
    }

    /**
     * INTERNAL:
     * This is used to match up objects during an update in a UOW.
     * Return the object corresponding to the specified attribute value.
     * The default is to simply return the backup attribute value.
     */
    protected Object getMatchingBackupAttributeValue(WriteObjectQuery query, Object attributeValue) {
        return getAttributeValueFromObject(query.getBackupClone());
    }

    /**
     * INTERNAL:
     * Since aggregate object mappings clone their descriptors, for inheritance the correct child clone must be found.
     */
    protected ClassDescriptor getReferenceDescriptor(Class theClass, AbstractSession session) {
        if (getReferenceDescriptor().getJavaClass().equals(theClass)) {
            return getReferenceDescriptor();
        }

        ClassDescriptor subclassDescriptor = getReferenceDescriptor().getInheritancePolicy().getSubclassDescriptor(theClass);
        if (subclassDescriptor == null) {
            throw DescriptorException.noSubClassMatch(theClass, this);
        } else {
            return subclassDescriptor;
        }
    }

    /**
     * INTERNAL:
     * Return the fields used to build the aggregate object.
     */
    protected Vector<DatabaseField> getReferenceFields() {
        return getReferenceDescriptor().getAllFields();
    }

    /**
     * INTERNAL:
     * Return if the mapping has any ownership or other dependency over its target object(s).
     */
    public boolean hasDependency() {
        return getReferenceDescriptor().hasDependencyOnParts();
    }

    /**
     * INTERNAL:
     * For an aggregate mapping the reference descriptor is cloned. The cloned descriptor is then
     * assigned primary keys and table names before initialize. Once the cloned descriptor is initialized
     * it is assigned as reference descriptor in the aggregate mapping. This is a very specific
     * behaviour for aggregate mappings. The original descriptor is used only for creating clones and
     * after that the aggregate mapping never uses it.
     * Some initialization is done in postInitialize to ensure the target descriptor's references are initialized.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        super.initialize(session);

        ClassDescriptor clonedDescriptor = (ClassDescriptor)getReferenceDescriptor().clone();
        if (clonedDescriptor.isChildDescriptor()) {
            ClassDescriptor parentDescriptor = session.getDescriptor(clonedDescriptor.getInheritancePolicy().getParentClass());
            initializeParentInheritance(parentDescriptor, clonedDescriptor, session);
        }
        setReferenceDescriptor(clonedDescriptor);

        initializeReferenceDescriptor(clonedDescriptor);
        clonedDescriptor.preInitialize(session);
        clonedDescriptor.initialize(session);
        translateFields(clonedDescriptor, session);

        if (clonedDescriptor.hasInheritance() && clonedDescriptor.getInheritancePolicy().hasChildren()) {
            //clone child descriptors
            initializeChildInheritance(clonedDescriptor, session);
        }

        setFields(collectFields());
    }

    /**
     * INTERNAL:
     * For an aggregate mapping the reference descriptor is cloned.
     * If the reference descriptor is involved in an inheritance tree,
     * all the parent and child descriptors are cloned also.
     * The cloned descriptors are then assigned primary keys and
     * table names before initialize.
     * This is a very specific behaviour for aggregate mappings.
     */
    public void initializeChildInheritance(ClassDescriptor parentDescriptor, AbstractSession session) throws DescriptorException {
        //recursive call to the further childern descriptors
        if (parentDescriptor.getInheritancePolicy().hasChildren()) {
            //setFields(clonedChildDescriptor.getFields());
            Vector childDescriptors = parentDescriptor.getInheritancePolicy().getChildDescriptors();
            Vector cloneChildDescriptors = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            for (Enumeration enumtr = childDescriptors.elements(); enumtr.hasMoreElements();) {
                ClassDescriptor clonedChildDescriptor = (ClassDescriptor)((ClassDescriptor)enumtr.nextElement()).clone();
                clonedChildDescriptor.getInheritancePolicy().setParentDescriptor(parentDescriptor);
                initializeReferenceDescriptor(clonedChildDescriptor);
                clonedChildDescriptor.preInitialize(session);
                clonedChildDescriptor.initialize(session);
                translateFields(clonedChildDescriptor, session);
                cloneChildDescriptors.addElement(clonedChildDescriptor);
                initializeChildInheritance(clonedChildDescriptor, session);
            }
            parentDescriptor.getInheritancePolicy().setChildDescriptors(cloneChildDescriptors);
        }
    }

    /**
     * INTERNAL:
     * For an aggregate mapping the reference descriptor is cloned.
     * If the reference descriptor is involved in an inheritance tree,
     * all the parent and child descriptors are cloned also.
     * The cloned descriptors are then assigned primary keys and
     * table names before initialize.
     * This is a very specific behaviour for aggregate mappings.
     */
    public void initializeParentInheritance(ClassDescriptor parentDescriptor, ClassDescriptor childDescriptor, AbstractSession session) throws DescriptorException {
        ClassDescriptor clonedParentDescriptor = (ClassDescriptor)parentDescriptor.clone();

        //recursive call to the further parent descriptors
        if (clonedParentDescriptor.getInheritancePolicy().isChildDescriptor()) {
            ClassDescriptor parentToParentDescriptor = session.getDescriptor(clonedParentDescriptor.getJavaClass());
            initializeParentInheritance(parentToParentDescriptor, parentDescriptor, session);
        }

        initializeReferenceDescriptor(clonedParentDescriptor);
        Vector children = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        children.addElement(childDescriptor);
        clonedParentDescriptor.getInheritancePolicy().setChildDescriptors(children);
        clonedParentDescriptor.preInitialize(session);
        clonedParentDescriptor.initialize(session);
        translateFields(clonedParentDescriptor, session);
    }

    /**
     * INTERNAL:
     * Initialize the cloned reference descriptor with table names and primary keys
     */
    protected void initializeReferenceDescriptor(ClassDescriptor clonedDescriptor) {
        // Must ensure default tables remains the same.
        clonedDescriptor.setDefaultTable(getDescriptor().getDefaultTable());
        clonedDescriptor.setTables(getDescriptor().getTables());
        clonedDescriptor.setPrimaryKeyFields(getDescriptor().getPrimaryKeyFields());
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAggregateObjectMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return false;
    }
    
    /**
     * INTERNAL
     * Return true if this mapping supports cascaded version optimistic locking.
     */
    public boolean isCascadedLockingSupported() {
        return true;
    }
    
    /**
     * INTERNAL:
     * Return setting.
     */
    public boolean isNullAllowed() {
        return isNullAllowed;
    }

    /**
     * INTERNAL:
     * For an aggregate mapping the reference descriptor is cloned. The cloned descriptor is then
     * assigned primary keys and table names before initialize. Once the cloned descriptor is initialized
     * it is assigned as reference descriptor in the aggregate mapping. This is a very specific
     * behaviour for aggregate mappings. The original descriptor is used only for creating clones and
     * after that the aggregate mapping never uses it.
     * Some initialization is done in postInitialize to ensure the target descriptor's references are initialized.
     */
    public void postInitialize(AbstractSession session) throws DescriptorException {
        super.postInitialize(session);

        if (getReferenceDescriptor() != null) {
            getReferenceDescriptor().postInitialize(session);
        }
    }

    /**
     * INTERNAL:
     * Build an aggregate object from the specified return row and put it
     * in the specified target object.
     * Return row is merged into object after execution of insert or update call
     * accordiing to ReturningPolicy.
     */
    public Object readFromReturnRowIntoObject(AbstractRecord row, Object targetObject, ReadObjectQuery query, Collection handledMappings) throws DatabaseException {
        Object aggregate = getAttributeValueFromObject(targetObject);
        if (aggregate == null) {
            aggregate = readFromRowIntoObject(row, null, targetObject, query);
            handledMappings.add(this);
            return aggregate;
        }

        for (int i = 0; i < getReferenceFields().size(); i++) {
            DatabaseField field = (DatabaseField)getReferenceFields().elementAt(i);
            if (row.containsKey(field)) {
                getObjectBuilder(aggregate, query.getSession()).assignReturnValueForField(aggregate, query, row, field, handledMappings);
            }
        }

        if (isNullAllowed()) {
            boolean allAttributesNull = true;
            for (int i = 0; (i < getReferenceFields().size()) && allAttributesNull; i++) {
                DatabaseField field = (DatabaseField)fields.elementAt(i);
                if (row.containsKey(field)) {
                    allAttributesNull = row.get(field) == null;
                } else {
                    Object fieldValue = valueFromObject(targetObject, field, query.getSession());
                    if (fieldValue == null) {
                        Object baseValue = getDescriptor().getObjectBuilder().getBaseValueForField(field, targetObject);
                        if (baseValue != null) {
                            DatabaseMapping baseMapping = getDescriptor().getObjectBuilder().getBaseMappingForField(field);
                            if (baseMapping.isForeignReferenceMapping()) {
                                ForeignReferenceMapping refMapping = (ForeignReferenceMapping)baseMapping;
                                if (refMapping.usesIndirection()) {
                                    allAttributesNull = refMapping.getIndirectionPolicy().objectIsInstantiated(baseValue);
                                }
                            }
                        }
                    } else {
                        allAttributesNull = false;
                    }
                }
            }
            if (allAttributesNull) {
                aggregate = null;
                setAttributeValueInObject(targetObject, aggregate);
            }
        }
        handledMappings.add(this);
        return aggregate;
    }

    /**
     * INTERNAL:
     * Build an aggregate object from the specified row and put it
     * in the specified target object.
     */
    public Object readFromRowIntoObject(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object targetObject, ObjectBuildingQuery sourceQuery, AbstractSession executionSession) throws DatabaseException {
        Object aggregate = buildAggregateFromRow(databaseRow, targetObject, joinManager, false, executionSession);// don't just build a shallow original
        setAttributeValueInObject(targetObject, aggregate);
        return aggregate;
    }

    /**
     * INTERNAL:
     * Rehash any hashtables based on fields.
     * This is used to clone descriptors for aggregates, which hammer field names.
     */
    public void rehashFieldDependancies(AbstractSession session) {
        getReferenceDescriptor().rehashFieldDependancies(session);
    }

    /**
     * INTERNAL:
     * Set a collection of the aggregate to source field name associations.
     */
    public void setAggregateToSourceFieldNameAssociations(Vector<Association> fieldAssociations) {
        Hashtable fieldNames = new Hashtable(fieldAssociations.size() + 1);
        for (Enumeration associationsEnum = fieldAssociations.elements();
                 associationsEnum.hasMoreElements();) {
            Association association = (Association)associationsEnum.nextElement();
            fieldNames.put(association.getKey(), association.getValue());
        }

        setAggregateToSourceFieldNames(fieldNames);
    }

    /**
     * INTERNAL:
     * Set the hashtable that stores target field name to the source field name.
     */
    protected void setAggregateToSourceFieldNames(Map<String, String> aggregateToSource) {
        aggregateToSourceFieldNames = aggregateToSource;
    }

    /**
     * INTERNAL:
     * Will be used by Gromit only.
     */
    public void setIsNullAllowed(boolean aBoolean) {
        isNullAllowed = aBoolean;
    }

    /**
     * INTERNAL:
     * If field names are different in the source and aggregate objects then the translation
     * is done here. The aggregate field name is converted to source field name from the
     * field name mappings stored.
     */
    protected void translateFields(ClassDescriptor clonedDescriptor, AbstractSession session) {
        for (Enumeration entry = clonedDescriptor.getFields().elements(); entry.hasMoreElements();) {
            DatabaseField field = (DatabaseField)entry.nextElement();
            String nameInAggregate = field.getName();
            String nameInSource = (String)getAggregateToSourceFieldNames().get(nameInAggregate);

            // Do not modify non-translated fields.
            if (nameInSource != null) {
                DatabaseField fieldInSource = new DatabaseField(nameInSource);

                // Check if the translated field specified a table qualifier.
                if (fieldInSource.getName().equals(nameInSource)) {
                    // No table so just set the field name.
                    field.setName(nameInSource);
                } else {
                    // There is a table, so set the name and table.
                    field.setName(fieldInSource.getName());
                    field.setTable(clonedDescriptor.getTable(fieldInSource.getTable().getName()));
                }
            }
        }

        clonedDescriptor.rehashFieldDependancies(session);
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Write the foreign key values from the attribute to the row.
     */
    
    public void writeFromAttributeIntoRow(Object attribute, AbstractRecord row, AbstractSession session)
    {
          AbstractRecord targetRow = buildRowFromAggregate(null, attribute, session);
          for (Enumeration stream = targetRow.keys(); stream.hasMoreElements(); ) {
                  DatabaseField field = (DatabaseField) stream.nextElement();
                  Object value = targetRow.get(field);
                  row.put(field, value);
          }
    }
    
    /**
     * INTERNAL:
     * Extract value of the field from the object
     */
    public Object valueFromObject(Object object, DatabaseField field, AbstractSession session) throws DescriptorException {
        Object attributeValue = getAttributeValueFromObject(object);
        if (attributeValue == null) {
            if (isNullAllowed()) {
                return null;
            } else {
                throw DescriptorException.nullForNonNullAggregate(object, this);
            }
        } else {
            return getObjectBuilder(attributeValue, session).extractValueFromObjectForField(attributeValue, field, session);
        }
    }

    /**
     * INTERNAL:
     * Get the attribute value from the object and add the appropriate
     * values to the specified database row.
     */
    public void writeFromObjectIntoRow(Object object, AbstractRecord databaseRow, AbstractSession session) throws DescriptorException {
        if (isReadOnly()) {
            return;
        }
        AbstractRecord targetRow = buildRowFromAggregate(object, getAttributeValueFromObject(object), session);
        for (Enumeration stream = targetRow.keys(); stream.hasMoreElements();) {
            DatabaseField field = (DatabaseField)stream.nextElement();
            Object value = targetRow.get(field);
            databaseRow.add(field, value);
        }
    }

    /**
     * INTERNAL:
     * Get the attribute value from the object and add the appropriate
     * values to the specified database row.
     */
    public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord databaseRow, AbstractSession session) throws DescriptorException {
        if (isReadOnly()) {
            return;
        }
        AbstractRecord targetRow = buildRowFromAggregateWithChangeRecord(changeRecord, (ObjectChangeSet)((AggregateChangeRecord)changeRecord).getChangedObject(), session);
        for (Enumeration stream = targetRow.keys(); stream.hasMoreElements();) {
            DatabaseField field = (DatabaseField)stream.nextElement();
            Object value = targetRow.get(field);
            databaseRow.add(field, value);
        }
    }

    /**
     * INTERNAL:
     * Get the attribute value from the object and add the changed
     * values to the specified database row.
     */
    public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord databaseRow) throws DescriptorException {
        if (isReadOnly()) {
            return;
        }
        AbstractRecord targetRow = buildRowFromAggregateForUpdate(query, getAttributeValueFromObject(query.getObject()));
        for (Enumeration stream = targetRow.keys(); stream.hasMoreElements();) {
            DatabaseField field = (DatabaseField)stream.nextElement();
            Object value = targetRow.get(field);
            databaseRow.add(field, value);
        }
    }

    /**
     * INTERNAL:
     * Write fields needed for insert into the template for with null values.
     */
    public void writeInsertFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) {
        if (isReadOnly()) {
            return;
        }

        AbstractRecord targetRow = buildTemplateInsertRow(session);
        for (Enumeration keyEnum = targetRow.keys(); keyEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)keyEnum.nextElement();
            Object value = targetRow.get(field);

            //CR-3286097 - Should use add not put, to avoid linear search.
            databaseRow.add(field, value);
        }
    }

    /**
     * INTERNAL:
     * Add a primary key join column (secondary field).
     * If this contain primary keys and the descriptor(or its subclass) has multiple tables
     * (secondary tables or joined inheritance strategy), this should also know the primary key
     * join columns to handle some cases properly.
     */
    public void addPrimaryKeyJoinField(DatabaseField primaryKeyField, DatabaseField secondaryField) {
        // now it doesn't need to manage this as a separate table here,
        // it's enough just to add the mapping to ObjectBuilder.mappingsByField
        ObjectBuilder builder = getReferenceDescriptor().getObjectBuilder();
        DatabaseMapping mapping = builder.getMappingForField(primaryKeyField);
        if (mapping != null) {
            builder.getMappingsByField().put(secondaryField, mapping);
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;
import java.util.*;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p>
 * <b>Purpose</b>: This class holds the record of the changes made to a collection attribute of
 * an object.
 * <p>
 * <b>Description</b>: Collections must be compared to each other and added and removed objects must
 * be recorded seperately
 * @see OtherRelatedClasses prototype.changeset.DirectToFieldChangeRecord,prototype.changeset.SingleObjectChangeRecord
 */
public class CollectionChangeRecord extends ChangeRecord implements oracle.toplink.essentials.changesets.CollectionChangeRecord {

    /**
     * Contains the added values to the collection and their corresponding ChangeSets.
     */
    protected HardIdentityHashtable addObjectList;
    
    /**
     * Contains the added values to the collection and their corresponding ChangeSets in order.
     */
    protected transient Vector orderedAddObjects;
    
    /**
     * Contains the added values index to the collection.
     */
    protected HardIdentityHashtable orderedAddObjectIndices;
    
    /**
     * Contains the removed values to the collection and their corresponding ChangeSets.
     */
    protected Hashtable orderedRemoveObjects;
    
    /**
     * Contains the removed values index to the collection.
     */
    protected transient Vector orderedRemoveObjectIndices;
    
    /**
     * Contains a list of extra adds. These extra adds are used by attribute change tracking
     * to replicate behaviour when someone adds the same object to a list and removes it once
     * In this case the object should still appear once in the change set.
     */
     protected transient List addOverFlow;

    /**
     * Contains the removed values from the collection and their corresponding ChangeSets.
     */
    protected HardIdentityHashtable removeObjectList;

    /**
     * Contain the same added values as in addObjectList. It is only used by SDK mapping of this change record.
     */
    protected transient Vector sdkAddObjects;

    /**
     * Contain the same added values as in addObjectList. It is only used by SDK mapping of this change record.
     */
    protected transient Vector sdkRemoveObjects;
    
    /**
     * Used for change tracking when customer sets entire collection
     */
    protected transient Object originalCollection;

    /**
     * Used for change tracking when customer sets entire collection
     */
    protected transient Object latestCollection;

    /**
     * This default constructor is reference internally by SDK XML project to mapp this class
     */
    public CollectionChangeRecord() {
        super();
    }

    /**
     * Constructor for the ChangeRecord representing a collection mapping
     * @param owner prototype.changeset.ObjectChangeSet the changeSet that uses this record
     */
    public CollectionChangeRecord(ObjectChangeSet owner) {
        this.owner = owner;
    }
    
    /**
     * This method takes a HardIdentityHashtable of objects, converts these into
     * ObjectChangeSets.
     * @param objectChanges prototype.changeset.ObjectChangeSet
     */
    public void addAdditionChange(IdentityHashMap objectChanges, UnitOfWorkChangeSet changeSet, AbstractSession session) {
        Iterator enumtr = objectChanges.keySet().iterator();
        while (enumtr.hasNext()) {
            Object object = enumtr.next();
            ObjectChangeSet change = session.getDescriptor(object.getClass()).getObjectBuilder().createObjectChangeSet(object, changeSet, session);
            if (change.hasKeys()){
                // if change set has keys this is a map comparison. Maps are
                // not support in change tracking so do not need to prevent duplicates
                // when map support is added this will have to be refactored
                getAddObjectList().put(change, change);
            }else{
                if (getRemoveObjectList().contains(change)){
                    getRemoveObjectList().remove(change);
                }else{
                    getAddObjectList().put(change, change);
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * This method takes a Vector of objects and converts them into
     * ObjectChangeSets. This method should only be called from a
     * ListContainerPolicy. Additions to the list are made by index, hence,
     * the second HardIdentityHashtable of objectChangesIndices.
     */
    public void addOrderedAdditionChange(Vector objectChanges, HardIdentityHashtable objectChangesIndices, UnitOfWorkChangeSet changeSet, AbstractSession session) {
        Enumeration e = objectChanges.elements();
        
        while (e.hasMoreElements()) {
            Object object = e.nextElement();
            ObjectChangeSet change = session.getDescriptor(object.getClass()).getObjectBuilder().createObjectChangeSet(object, changeSet, session);
            
            getOrderedAddObjects().add(change);
            getOrderedAddObjectIndices().put(change, (Integer) objectChangesIndices.get(object));
        }
    }
    
    /**
     * INTERNAL:
     * This method takes a Hashtable of objects and converts them into
     * ObjectChangeSets. This method should only be called from a
     * ListContainerPolicy. Deletions from the list is made by index, hence,
     * the second Vector of indicesToRemove.
     */
    public void addOrderedRemoveChange(Vector indicesToRemove, Hashtable objectChanges, UnitOfWorkChangeSet changeSet, AbstractSession session) {
        orderedRemoveObjectIndices = indicesToRemove;
        Enumeration e = orderedRemoveObjectIndices.elements();
        
        while (e.hasMoreElements()) {
            Integer index = (Integer) e.nextElement();
            Object object = objectChanges.get(index);
            ObjectChangeSet change = session.getDescriptor(object.getClass()).getObjectBuilder().createObjectChangeSet(object, changeSet, session);

            getOrderedRemoveObjects().put(index, change);
        }
    }

    /**
     * This method takes a HardIdentityHashtable of objects, converts these into ObjectChangeSets.
     * @param objectChanges prototype.changeset.ObjectChangeSet
     */
    public void addRemoveChange(IdentityHashMap objectChanges, UnitOfWorkChangeSet changeSet, AbstractSession session) {
        // There is no need to keep track of removed new objects because it will not be in the backup,
        // It will not be in the backup because it is new.
        Iterator enumtr = objectChanges.keySet().iterator();
        while (enumtr.hasNext()) {
            Object object = enumtr.next();
            ClassDescriptor descriptor = session.getDescriptor(object.getClass());
            ObjectChangeSet change = descriptor.getObjectBuilder().createObjectChangeSet(object, changeSet, session);
            if (change.hasKeys()){
                // if change set has keys this is a map comparison. Maps are
                // not support in change tracking so do not need to prevent duplicates
                // when map support is added this will have to be refactored
                getRemoveObjectList().put(change, change);
            }else{
                if (getAddObjectList().contains(change)){
                    getAddObjectList().remove(change);
                }else{
                    getRemoveObjectList().put(change, change);
                }
            }
        }
    }

    /**
     * ADVANCED:
     * This method returns the collection of ChangeSets that were added to the collection.
     * @return java.util.Vector
     */
    public HardIdentityHashtable getAddObjectList() {
        if (addObjectList == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            addObjectList = new HardIdentityHashtable();
        }
        return addObjectList;
    }

     /**
     * ADVANCED:
     * This method returns the collection of ChangeSets that were added to the collection.
     * @return java.util.Vector
     */
    public List getAddOverFlow() {
        if (addOverFlow == null) {
            addOverFlow = new ArrayList();
        }
        return addOverFlow;
    }

   /**
     * ADVANCED:
     * This method returns the HardIdentityHashtable that contains the removed values from the collection
     * and their corresponding ChangeSets.
     * @return java.util.Vector
     */
    public HardIdentityHashtable getRemoveObjectList() {
        if (removeObjectList == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            removeObjectList = new HardIdentityHashtable();
        }
        return removeObjectList;
    }

    /**
     * returns true if the change set has changes
     */
    public boolean hasChanges() {
        return (!( getAddObjectList().isEmpty() &&
                    getRemoveObjectList().isEmpty() &&
                    getOrderedAddObjects().isEmpty() &&
                    getOrderedRemoveObjects().isEmpty()))
                || getOwner().isNew();
    }

    /**
     * INTERNAL:
     * This method will be used to merge one record into another
     */
    public void mergeRecord(ChangeRecord mergeFromRecord, UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) {
        Enumeration addEnum = ((CollectionChangeRecord)mergeFromRecord).getAddObjectList().keys();
        while (addEnum.hasMoreElements()) {
            ObjectChangeSet mergingObject = (ObjectChangeSet)addEnum.nextElement();
            ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(mergingObject, mergeFromChangeSet);
            if (getRemoveObjectList().containsKey(localChangeSet)) {
                getRemoveObjectList().remove(localChangeSet);
            } else {
                getAddObjectList().put(localChangeSet, localChangeSet);
            }
        }
        Enumeration removeEnum = ((CollectionChangeRecord)mergeFromRecord).getRemoveObjectList().keys();
        while (removeEnum.hasMoreElements()) {
            ObjectChangeSet mergingObject = (ObjectChangeSet)removeEnum.nextElement();
            ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(mergingObject, mergeFromChangeSet);
            if (getAddObjectList().containsKey(localChangeSet)) {
                getAddObjectList().remove(localChangeSet);
            } else {
                getRemoveObjectList().put(localChangeSet, localChangeSet);
            }
        }
    }

    /**
     * Sets the Added objects list
     * @param newValue java.util.Vector
     */
    public void setAddObjectList(HardIdentityHashtable objectChangesList) {
        this.addObjectList = objectChangesList;
    }

    /**
     * Sets the removed objects list
     * @param newValue java.util.Vector
     */
    public void setRemoveObjectList(HardIdentityHashtable objectChangesList) {
        this.removeObjectList = objectChangesList;
    }
    
    /**
     * INTERNAL:
     * This method will be used to update the objectsChangeSets references
     */
    public void updateReferences(UnitOfWorkChangeSet mergeToChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) {
        HardIdentityHashtable addList = new HardIdentityHashtable(getAddObjectList().size());
        HardIdentityHashtable removeList = new HardIdentityHashtable(getRemoveObjectList().size());
        
        // If we have ordered lists we need to iterate through those.
        if (getOrderedAddObjects().size() > 0 || getOrderedRemoveObjectIndices().size() > 0) {
            // Do the ordered adds first ...
            Vector orderedAddList = new Vector(getOrderedAddObjects().size());
            HardIdentityHashtable orderedAddListIndices = new HardIdentityHashtable(getOrderedAddObjectIndices().size());
            
            for (int i = 0; i < getOrderedAddObjects().size(); i++) {
                ObjectChangeSet changeSet = (ObjectChangeSet) getOrderedAddObjects().elementAt(i);
                ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(changeSet, mergeFromChangeSet);
                
                orderedAddList.add(localChangeSet);
                orderedAddListIndices.put(localChangeSet, getOrderedAddObjectIndices().get(changeSet));
                
                // Object was actually added and not moved.
                if (getAddObjectList().contains(changeSet)) {
                    addList.put(localChangeSet, localChangeSet);
                }
            }
            
            setOrderedAddObjects(orderedAddList);
            setOrderedAddObjectIndices(orderedAddListIndices);
            
            // Do the ordered removes now ...
            Hashtable orderedRemoveList = new Hashtable(getOrderedRemoveObjects().size());
            Enumeration changes = getOrderedRemoveObjects().keys();
            
            while (changes.hasMoreElements()) {
                Object index = changes.nextElement();
                ObjectChangeSet changeSet = (ObjectChangeSet) getOrderedRemoveObjects().get(index);
                ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet(changeSet, mergeFromChangeSet);
                
                orderedRemoveList.put(index, localChangeSet);
                
                // Object was actually removed and not moved.
                if (getRemoveObjectList().contains(changeSet)) {
                    removeList.put(localChangeSet, localChangeSet);
                }
            }
            
            setOrderedRemoveObjects(orderedRemoveList);
            // Don't need to worry about the vector of indices (Integer's), just leave them as is.
        } else {
            Enumeration changes = getAddObjectList().elements();
            while (changes.hasMoreElements()) {
                ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet((ObjectChangeSet)changes.nextElement(), mergeFromChangeSet);
                addList.put(localChangeSet, localChangeSet);
            }
        
            changes = getRemoveObjectList().elements();
            while (changes.hasMoreElements()) {
                ObjectChangeSet localChangeSet = mergeToChangeSet.findOrIntegrateObjectChangeSet((ObjectChangeSet)changes.nextElement(), mergeFromChangeSet);
                removeList.put(localChangeSet, localChangeSet);
            }
        }
        
        setAddObjectList(addList);
        setRemoveObjectList(removeList);
    }

    /**
     * INTERNAL:
     * This method used by SDK mapping that only supports Collection type not HardIdentityHashtable.
     * This method is mapped in oracle.toplink.essentials.internal.remotecommand.CommandProject
     * @return java.util.Vector
     */
    public Vector getAddObjectsForSDK() {
        if (sdkAddObjects == null) {
            sdkAddObjects = new Vector();

            for (Enumeration enumtr = this.getAddObjectList().keys(); enumtr.hasMoreElements();) {
                sdkAddObjects.add(enumtr.nextElement());
            }
        }
        return sdkAddObjects;
    }

    /**
     * INTERNAL:
     * This method used by SDK mapping that only supports Collection type not HardIdentityHashtable.
     * This method is mapped in oracle.toplink.essentials.internal.remotecommand.CommandProject
     * @param java.util.Vector addObjects
     */
    public void setAddObjectsForSDK(Vector addObjects) {
        sdkAddObjects = addObjects;

        // build the equivalent addObjectList
        HardIdentityHashtable newList = new HardIdentityHashtable();
        for (int i = 0; i < sdkAddObjects.size(); i++) {
            Object change = sdkAddObjects.elementAt(i);
            newList.put(change, change);
        }
        this.setAddObjectList(newList);
    }

    /**
     * INTERNAL:
     * This method used by SDK mapping that only supports Collection type not HardIdentityHashtable.
     * This method is mapped in oracle.toplink.essentials.internal.remotecommand.CommandProject
     * @return java.util.Vector
     */
    public Vector getRemoveObjectsForSDK() {
        if (sdkRemoveObjects == null) {
            sdkRemoveObjects = new Vector();

            for (Enumeration enumtr = this.getRemoveObjectList().keys(); enumtr.hasMoreElements();) {
                sdkRemoveObjects.add(enumtr.nextElement());
            }
        }
        return sdkRemoveObjects;
    }

    /**
     * INTERNAL:
     * This method used by SDK mapping that only supports Collection type not HardIdentityHashtable.
     * This method is mapped in oracle.toplink.essentials.internal.remotecommand.CommandProject
     * @param java.util.Vector removeObjects
     */
    public void setRemoveObjectsForSDK(Vector removeObjects) {
        sdkRemoveObjects = removeObjects;

        // build the equivalent removeObjectList
        HardIdentityHashtable newList = new HardIdentityHashtable();
        for (int i = 0; i < sdkRemoveObjects.size(); i++) {
            Object change = sdkRemoveObjects.elementAt(i);
            newList.put(change, change);
        }
        this.setRemoveObjectList(newList);
    }

    /**
     * Used for change tracking when cutomer sets entire collection
     * This is the last collection that was set on the object
     */
    public Object getLatestCollection() {
        return latestCollection;
    }
    
    /**
     * ADVANCED:
     * This method returns the collection of ChangeSets in the order they were
     * added to the collection. This list includes those objects that were
     * moved within the collection.
     */
    public Vector getOrderedAddObjects() {
        if (orderedAddObjects == null) {
            orderedAddObjects = new Vector();
        }
        
        return orderedAddObjects;
    }
    
    /**
     * ADVANCED:
     * This method returns the index of an object added to the collection.
     */
    public Integer getOrderedAddObjectIndex(ObjectChangeSet changes) {
        return (Integer) getOrderedAddObjectIndices().get(changes);
    }
    
    /**
     * ADVANCED:
     * This method returns the collection of ChangeSets that they were
     * added to the collection.
     */
    public HardIdentityHashtable getOrderedAddObjectIndices() {
        if (orderedAddObjectIndices == null) {
            orderedAddObjectIndices = new HardIdentityHashtable();
        }
        
        return orderedAddObjectIndices;
    }
    
    /**
     * ADVANCED:
     * This method returns the ordered list of indices to remove from the
     * collection.
     */
    public Vector getOrderedRemoveObjectIndices() {
        if (orderedRemoveObjectIndices == null) {
            orderedRemoveObjectIndices = new Vector();
        }
        
        return orderedRemoveObjectIndices;
    }
    
    /**
     * ADVANCED:
     * This method returns the index of an object removed from the collection.
     */
    public Object getOrderedRemoveObject(Integer index) {
        return getOrderedRemoveObjects().get(index);
    }
    
    /**
     * ADVANCED:
     * This method returns the collection of ChangeSets of objects removed from
     * the collection.
     */
    public Hashtable getOrderedRemoveObjects() {
        if (orderedRemoveObjects == null) {
            orderedRemoveObjects = new Hashtable();
        }
        
        return orderedRemoveObjects;
    }

    /**
     * Used for change tracking when cutomer sets entire collection
     * This is the last collection that was set on the object
     */
    public void setLatestCollection(Object latestCollection) {
        this.latestCollection = latestCollection;
    }
    
    /**
     * ADVANCED:
     * Sets collection of ChangeSets (and their respective index) that they
     * were added to the collection.
     */
    public void setOrderedAddObjectIndices(HardIdentityHashtable orderedAddObjectIndices) {
        this.orderedAddObjectIndices = orderedAddObjectIndices;
    }
    
    /**
     * ADVANCED:
     * Sets collection of ChangeSets that they were added to the collection.
     */
    public void setOrderedAddObjects(Vector orderedAddObjects) {
        this.orderedAddObjects = orderedAddObjects;
    }
    
    /**
     * ADVANCED:
     * Sets collection of ChangeSets that they were remvoved from the collection.
     */
    public void setOrderedRemoveObjects(Hashtable orderedRemoveObjects) {
        this.orderedRemoveObjects = orderedRemoveObjects;
    }

    /**
     * Used for change tracking when cutomer sets entire collection
     * This is the original collection that was set on the object when it was cloned
     */
    public Object getOriginalCollection() {
        return originalCollection;
    }

    /**
     * Used for change tracking when cutomer sets entire collection
     * This is the original collection that was set on the object when it was cloned
     */
    public void setOriginalCollection(Object originalCollection) {
        this.originalCollection = originalCollection;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.indirection.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.indirection.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.expressions.Expression;
import oracle.toplink.essentials.expressions.ExpressionBuilder;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: Abstract class for relationship mappings which store collection of objects
 *
 * @author Sati
 * @since TOPLink/Java 1.0
 */
public abstract class CollectionMapping extends ForeignReferenceMapping implements ContainerMapping {
    /** Used for delete all in m-m, dc and delete all optimization in 1-m. */
    protected transient ModifyQuery deleteAllQuery;
    protected transient boolean hasCustomDeleteAllQuery;
    protected ContainerPolicy containerPolicy;
    protected transient boolean hasOrderBy;

    /**
     * PUBLIC:
     * Default constructor.
     */
    public CollectionMapping() {
        this.selectionQuery = new ReadAllQuery();
        this.hasCustomDeleteAllQuery = false;
        this.containerPolicy = ContainerPolicy.buildPolicyFor(ClassConstants.Vector_class);
        this.hasOrderBy = false;
    }
    
    /**
     * PUBLIC:
     * Provide order support for queryKeyName in ascending order
     */
    public void addAscendingOrdering(String queryKeyName) {
        if (queryKeyName == null) {
            return;
        }
        
        ((ReadAllQuery)getSelectionQuery()).addAscendingOrdering(queryKeyName);
    }
    
    /**
     * PUBLIC:
     * Provide order support for queryKeyName in descending order.
     */
    public void addDescendingOrdering(String queryKeyName) {
        if (queryKeyName == null) {
            return;
        }
        
        ((ReadAllQuery)getSelectionQuery()).addDescendingOrdering(queryKeyName);
    }
    
    /**
     * PUBLIC:
     * Provide order support for queryKeyName in descending or ascending order.
     * Called from the EJBAnnotationsProcessor when an @OrderBy is found.
     */
    public void addOrderBy(String queryKeyName, boolean isDescending) {
        this.hasOrderBy = true;
        
        if (isDescending) {
            addDescendingOrdering(queryKeyName);
        } else {
            addAscendingOrdering(queryKeyName);
        }
    }
    
    /**
     * PUBLIC:
     * Provide order support for queryKeyName in ascending order.
     * Called from the EJBAnnotationsProcessor when an @OrderBy on an
     * aggregate is found.
     */
    public void addAggregateOrderBy(String aggregateName, String queryKeyName, boolean isDescending) {
        this.hasOrderBy = true;
        
        ReadAllQuery readAllQuery = (ReadAllQuery) getSelectionQuery();
        ExpressionBuilder builder = readAllQuery.getExpressionBuilder();
        Expression expression = builder.get(aggregateName).get(queryKeyName).toUpperCase();
        
        if (isDescending) {
            readAllQuery.addOrdering(expression.descending());
        } else {
            readAllQuery.addOrdering(expression.ascending());
        }
    }

    /**
     * INTERNAL:
     * Used during building the backup shallow copy to copy
     * the vector without re-registering the target objects.
     */
    public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
        // Check for null
        if (attributeValue == null) {
            return getContainerPolicy().containerInstance(1);
        } else {
            return getContainerPolicy().cloneFor(attributeValue);
        }
    }

    /**
     * INTERNAL:
     * Require for cloning, the part must be cloned.
     * Ignore the objects, use the attribute value.
     */
    public Object buildCloneForPartObject(Object attributeValue, Object original, Object clone, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        ContainerPolicy containerPolicy = getContainerPolicy();
        if (attributeValue == null) {
            Object container = containerPolicy.containerInstance(1);
            return container;
        }
        Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue));

        // I need to synchronize here to prevent the collection from changing while I am cloning it.
        // This will occur when I am merging into the cache and I am instantiating a UOW valueHolder at the same time
        // I can not synchronize around the clone, as this will cause deadlocks, so I will need to copy the collection then create the clones
        // I will use a temporary collection to help speed up the process
        Object temporaryCollection = null;
        synchronized (attributeValue) {
            temporaryCollection = containerPolicy.cloneFor(attributeValue);
        }
        for (Object valuesIterator = containerPolicy.iteratorFor(temporaryCollection);
                 containerPolicy.hasNext(valuesIterator);) {
            Object cloneValue = buildElementClone(containerPolicy.next(valuesIterator, unitOfWork), unitOfWork, isExisting);
            containerPolicy.addInto(cloneValue, clonedAttributeValue, unitOfWork);
        }
        return clonedAttributeValue;
    }

    /**
     * INTERNAL:
     * Copy of the attribute of the object.
     * This is NOT used for unit of work but for templatizing an object.
     */
    public void buildCopy(Object copy, Object original, ObjectCopyingPolicy policy) {
        Object attributeValue = getRealCollectionAttributeValueFromObject(original, policy.getSession());
        Object valuesIterator = getContainerPolicy().iteratorFor(attributeValue);
        attributeValue = getContainerPolicy().containerInstance(getContainerPolicy().sizeFor(attributeValue));
        while (getContainerPolicy().hasNext(valuesIterator)) {
            Object originalValue = getContainerPolicy().next(valuesIterator, policy.getSession());
            Object copyValue = originalValue;
            if (policy.shouldCascadeAllParts() || (policy.shouldCascadePrivateParts() && isPrivateOwned())) {
                copyValue = policy.getSession().copyObject(originalValue, policy);
            } else {
                // Check for backrefs to copies.
                copyValue = policy.getCopies().get(originalValue);
                if (copyValue == null) {
                    copyValue = originalValue;
                }
            }
            getContainerPolicy().addInto(copyValue, attributeValue, policy.getSession());
        }
        setRealAttributeValueInObject(copy, attributeValue);
    }

    /**
     * INTERNAL:
     * Clone the element, if necessary.
     */
    protected Object buildElementClone(Object element, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        // optimize registration to knowledge of existence
        if (isExisting) {
            return unitOfWork.registerExistingObject(element);
        } else {// not known whether existing or not
            return unitOfWork.registerObject(element);
        }
    }

    /**
     * INTERNAL:
     * Cascade perform delete through mappings that require the cascade
     */
    public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){
        Object cloneAttribute = null;
        cloneAttribute = getAttributeValueFromObject(object);
        if ((cloneAttribute == null) || (!this.isCascadeRemove())) {
            return;
        }

        ContainerPolicy cp = getContainerPolicy();
        Object cloneObjectCollection = null;
        cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow);
        Object cloneIter = cp.iteratorFor(cloneObjectCollection);
        while (cp.hasNext(cloneIter)) {
            Object nextObject = cp.next(cloneIter, uow);
            if (nextObject != null && (! visitedObjects.contains(nextObject)) ){
                visitedObjects.put(nextObject, nextObject);
                uow.performRemove(nextObject, visitedObjects);
            }
        }
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){
        Object cloneAttribute = null;
        cloneAttribute = getAttributeValueFromObject(object);
        if ((cloneAttribute == null) || (!this.isCascadePersist()) || (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            return;
        }

        ContainerPolicy cp = getContainerPolicy();
        Object cloneObjectCollection = null;
        cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow);
        Object cloneIter = cp.iteratorFor(cloneObjectCollection);
        while (cp.hasNext(cloneIter)) {
            Object nextObject = cp.next(cloneIter, uow);
            uow.registerNewObjectForPersist(nextObject, visitedObjects);
        }
    }
    
    /**
     * INTERNAL:
     * Common validation for a collection mapping using a Map class.
     */
    private void checkMapClass(Class concreteClass) {
        // the reference class has to be specified before coming here
        if (getReferenceClass() == null) {
            throw DescriptorException.referenceClassNotSpecified(this);
        }
        
        if (! Helper.classImplementsInterface(concreteClass, ClassConstants.Map_Class)) {
            throw ValidationException.illegalContainerClass(concreteClass);
        }
    }

    /**
     * INTERNAL:
     * Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes
     * as apposed to detected changes. If an attribute can not be change tracked it's
     * changes can be detected through this process.
     */
    public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session){
        CollectionChangeRecord collectionRecord = (CollectionChangeRecord) changeRecord;
        //clear incase events were fired since the set of the collection
// collectionRecord.getAddObjectList().clear();
// collectionRecord.getRemoveObjectList().clear();
        compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session);
    }

    /**
     * INTERNAL:
     * Cascade the merge to the component object, if appropriate.
     */
    public void cascadeMerge(Object sourceElement, MergeManager mergeManager) {
        if (shouldMergeCascadeParts(mergeManager)) {
            mergeManager.mergeChanges(mergeManager.getObjectToMerge(sourceElement), null);
        }
    }

    /**
     * INTERNAL:
     * This method is used to calculate the differences between two collections.
     * It is passed to the container policy to calculate the changes.
     */
    public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session) {
        getContainerPolicy().compareCollectionsForChange(oldCollection, newCollection, (CollectionChangeRecord) changeRecord, session, getReferenceDescriptor());
    }
    
    /**
     * INTERNAL:
     * This method is used to create a change record from comparing two collections
     * @return prototype.changeset.ChangeRecord
     */
    public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
        Object cloneAttribute = null;
        Object backUpAttribute = null;

        Object backUpObjectCollection = null;

        cloneAttribute = getAttributeValueFromObject(clone);

        if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            return null;
        }


        if (!owner.isNew()) {// if the changeSet is for a new object then we must record all off the attributes
            backUpAttribute = getAttributeValueFromObject(backUp);

            if ((cloneAttribute == null) && (backUpAttribute == null)) {
                return null;
            }

            backUpObjectCollection = getRealCollectionAttributeValueFromObject(backUp, session);
       }

        Object cloneObjectCollection = null;
        if (cloneAttribute != null) {
            cloneObjectCollection = getRealCollectionAttributeValueFromObject(clone, session);
        } else {
            cloneObjectCollection = getContainerPolicy().containerInstance(1);
        }

        CollectionChangeRecord changeRecord = new CollectionChangeRecord(owner);
        changeRecord.setAttribute(getAttributeName());
        changeRecord.setMapping(this);
        compareCollectionsForChange(backUpObjectCollection, cloneObjectCollection, changeRecord, session);
        if (changeRecord.hasChanges()) {
            return changeRecord;
        }
        return null;
    }

    /**
     * INTERNAL:
     * Compare the attributes belonging to this mapping for the objects.
     */
    public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
        Object firstObjectCollection = getRealCollectionAttributeValueFromObject(firstObject, session);
        Object secondObjectCollection = getRealCollectionAttributeValueFromObject(secondObject, session);

        return super.compareObjects(firstObjectCollection, secondObjectCollection, session);
    }

    /**
     * INTERNAL:
     * The memory objects are compared and only the changes are written to the database
     */
    protected void compareObjectsAndWrite(Object previousObjects, Object currentObjects, WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        ContainerPolicy cp = getContainerPolicy();
        
        // If it is for an aggregate collection let it continue so that all of
        // the correct values are deleted and then re-added This could be
        // changed to make AggregateCollection changes smarter.
        if ((query.getObjectChangeSet() != null) && !this.isAggregateCollectionMapping()) {
            ObjectChangeSet changeSet = query.getObjectChangeSet();
            CollectionChangeRecord record = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
            
            if (record != null) {
                ObjectChangeSet removedChangeSet = null;
                ObjectChangeSet addedChangeSet = null;
                UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)changeSet.getUOWChangeSet();
                Enumeration removedObjects = record.getRemoveObjectList().elements();
                
                while (removedObjects.hasMoreElements()) {
                    removedChangeSet = (ObjectChangeSet)removedObjects.nextElement();
                    objectRemovedDuringUpdate(query, removedChangeSet.getUnitOfWorkClone());
                }

                Enumeration addedObjects = record.getAddObjectList().elements();
                
                while (addedObjects.hasMoreElements()) {
                    addedChangeSet = (ObjectChangeSet)addedObjects.nextElement();
                    objectAddedDuringUpdate(query, addedChangeSet.getUnitOfWorkClone(), addedChangeSet);
                }
            }
            
            return;
        }

        Hashtable previousObjectsByKey = new Hashtable(cp.sizeFor(previousObjects) + 2);// Read from db or from backup in uow.
        Hashtable currentObjectsByKey = new Hashtable(cp.sizeFor(currentObjects) + 2);// Current value of object's attribute (clone in uow).

        IdentityHashtable cacheKeysOfCurrentObjects = new HardIdentityHashtable(cp.sizeFor(currentObjects) + 1);

        // First index the current objects by their primary key.
        for (Object currentObjectsIter = cp.iteratorFor(currentObjects);
                 cp.hasNext(currentObjectsIter);) {
            Object currentObject = cp.next(currentObjectsIter, query.getSession());
            try {
                Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(currentObject, query.getSession());
                CacheKey key = new CacheKey(primaryKey);
                currentObjectsByKey.put(key, currentObject);
                cacheKeysOfCurrentObjects.put(currentObject, key);
            } catch (NullPointerException e) {
                // For CR#2646 quietly discard nulls added to a collection mapping.
                // This try-catch is essentially a null check on currentObject, for
                // ideally the customer should check for these themselves.
                if (currentObject != null) {
                    throw e;
                }
            }
        }

        // Next index the previous objects (read from db or from backup in uow)
        // and process the difference to current (optimized in same loop).
        for (Object previousObjectsIter = cp.iteratorFor(previousObjects);
                 cp.hasNext(previousObjectsIter);) {
            Object previousObject = cp.next(previousObjectsIter, query.getSession());
            Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(previousObject, query.getSession());
            CacheKey key = new CacheKey(primaryKey);
            previousObjectsByKey.put(key, previousObject);

            // Delete must occur first, incase object with same pk is removed and added,
            // (technically should not happen, but same applies to unquie constainsts)
            if (!currentObjectsByKey.containsKey(key)) {
                objectRemovedDuringUpdate(query, previousObject);
            }
        }

        for (Object currentObjectsIter = cp.iteratorFor(currentObjects);
                 cp.hasNext(currentObjectsIter);) {
            Object currentObject = cp.next(currentObjectsIter, query.getSession());
            try {
                CacheKey cacheKey = (CacheKey)cacheKeysOfCurrentObjects.get(currentObject);

                if (!(previousObjectsByKey.containsKey(cacheKey))) {
                    objectAddedDuringUpdate(query, currentObject, null);
                } else {
                    objectUnchangedDuringUpdate(query, currentObject, previousObjectsByKey, cacheKey);
                }
            } catch (NullPointerException e) {
                // For CR#2646 skip currentObject if it is null.
                if (currentObject != null) {
                    throw e;
                }
            }
        }
    }

    /**
     * Compare two objects if their parts are not private owned
     */
    protected boolean compareObjectsWithoutPrivateOwned(Object firstCollection, Object secondCollection, AbstractSession session) {
        ContainerPolicy cp = getContainerPolicy();
        if (cp.sizeFor(firstCollection) != cp.sizeFor(secondCollection)) {
            return false;
        }

        Object firstIter = cp.iteratorFor(firstCollection);
        Object secondIter = cp.iteratorFor(secondCollection);

        Vector keyValue = new Vector();

        while (cp.hasNext(secondIter)) {
            Object secondObject = cp.next(secondIter, session);
            Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session);
            keyValue.addElement(new CacheKey(primaryKey));
        }

        while (cp.hasNext(firstIter)) {
            Object firstObject = cp.next(firstIter, session);
            Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session);

            if (!keyValue.contains(new CacheKey(primaryKey))) {
                return false;
            }
        }
        return true;
    }

    /**
     * Compare two objects if their parts are private owned
     */
    protected boolean compareObjectsWithPrivateOwned(Object firstCollection, Object secondCollection, AbstractSession session) {
        ContainerPolicy cp = getContainerPolicy();
        if (cp.sizeFor(firstCollection) != cp.sizeFor(secondCollection)) {
            return false;
        }

        Object firstIter = cp.iteratorFor(firstCollection);
        Object secondIter = cp.iteratorFor(secondCollection);

        Hashtable keyValueToObject = new Hashtable(cp.sizeFor(firstCollection) + 2);
        CacheKey cacheKey;

        while (cp.hasNext(secondIter)) {
            Object secondObject = cp.next(secondIter, session);
            Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondObject, session);
            keyValueToObject.put(new CacheKey(primaryKey), secondObject);
        }

        while (cp.hasNext(firstIter)) {
            Object firstObject = cp.next(firstIter, session);
            Vector primaryKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstObject, session);
            cacheKey = new CacheKey(primaryKey);

            if (keyValueToObject.containsKey(cacheKey)) {
                Object object = keyValueToObject.get(cacheKey);

                if (!session.compareObjects(firstObject, object)) {
                    return false;
                }
            } else {
                return false;
            }
        }

        return true;
    }

    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this mapping to actual class-based
     * settings
     * This method is implemented by subclasses as necessary.
     * @param classLoader
     */
    public void convertClassNamesToClasses(ClassLoader classLoader){
        super.convertClassNamesToClasses(classLoader);
        containerPolicy.convertClassNamesToClasses(classLoader);
    };

    /**
     * INTERNAL:
     * Returns the receiver's containerPolicy.
     */
    public ContainerPolicy getContainerPolicy() {
        return containerPolicy;
    }

    protected ModifyQuery getDeleteAllQuery() {
        if (deleteAllQuery == null) {
            deleteAllQuery = new DataModifyQuery();
        }
        return deleteAllQuery;
    }
 
    /**
     * INTERNAL:
     * Return the value of an attribute, unwrapping value holders if necessary.
     * Also check to ensure the collection is a vector.
     */
    public Object getRealAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException {
        Object value = super.getRealAttributeValueFromObject(object, session);
        if (value != null) {
            if (!getContainerPolicy().isValidContainer(value)) {
                throw DescriptorException.attributeTypeNotValid(this);
            }
        }
        return value;
    }

    /**
     * Convenience method.
     * Return the value of an attribute, unwrapping value holders if necessary.
     * If the value is null, build a new container.
     */
    public Object getRealCollectionAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException {
        Object value = this.getRealAttributeValueFromObject(object, session);
        if (value == null) {
            value = this.getContainerPolicy().containerInstance(1);
        }
        return value;
    }

    protected boolean hasCustomDeleteAllQuery() {
        return hasCustomDeleteAllQuery;
    }

    /**
     * INTERNAL:
     * Return true if ascending or descending ordering has been set on this
     * mapping via the @OrderBy annotation.
     */
    public boolean hasOrderBy() {
        return hasOrderBy;
    }
    
    /**
     * INTERNAL:
     * Initialize the state of mapping.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        super.initialize(session);
        setFields(collectFields());
        getContainerPolicy().prepare(getSelectionQuery(), session);

        // Check that the container policy is correct for the collection type.
        if ((!usesIndirection()) && (!getAttributeAccessor().getAttributeClass().isAssignableFrom(getContainerPolicy().getContainerClass()))) {
            throw DescriptorException.incorrectCollectionPolicy(this, getAttributeAccessor().getAttributeClass(), getContainerPolicy().getContainerClass());
        }
    }

    /**
     * INTERNAL:
     */
    public boolean isCollectionMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Iterate on the specified element.
     */
    public void iterateOnElement(DescriptorIterator iterator, Object element) {
        iterator.iterateReferenceObjectForMapping(element, this);
    }

    /**
     * INTERNAL:
     * Iterate on the attribute value.
     * The value holder has already been processed.
     */
    public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) {
        if (realAttributeValue == null) {
            return;
        }
        ContainerPolicy cp = getContainerPolicy();
        for (Object iter = cp.iteratorFor(realAttributeValue); cp.hasNext(iter);) {
            iterateOnElement(iterator, cp.next(iter, iterator.getSession()));
        }
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object. Because this is a
     * collection mapping, values are added to or removed from the collection
     * based on the change set.
     */
    public void mergeChangesIntoObject(Object target, ChangeRecord chgRecord, Object source, MergeManager mergeManager) {
        Object valueOfTarget = null;
        Object valueOfSource = null;
        AbstractSession parentSession = null;
        ContainerPolicy containerPolicy = getContainerPolicy();
        CollectionChangeRecord changeRecord = (CollectionChangeRecord) chgRecord;
        UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)changeRecord.getOwner().getUOWChangeSet();

        // Collect the changes into a vector. Check to see if the target has an instantiated
        // collection, if it does then iterate over the changes and merge the collections.
        if (isAttributeValueInstantiated(target)) {
            // If it is new will need a new collection.
            if (changeRecord.getOwner().isNew()) {
                valueOfTarget = containerPolicy.containerInstance(changeRecord.getAddObjectList().size());
            } else {
                valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession());
            }

            // Remove must happen before add to allow for changes in hash keys.
            // This is required to return the appropriate object from the parent when unwrapping.
            if (mergeManager.getSession().isUnitOfWork() && !mergeManager.shouldMergeWorkingCopyIntoBackup()) {
                parentSession = ((UnitOfWorkImpl)mergeManager.getSession()).getParent();
            } else {
                parentSession = mergeManager.getSession();
            }
            
            containerPolicy.mergeChanges(changeRecord, valueOfTarget, shouldMergeCascadeParts(mergeManager), mergeManager, parentSession);
        } else {
            // The valueholder has not been instantiated
            if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
                return; // do nothing
            }

            // If I'm not merging on another server then create instance of the collection.
            valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession());
            Object iterator = containerPolicy.iteratorFor(valueOfSource);
            valueOfTarget = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource));
            
            while (containerPolicy.hasNext(iterator)) {
                // CR2195 - Problem with merging Collection mapping in unit of work and inheritance.
                Object objectToMerge = containerPolicy.next(iterator, mergeManager.getSession());
                
                ObjectChangeSet changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(objectToMerge);
                if (shouldMergeCascadeParts(mergeManager) && (valueOfSource != null)) {
                    mergeManager.mergeChanges(objectToMerge, changeSet);
                }

                // Let the mergemanager get it because I don't have the change for the object.
                // CR2188 - Problem with merging Collection mapping in unit of work and transparent indirection.
                containerPolicy.addInto(mergeManager.getTargetVersionOfSourceObject(objectToMerge), valueOfTarget, mergeManager.getSession());
            }
        }
        
        if (valueOfTarget == null) {
            valueOfTarget = containerPolicy.containerInstance();
        }
        
        setRealAttributeValueInObject(target, valueOfTarget);
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object. This merge is only called when a changeSet for the target
     * does not exist or the target is uninitialized
     */
    public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) {
        if (isTargetUnInitialized) {
            // This will happen if the target object was removed from the cache before the commit was attempted
            if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) {
                setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), mergeManager.getSession()));
                return;
            }
        }
        if (!shouldMergeCascadeReference(mergeManager)) {
            // This is only going to happen on mergeClone, and we should not attempt to merge the reference
            return;
        }
        if (mergeManager.shouldMergeOriginalIntoWorkingCopy()) {
            if (!isAttributeValueInstantiated(target)) {
                // This will occur when the clone's value has not been instantiated yet and we do not need
                // the refresh that attribute
                return;
            }
        } else if (!isAttributeValueInstantiated(source)) {
            // I am merging from a clone into an original. No need to do merge if the attribute was never
            // modified
            return;
        }

        Object valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession());

        // There is a very special case when merging into the shared cache that the original
        // has been refreshed and now has non-instantiated indirection objects.
        // Force instantiation is not necessary and can cause problem with JTS drivers.
        AbstractSession mergeSession = mergeManager.getSession();
        Object valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeSession);
        ContainerPolicy containerPolicy = getContainerPolicy();
        boolean fireChangeEvents = false;
        if (! mergeManager.shouldMergeOriginalIntoWorkingCopy()){
                   // if we are copying from original to clone then the source will be
            // instantiated anyway and we must continue to use the UnitOfWork
            // valueholder in the case of transparent indirection
            Object newContainer = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource));
            valueOfTarget = newContainer;
        }else{
            //bug 3953038 - set a new collection in the object until merge completes, this
            // prevents rel-maint. from adding duplicates.
            setRealAttributeValueInObject(target, containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource)));
            containerPolicy.clear(valueOfTarget);
        }

        synchronized(valueOfSource){
            Object sourceIterator = containerPolicy.iteratorFor(valueOfSource);
            while (containerPolicy.hasNext(sourceIterator)) {
                Object object = containerPolicy.next(sourceIterator, mergeManager.getSession());
                if (object == null){
                    continue; // skip the null
                }
                if (shouldMergeCascadeParts(mergeManager)) {
                    if ((mergeManager.getSession().isUnitOfWork()) && (((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet() != null)) {
                        // If it is a unit of work, we have to check if I have a change Set fot this object
                        mergeManager.mergeChanges(mergeManager.getObjectToMerge(object), (ObjectChangeSet)((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet().getObjectChangeSetForClone(object));
                    } else {
                        mergeManager.mergeChanges(mergeManager.getObjectToMerge(object), null);
                    }
                }
                object = getReferenceDescriptor().getObjectBuilder().wrapObject(mergeManager.getTargetVersionOfSourceObject(object), mergeManager.getSession());
                synchronized (valueOfTarget){
                    containerPolicy.addInto(object, valueOfTarget, mergeManager.getSession());
                }
            }
        }
        // Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly.
        setRealAttributeValueInObject(target, valueOfTarget);
    }

    /**
     * INTERNAL:
     * An object was added to the collection during an update, insert it if private.
     */
    protected void objectAddedDuringUpdate(ObjectLevelModifyQuery query, Object objectAdded, ObjectChangeSet changeSet) throws DatabaseException, OptimisticLockException {
        if (!shouldObjectModifyCascadeToParts(query)) {// Called always for M-M
            return;
        }

        // Only cascade dependents writes in uow.
        if (query.shouldCascadeOnlyDependentParts()) {
            return;
        }

        // Insert must not be done for uow or cascaded queries and we must cascade to cascade policy.
        // We should distiguish between insert and write (optimization/paraniod).
        if (isPrivateOwned()) {
            InsertObjectQuery insertQuery = new InsertObjectQuery();
            insertQuery.setObject(objectAdded);
            insertQuery.setCascadePolicy(query.getCascadePolicy());
            query.getSession().executeQuery(insertQuery);
        } else {
            // Always write for updates, either private or in uow if calling this method.
            UnitOfWorkChangeSet uowChangeSet = null;
            if ((changeSet == null) && query.getSession().isUnitOfWork() && (((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet() != null)) {
                uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet();
                changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(query.getObject());
            }
            WriteObjectQuery writeQuery = new WriteObjectQuery();
            writeQuery.setObject(objectAdded);
            writeQuery.setObjectChangeSet(changeSet);
            writeQuery.setCascadePolicy(query.getCascadePolicy());
            query.getSession().executeQuery(writeQuery);
        }
    }

    /**
     * INTERNAL:
     * An object was removed to the collection during an update, delete it if private.
     */
    protected void objectRemovedDuringUpdate(ObjectLevelModifyQuery query, Object objectDeleted) throws DatabaseException, OptimisticLockException {
        if (isPrivateOwned()) {// Must check ownership for uow and cascading.
            if (query.shouldCascadeOnlyDependentParts()) {
                // If the session is a unit of work
                if (query.getSession().isUnitOfWork()) {
                    // ...and the object has not been explictly deleted in the unit of work
                    if (!(((UnitOfWorkImpl)query.getSession()).getDeletedObjects().containsKey(objectDeleted))) {
                        query.getSession().getCommitManager().addObjectToDelete(objectDeleted);
                    }
                } else {
                    query.getSession().getCommitManager().addObjectToDelete(objectDeleted);
                }
            } else {
                query.getSession().deleteObject(objectDeleted);
            }
        }
    }

    /**
     * INTERNAL:
     * An object is still in the collection, update it as it may have changed.
     */
    protected void objectUnchangedDuringUpdate(ObjectLevelModifyQuery query, Object object) throws DatabaseException, OptimisticLockException {
        if (!shouldObjectModifyCascadeToParts(query)) {// Called always for M-M
            return;
        }

        // Only cascade dependents writes in uow.
        if (query.shouldCascadeOnlyDependentParts()) {
            return;
        }

        // Always write for updates, either private or in uow if calling this method.
        WriteObjectQuery writeQuery = new WriteObjectQuery();
        writeQuery.setObject(object);
        writeQuery.setCascadePolicy(query.getCascadePolicy());
        query.getSession().executeQuery(writeQuery);
    }

    /**
     * INTERNAL:
     * copies the non primary key information into the row currently used only in ManyToMany
     */
    protected void prepareTranslationRow(AbstractRecord translationRow, Object object, AbstractSession session) {
        //Do nothing for the generic Collection Mapping
    }

    /**
     * INTERNAL:
     * An object is still in the collection, update it as it may have changed.
     */
    protected void objectUnchangedDuringUpdate(ObjectLevelModifyQuery query, Object object, Hashtable backupclones, CacheKey keys) throws DatabaseException, OptimisticLockException {
        objectUnchangedDuringUpdate(query, object);
    }

    /**
     * INTERNAL:
     * All the privately owned parts are read
     */
    protected Object readPrivateOwnedForObject(ObjectLevelModifyQuery modifyQuery) throws DatabaseException {
        if (modifyQuery.getSession().isUnitOfWork()) {
            return getRealCollectionAttributeValueFromObject(modifyQuery.getBackupClone(), modifyQuery.getSession());
        } else {
            // cr 3819
            prepareTranslationRow(modifyQuery.getTranslationRow(), modifyQuery.getObject(), modifyQuery.getSession());
            return modifyQuery.getSession().executeQuery(getSelectionQuery(), modifyQuery.getTranslationRow());
        }
    }

    /**
     * ADVANCED:
     * Configure the mapping to use a container policy.
     * The policy manages the access to the collection.
     */
    public void setContainerPolicy(ContainerPolicy containerPolicy) {
        this.containerPolicy = containerPolicy;
        ((ReadAllQuery)getSelectionQuery()).setContainerPolicy(containerPolicy);
    }

    /**
     * PUBLIC:
     * The default delete all query for mapping can be overridden by specifying the new query.
     * This query is responsible for doing the deletion required by the mapping,
     * such as deletion of all the rows from join table for M-M, or optimized delete all of target objects for 1-M.
     */
    public void setCustomDeleteAllQuery(ModifyQuery query) {
        setDeleteAllQuery(query);
        setHasCustomDeleteAllQuery(true);
    }

    protected void setDeleteAllQuery(ModifyQuery query) {
        deleteAllQuery = query;
    }

    /**
     * PUBLIC:
     * Set the receiver's delete all SQL string. This allows the user to override the SQL
     * generated by TopLink, with there own SQL or procedure call. The arguments are
     * translated from the fields of the source row, through replacing the field names
     * marked by '#' with the values for those fields.
     * This SQL is responsible for doing the deletion required by the mapping,
     * such as deletion of all the rows from join table for M-M, or optimized delete all of target objects for 1-M.
     * Example, 'delete from PROJ_EMP where EMP_ID = #EMP_ID'.
     */
    public void setDeleteAllSQLString(String sqlString) {
        DataModifyQuery query = new DataModifyQuery();
        query.setSQLString(sqlString);
        setCustomDeleteAllQuery(query);
    }
    
    /**
     * PUBLIC:
     * Set the receiver's delete all call. This allows the user to override the SQL
     * generated by TopLink, with there own SQL or procedure call. The arguments are
     * translated from the fields of the source row.
     * This call is responsible for doing the deletion required by the mapping,
     * such as deletion of all the rows from join table for M-M, or optimized delete all of target objects for 1-M.
     * Example, 'new SQLCall("delete from PROJ_EMP where EMP_ID = #EMP_ID")'.
     */
    public void setDeleteAllCall(Call call) {
        DataModifyQuery query = new DataModifyQuery();
        query.setCall(call);
        setCustomDeleteAllQuery(query);
    }
    
    protected void setHasCustomDeleteAllQuery(boolean bool) {
        hasCustomDeleteAllQuery = bool;
    }

    /**
     * PUBLIC:
     * Set the name of the session to execute the mapping's queries under.
     * This can be used by the session broker to override the default session
     * to be used for the target class.
     */
    public void setSessionName(String name) {
        getDeleteAllQuery().setSessionName(name);
        getSelectionQuery().setSessionName(name);
    }

    /**
     * ADVANCED:
     * This method is used to have an object add to a collection once the
     * changeSet is applied. The referenceKey parameter should only be used for
     * direct Maps.
     */
    public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) {
        CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new CollectionChangeRecord(changeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            collectionChangeRecord.getAddObjectList().put(changeSetToAdd, changeSetToAdd);
            collectionChangeRecord.getOrderedAddObjects().add(changeSetToAdd);
            changeSet.addChange(collectionChangeRecord);
        } else {
            getContainerPolicy().recordAddToCollectionInChangeRecord((ObjectChangeSet)changeSetToAdd, collectionChangeRecord);
        }
        if (referenceKey != null){
            ((ObjectChangeSet)changeSetToAdd).setNewKey(referenceKey);
        }
    }

    /**
     * ADVANCED:
     * This method is used to have an object removed from a collection once the
     * changeSet is applied. The referenceKey parameter should only be used for
     * direct Maps.
     */
    public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) {
        CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new CollectionChangeRecord(changeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            collectionChangeRecord.getRemoveObjectList().put(changeSetToRemove, changeSetToRemove);
            changeSet.addChange(collectionChangeRecord);
        } else {
            getContainerPolicy().recordRemoveFromCollectionInChangeRecord((ObjectChangeSet)changeSetToRemove, collectionChangeRecord);
        }
        if (referenceKey != null){
            ((ObjectChangeSet)changeSetToRemove).setOldKey(referenceKey);
        }
    }

    /**
     * INTERNAL:
     * Either create a new change record or update with the new value. This is used
     * by attribute change tracking.
     * Specifically in a collection mapping this will be called when the customer
     * Set a new collection. In this case we will need to mark the change record
     * with the new and the old versions of the collection.
     * And mark the ObjectChangeSet with the attribute name then when the changes are calculated
     * force a compare on the collections to determine changes.
     */
    public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        CollectionChangeRecord collectionChangeRecord = (CollectionChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new CollectionChangeRecord(objectChangeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            objectChangeSet.addChange(collectionChangeRecord);
        }
        if (collectionChangeRecord.getOriginalCollection() == null){
            collectionChangeRecord.setOriginalCollection(oldValue);
        }
        collectionChangeRecord.setLatestCollection(newValue);
        
        objectChangeSet.deferredDetectionRequiredOn(getAttributeName());
    }

    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container class
     * to hold the target objects.
     * <p>The container class must implement (directly or indirectly) the
     * <code>java.util.Collection</code> interface.
     */
    public void useCollectionClass(Class concreteClass) {
        ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass, hasOrderBy());
        setContainerPolicy(policy);
    }

    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container class
     * to hold the target objects.
     * <p>The container class must implement (directly or indirectly) the
     * <code>java.util.SortedSet</code> interface.
     */
    public void useSortedSetClass(Class concreteClass, Comparator comparator) {
        try {
            SortedCollectionContainerPolicy policy = (SortedCollectionContainerPolicy)ContainerPolicy.buildPolicyFor(concreteClass);
            policy.setComparator(comparator);
            setContainerPolicy(policy);
        } catch (ClassCastException e) {
            useCollectionClass(concreteClass);
        }
    }
    
    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container
     * clas to hold the target objects. The key used to index a value in the
     * <code>Map</code> is the value returned by either a call to a
     * specified zero-argument method or the value of a field.
     * <p> To facilitate resolving the keyName to a method or field,
     * the mapping's referenceClass must set before calling this method.
     * <p> Note: If the keyName is for a method, that method must be implemented
     * by the class (or a superclass) of any value to be inserted into the
     * <code>Map</code>.
     * <p> The container class must implement (directly or indirectly) the
     * <code>java.util.Map</code> interface.
     */
    public void useMapClass(Class concreteClass, String keyName) {
        // the reference class has to be specified before coming here
        if (getReferenceClassName() == null) {
            throw DescriptorException.referenceClassNotSpecified(this);
        }
        
        ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass);
        policy.setKeyName(keyName, getReferenceClassName());
        setContainerPolicy(policy);
    }
    
    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container
     * class to hold the target objects. The key used to index a value in the
     * <code>Map</code> is an instance of the composite primary key class.
     * <p> To facilitate resolving the primary key class, the mapping's
     * referenceClass must set before calling this method.
     * <p> The container class must implement (directly or indirectly) the
     * <code>java.util.Map</code> interface.
     */
    public void useMapClass(Class concreteClass) {
        useMapClass(concreteClass, null);
    }

    /**
     * PUBLIC:
     * If transparent indirection is used, a special collection will be placed in the source
     * object's attribute.
     * Fetching of the contents of the collection from the database will be delayed
     * until absolutely necessary. (Any message sent to the collection will cause
     * the contents to be faulted in from the database.)
     * This can result in rather significant performance gains, without having to change
     * the source object's attribute from Collection (or List or Vector) to
     * ValueHolderInterface.
     */
    public void useTransparentCollection() {
        setIndirectionPolicy(new TransparentIndirectionPolicy());
        useCollectionClass(ClassConstants.IndirectList_Class);
    }
    
    /**
     * PUBLIC:
     * If transparent indirection is used, a special collection will be placed in the source
     * object's attribute.
     * Fetching of the contents of the collection from the database will be delayed
     * until absolutely necessary. (Any message sent to the collection will cause
     * the contents to be faulted in from the database.)
     * This can result in rather significant performance gains, without having to change
     * the source object's attribute from Set to
     * ValueHolderInterface.
     */
    public void useTransparentSet() {
        setIndirectionPolicy(new TransparentIndirectionPolicy());
        useCollectionClass(IndirectSet.class);
    }
    
    /**
     * PUBLIC:
     * If transparent indirection is used, a special collection will be placed in the source
     * object's attribute.
     * Fetching of the contents of the collection from the database will be delayed
     * until absolutely necessary. (Any message sent to the collection will cause
     * the contents to be faulted in from the database.)
     * This can result in rather significant performance gains, without having to change
     * the source object's attribute from List to
     * ValueHolderInterface.
     */
    public void useTransparentList() {
        setIndirectionPolicy(new TransparentIndirectionPolicy());
        useCollectionClass(ClassConstants.IndirectList_Class);
    }

    /**
     * PUBLIC:
     * If transparent indirection is used, a special map will be placed in the source
     * object's attribute.
     * Fetching of the contents of the map from the database will be delayed
     * until absolutely necessary. (Any message sent to the map will cause
     * the contents to be faulted in from the database.)
     * This can result in rather significant performance gains, without having to change
     * the source object's attribute from Map (or Dictionary or Hashtable) to
     * ValueHolderInterface.<p>
     * The key used in the Map is the value returned by a call to the zero parameter
     * method named methodName. The method should be a zero argument method implemented (or
     * inherited) by the value to be inserted into the Map.
     */
    public void useTransparentMap(String methodName) {
        setIndirectionPolicy(new TransparentIndirectionPolicy());
        useMapClass(ClassConstants.IndirectMap_Class, methodName);
    }

    /**
     * INTERNAL:
     * To validate mappings declaration
     */
    public void validateBeforeInitialization(AbstractSession session) throws DescriptorException {
        super.validateBeforeInitialization(session);

        getIndirectionPolicy().validateContainerPolicy(session.getIntegrityChecker());

        if (getAttributeAccessor() instanceof InstanceVariableAttributeAccessor) {
            Class attributeType = ((InstanceVariableAttributeAccessor)getAttributeAccessor()).getAttributeType();
            getIndirectionPolicy().validateDeclaredAttributeTypeForCollection(attributeType, session.getIntegrityChecker());
        } else if (getAttributeAccessor() instanceof MethodAttributeAccessor) {
            Class returnType = ((MethodAttributeAccessor)getAttributeAccessor()).getGetMethodReturnType();
            getIndirectionPolicy().validateGetMethodReturnTypeForCollection(returnType, session.getIntegrityChecker());

            Class parameterType = ((MethodAttributeAccessor)getAttributeAccessor()).getSetMethodParameterType();
            getIndirectionPolicy().validateSetMethodParameterTypeForCollection(parameterType, session.getIntegrityChecker());
        }
    }

    /**
     * INTERNAL:
     * Checks if object is deleted from the database or not.
     */
    public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException {
        // Row is built for translation
        if (isReadOnly()) {
            return true;
        }

        if (isPrivateOwned()) {
            Object objects = getRealCollectionAttributeValueFromObject(object, session);

            ContainerPolicy containerPolicy = getContainerPolicy();
            for (Object iter = containerPolicy.iteratorFor(objects); containerPolicy.hasNext(iter);) {
                if (!session.verifyDelete(containerPolicy.next(iter, session))) {
                    return false;
                }
            }
        }

        AbstractRecord row = getDescriptor().getObjectBuilder().buildRowForTranslation(object, session);

        //cr 3819 added the line below to fix the translationtable to ensure that it
        // contains the required values
        prepareTranslationRow(row, object, session);
        Object value = session.executeQuery(getSelectionQuery(), row);

        return getContainerPolicy().isEmpty(value);
    }

    /**
     * INTERNAL:
     * Add a new value and its change set to the collection change record. This is used by
     * attribute change tracking.
     */
    public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        if (newValue != null) {
            ClassDescriptor descriptor;
            //PERF: Use referenceDescriptor if it does not have inheritance
            if (!getReferenceDescriptor().hasInheritance()) {
                descriptor = getReferenceDescriptor();
            } else {
                descriptor = uow.getDescriptor(newValue);
            }
            newValue = descriptor.getObjectBuilder().unwrapObject(newValue, uow);
            ObjectChangeSet newSet = descriptor.getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)objectChangeSet.getUOWChangeSet(), uow);
            simpleAddToCollectionChangeRecord(newKey, newSet, objectChangeSet, uow);
        }
    }
    
    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return true;
    }

    /**
     * INTERNAL:
     * Remove a value and its change set from the collection change record. This is used by
     * attribute change tracking.
     */
    public void removeFromCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        if (newValue != null) {
            ClassDescriptor descriptor;

            //PERF: Use referenceDescriptor if it does not have inheritance
            if (!getReferenceDescriptor().hasInheritance()) {
                descriptor = getReferenceDescriptor();
            } else {
                descriptor = uow.getDescriptor(newValue);
            }
            newValue = descriptor.getObjectBuilder().unwrapObject(newValue, uow);
            ObjectChangeSet newSet = descriptor.getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)objectChangeSet.getUOWChangeSet(), uow);
            simpleRemoveFromCollectionChangeRecord(newKey, newSet, objectChangeSet, uow);
        }
    }

    /**
     * INTERNAL:
     * Directly build a change record without comparison
     */
    public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) {
        Object cloneAttribute = null;
        cloneAttribute = getAttributeValueFromObject(clone);
        if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            return null;
        }

        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        IdentityHashMap cloneKeyValues = new IdentityHashMap();
        ContainerPolicy cp = getContainerPolicy();
        Object cloneObjectCollection = null;
        if (cloneAttribute != null) {
            cloneObjectCollection = getRealCollectionAttributeValueFromObject(clone, session);
        } else {
            cloneObjectCollection = cp.containerInstance(1);
        }
        Object cloneIter = cp.iteratorFor(cloneObjectCollection);

        while (cp.hasNext(cloneIter)) {
            Object firstObject = cp.next(cloneIter, session);
            if (firstObject != null) {
                cloneKeyValues.put(firstObject, firstObject);
            }
        }

        CollectionChangeRecord changeRecord = new CollectionChangeRecord(owner);
        changeRecord.setAttribute(getAttributeName());
        changeRecord.setMapping(this);
        changeRecord.addAdditionChange(cloneKeyValues, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session);
        if (changeRecord.hasChanges()) {
            return changeRecord;
        }
        return null;
    }

    /**
     * INTERNAL:
     * Indicates whether valueFromRow should call valueFromRowInternalWithJoin (true)
     * or valueFromRowInternal (false)
     */
    protected boolean shouldUseValueFromRowWithJoin(JoinedAttributeManager joinManager) {
        return joinManager.getDataResults_()!=null && super.shouldUseValueFromRowWithJoin(joinManager);
    }
    
    /**
     * INTERNAL:
     * Return the value of the field from the row or a value holder on the query to obtain the object.
     * To get here the mapping's isJoiningSupported() should return true,
     * currently that's the case for only 1-m and m-m.
     */
    protected Object valueFromRowInternalWithJoin(AbstractRecord row, JoinedAttributeManager joinManager, AbstractSession executionSession) throws DatabaseException {
        // If the query was using joining, all of the result rows will have been set.
        List rows = joinManager.getDataResults_();
        Object value = getContainerPolicy().containerInstance();
        
        // A nested query must be built to pass to the descriptor that looks like the real query execution would,
        // these should be cached on the query during prepare.
        ObjectLevelReadQuery nestedQuery = null;
        if (joinManager.getJoinedMappingQueries_() != null) {
            nestedQuery = (ObjectLevelReadQuery) joinManager.getJoinedMappingQueries_().get(this);
        } else {
            nestedQuery = prepareNestedJoins(joinManager, executionSession);
        }
        nestedQuery.setSession(executionSession);
        //CR #4365 - used to prevent infinite recursion on refresh object cascade all
        nestedQuery.setQueryId(joinManager.getBaseQuery().getQueryId());

        // Extract the primary key of the source object, to filter only the joined rows for that object.
        Vector sourceKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(row, executionSession);
        CacheKey sourceCacheKey = new CacheKey(sourceKey);
        
        // A set of target cache keys must be maintained to avoid duplicates from multiple 1-m joins.
        Set targetCacheKeys = new HashSet();

        // For each rows, extract the target row and build the target object and add to the collection.
        for (int index = 0; index < rows.size(); index++) {
            AbstractRecord sourceRow = (AbstractRecord)rows.get(index);
            AbstractRecord targetRow = sourceRow;
            
            // Row will be set to null if part of another object's join already processed.
            if (targetRow != null) {
                // CR #... the field for many objects may be in the row,
                // so build the subpartion of the row through the computed values in the query,
                // this also helps the field indexing match.
                targetRow = trimRowForJoin(targetRow, joinManager, executionSession);
                AbstractRecord pkRow = trimRowForJoin(sourceRow, new Integer(joinManager.getParentResultIndex()), executionSession);
                nestedQuery.setTranslationRow(targetRow);

                // Extract the primary key of the row to filter only the joined rows for the source object.
                Vector rowSourceKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(pkRow, executionSession);
                if(rowSourceKey != null) {
                    CacheKey rowSourceCacheKey = new CacheKey(rowSourceKey);
                    
                    // Only build/add the object if the join row is for the object.
                    if (sourceCacheKey.equals(rowSourceCacheKey)) {
                        // Partial object queries must select the primary key of the source and related objects.
                        // If the target joined rows in null (outerjoin) means an empty collection.
                        Vector targetKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(targetRow, executionSession);
                        if (targetKey == null) {
                            // A null primary key means an empty collection returned as nulls from an outerjoin.
                            return getIndirectionPolicy().valueFromRow(value);
                        }
                        CacheKey targetCacheKey = new CacheKey(targetKey);
                        
                        // Only build/add the taregt object once, skip duplicates from multiple 1-m joins.
                        if (!targetCacheKeys.contains(targetCacheKey)) {
                            targetCacheKeys.add(targetCacheKey);
                            Object targetObject = getReferenceDescriptor().getObjectBuilder().buildObject(nestedQuery, targetRow, nestedQuery.getJoinedAttributeManager());
                            nestedQuery.setTranslationRow(null);
                            getContainerPolicy().addInto(targetObject, value, executionSession);
                        }
                    }
                } else {
                    // Clear an empty row
                    rows.set(index, null);
                }
            }
        }
        return getIndirectionPolicy().valueFromRow(value);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import java.util.*;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.localization.*;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * This class maintains a commit stack and resolves circular references.
 */
public class CommitManager {
    protected Vector commitOrder;

    /** Changed the folowing line to work like mergemanager. The commitManager
     * will now track what has been processed as apposed to removing from the list
     * objects that have been processed. This must be done to allow for customers
     * modifying the changesets in events
     */
    protected HardIdentityHashtable processedCommits;
    protected HardIdentityHashtable pendingCommits;
    protected HardIdentityHashtable preModifyCommits;
    protected HardIdentityHashtable postModifyCommits;
    protected HardIdentityHashtable completedCommits;
    protected HardIdentityHashtable shallowCommits;
    protected AbstractSession session;
    protected boolean isActive;
    protected Hashtable dataModifications;
    protected Vector objectsToDelete;

    /**
     * Create the commit manager on the session.
     * It must be initialized later on after the descriptors have been added.
     */
    public CommitManager(AbstractSession session) {
        this.session = session;
        this.commitOrder = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        this.isActive = false;

        // PERF - move to lazy initialization (3286091)
        //this.processedCommits = new HardIdentityHashtable(20);
        //this.pendingCommits = new HardIdentityHashtable(20);
        //this.preModifyCommits = new HardIdentityHashtable(20);
        //this.postModifyCommits = new HardIdentityHashtable(20);
        //this.completedCommits = new HardIdentityHashtable(20);
        //this.shallowCommits = new HardIdentityHashtable(20);
    }

    /**
     * Add the data query to be performed at the end of the commit.
     * This is done to decrease dependencies and avoid deadlock.
     */
    public void addDataModificationEvent(DatabaseMapping mapping, Object[] event) {
        // For lack of inner class the array is being called an event.
        if (!getDataModifications().containsKey(mapping)) {
            getDataModifications().put(mapping, new Vector());
        }

        ((Vector)getDataModifications().get(mapping)).addElement(event);
    }

    /**
     * Deletion are cached until the end.
     */
    public void addObjectToDelete(Object objectToDelete) {
        getObjectsToDelete().addElement(objectToDelete);
    }

    /**
     * add the commit of the object to the processed list.
     */
    protected void addProcessedCommit(Object domainObject) {
        getProcessedCommits().put(domainObject, domainObject);
    }

    /**
     * Commit all of the objects as a single transaction.
     * This should commit the object in the correct order to maintain referencial integrity.
     */
    public void commitAllObjects(HardIdentityHashtable domainObjects) throws RuntimeException, DatabaseException, OptimisticLockException {
        reinitialize();
        setPendingCommits(domainObjects);

        setIsActive(true);
        getSession().beginTransaction();
        try {
            // The commit order is all of the classes ordered by dependencies, this is done for dealock avoidance.
            for (Enumeration classesEnum = getCommitOrder().elements();
                     classesEnum.hasMoreElements();) {
                Class theClass = (Class)classesEnum.nextElement();

                for (Enumeration pendingEnum = getPendingCommits().elements();
                         pendingEnum.hasMoreElements();) {
                    Object objectToWrite = pendingEnum.nextElement();
                    if (objectToWrite.getClass() == theClass) {
                        removePendingCommit(objectToWrite);// I think removing while enumerating is ok.

                        WriteObjectQuery commitQuery = new WriteObjectQuery();
                        commitQuery.setObject(objectToWrite);
                        if (getSession().isUnitOfWork()) {
                            commitQuery.cascadeOnlyDependentParts();
                        } else {
                            commitQuery.cascadeAllParts();// Used in write all objects in session.
                        }
                        getSession().executeQuery(commitQuery);
                    }
                }
            }

            for (Enumeration mappingsEnum = getDataModifications().keys(), mappingEventsEnum = getDataModifications().elements();
                     mappingEventsEnum.hasMoreElements();) {
                Vector events = (Vector)mappingEventsEnum.nextElement();
                DatabaseMapping mapping = (DatabaseMapping)mappingsEnum.nextElement();
                for (Enumeration eventsEnum = events.elements(); eventsEnum.hasMoreElements();) {
                    Object[] event = (Object[])eventsEnum.nextElement();
                    mapping.performDataModificationEvent(event, getSession());
                }
            }

            Vector objects = getObjectsToDelete();
            reinitialize();
            for (Enumeration objectsToDeleteEnum = objects.elements();
                     objectsToDeleteEnum.hasMoreElements();) {
                getSession().deleteObject(objectsToDeleteEnum.nextElement());
            }
        } catch (RuntimeException exception) {
            getSession().rollbackTransaction();
            throw exception;
        } finally {
            reinitialize();
            setIsActive(false);
        }

        getSession().commitTransaction();
    }

    /**
     * Commit all of the objects as a single transaction.
     * This should commit the object in the correct order to maintain referencial integrity.
     */
    public void commitAllObjectsWithChangeSet(UnitOfWorkChangeSet uowChangeSet) throws RuntimeException, DatabaseException, OptimisticLockException {
        reinitialize();
        setIsActive(true);
        getSession().beginTransaction();
        try {
            // PERF: if the number of classes in the project is large this loop can be a perf issue.
            // If only one class types changed, then avoid loop.
            if ((uowChangeSet.getObjectChanges().size() + uowChangeSet.getNewObjectChangeSets().size()) <= 1) {
                Enumeration classes = uowChangeSet.getNewObjectChangeSets().keys();
                if (classes.hasMoreElements()) {
                    Class theClass = (Class)classes.nextElement();
                    commitNewObjectsForClassWithChangeSet(uowChangeSet, theClass);
                }
                Enumeration classNames = uowChangeSet.getObjectChanges().keys();
                if (classNames.hasMoreElements()) {
                    String className = (String)classNames.nextElement();
                    commitChangedObjectsForClassWithChangeSet(uowChangeSet, className);
                }
            } else {
                // The commit order is all of the classes ordered by dependencies, this is done for dealock avoidance.
                for (Enumeration classesEnum = getCommitOrder().elements();
                         classesEnum.hasMoreElements();) {
                    Class theClass = (Class)classesEnum.nextElement();
                    commitAllObjectsForClassWithChangeSet(uowChangeSet, theClass);
                }
            }

            if (hasDataModifications()) {
                // Perform all batched up data modifications, done to avoid dependecies.
                for (Enumeration mappingsEnum = getDataModifications().keys(), mappingEventsEnum = getDataModifications().elements();
                         mappingEventsEnum.hasMoreElements();) {
                    Vector events = (Vector)mappingEventsEnum.nextElement();
                    DatabaseMapping mapping = (DatabaseMapping)mappingsEnum.nextElement();
                    for (Enumeration eventsEnum = events.elements(); eventsEnum.hasMoreElements();) {
                        Object[] event = (Object[])eventsEnum.nextElement();
                        mapping.performDataModificationEvent(event, getSession());
                    }
                }
            }

            if (hasObjectsToDelete()) {
                Vector objects = getObjectsToDelete();
                reinitialize();
                for (Enumeration objectsToDeleteEnum = objects.elements();
                         objectsToDeleteEnum.hasMoreElements();) {
                    getSession().deleteObject(objectsToDeleteEnum.nextElement());
                }
            }
        } catch (RuntimeException exception) {
            getSession().rollbackTransaction();
            throw exception;
        } finally {
            reinitialize();
            setIsActive(false);
        }

        getSession().commitTransaction();
    }

    /**
     * Commit all of the objects of the class type in the change set.
     * This allows for the order of the classes to be processed optimally.
     */
    protected void commitAllObjectsForClassWithChangeSet(UnitOfWorkChangeSet uowChangeSet, Class theClass) {
        // Although new objects should be first, there is an issue that new objects get added to non-new after the insert,
        // so the object would be written twice.
        commitChangedObjectsForClassWithChangeSet(uowChangeSet, theClass.getName());
        commitNewObjectsForClassWithChangeSet(uowChangeSet, theClass);
    }

    /**
     * Commit all of the objects of the class type in the change set.
     * This allows for the order of the classes to be processed optimally.
     */
    protected void commitNewObjectsForClassWithChangeSet(UnitOfWorkChangeSet uowChangeSet, Class theClass) {
        HardIdentityHashtable newObjectChangesList = (HardIdentityHashtable)uowChangeSet.getNewObjectChangeSets().get(theClass);
        if (newObjectChangesList != null) {// may be no changes for that class type.
            ClassDescriptor descriptor = getSession().getDescriptor(theClass);
            for (Enumeration pendingEnum = newObjectChangesList.elements();
                     pendingEnum.hasMoreElements();) {
                ObjectChangeSet changeSetToWrite = (ObjectChangeSet)pendingEnum.nextElement();
                Object objectToWrite = changeSetToWrite.getUnitOfWorkClone();
                if ((!getProcessedCommits().containsKey(changeSetToWrite)) && (!getProcessedCommits().containsKey(objectToWrite))) {
                    addProcessedCommit(changeSetToWrite);
                    InsertObjectQuery commitQuery = new InsertObjectQuery();
                    commitQuery.setObjectChangeSet(changeSetToWrite);
                    commitQuery.setObject(objectToWrite);
                    commitQuery.cascadeOnlyDependentParts();
                    // removed checking session type to set cascade level
                    // will always be a unitOfWork so we need to cascade dependent parts
                    getSession().executeQuery(commitQuery);
                }
                ((UnitOfWorkImpl)getSession()).updateChangeTrackersIfRequired(objectToWrite, changeSetToWrite, (UnitOfWorkImpl)getSession(), descriptor);

                //after the query has executed lets clear the change detection policies
                //this is important for write changes and non deferred writes support
            }
        }
    }

    /**
     * Commit changed of the objects of the class type in the change set.
     * This allows for the order of the classes to be processed optimally.
     */
    protected void commitChangedObjectsForClassWithChangeSet(UnitOfWorkChangeSet uowChangeSet, String className) {
        Hashtable objectChangesList = (Hashtable)uowChangeSet.getObjectChanges().get(className);
        if (objectChangesList != null) {// may be no changes for that class type.
            ClassDescriptor descriptor = null;
            for (Enumeration pendingEnum = objectChangesList.elements();
                     pendingEnum.hasMoreElements();) {
                ObjectChangeSet changeSetToWrite = (ObjectChangeSet)pendingEnum.nextElement();
                if (descriptor == null) {
                    // Need a class to get descriptor, for some evil reason the keys are class name.
                    descriptor = getSession().getDescriptor(changeSetToWrite.getClassType(getSession()));
                }
                Object objectToWrite = changeSetToWrite.getUnitOfWorkClone();
                if ((!getProcessedCommits().containsKey(changeSetToWrite)) && (!getProcessedCommits().containsKey(objectToWrite))) {
                    addProcessedCommit(changeSetToWrite);
                    // Commit and resume on failure can cause a new change set to be in existing, so need to check here.
                    WriteObjectQuery commitQuery = null;
                    if (changeSetToWrite.isNew()) {
                        commitQuery = new InsertObjectQuery();
                    } else {
                        commitQuery = new UpdateObjectQuery();
                    }
                    commitQuery.setObjectChangeSet(changeSetToWrite);
                    commitQuery.setObject(objectToWrite);
                    commitQuery.cascadeOnlyDependentParts();
                    // removed checking session type to set cascade level
                    // will always be a unitOfWork so we need to cascade dependent parts
                    getSession().executeQuery(commitQuery);
                }
                ((UnitOfWorkImpl)getSession()).updateChangeTrackersIfRequired(objectToWrite, changeSetToWrite, (UnitOfWorkImpl)getSession(), descriptor);

                // after the query has executed lets clear the change detection policies
                // this is important for write changes and non deferred writes support
            }
        }
    }

    /**
     * delete all of the objects as a single transaction.
     * This should delete the object in the correct order to maintain referencial integrity.
     */
    public void deleteAllObjects(Vector objectsForDeletion) throws RuntimeException, DatabaseException, OptimisticLockException {
        setIsActive(true);
        getSession().beginTransaction();

        try {
            for (int index = getCommitOrder().size() - 1; index >= 0; index--) {
                Class theClass = (Class)getCommitOrder().elementAt(index);

                for (Enumeration objectsForDeletionEnum = objectsForDeletion.elements();
                         objectsForDeletionEnum.hasMoreElements();) {
                    Object objectToDelete = objectsForDeletionEnum.nextElement();
                    if (objectToDelete.getClass() == theClass) {
                        DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
                        deleteQuery.setObject(objectToDelete);
                        deleteQuery.cascadeOnlyDependentParts();
                        getSession().executeQuery(deleteQuery);
                    }
                }
            }
        } catch (RuntimeException exception) {
            try {
                getSession().rollbackTransaction();
            } catch (Exception ignore) {
            }
            throw exception;
        } finally {
            setIsActive(false);
        }

        getSession().commitTransaction();
    }

    /**
     * Return the order in which objects should be commited to the database.
     * This order is based on ownership in the descriptors and is require for referencial integrity.
     * The commit order is a vector of vectors,
     * where the first vector is all root level classes, the second is classes owned by roots and so on.
     */
    public Vector getCommitOrder() {
        return commitOrder;
    }

    /**
     * Return any objects that have been written during this commit process.
     */
    protected HardIdentityHashtable getCompletedCommits() {
        if (completedCommits == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            completedCommits = new HardIdentityHashtable();
        }
        return completedCommits;
    }

    protected boolean hasDataModifications() {
        return ((dataModifications != null) && (!dataModifications.isEmpty()));
    }

    /**
     * Used to store data querys to be performed at the end of the commit.
     * This is done to decrease dependencies and avoid deadlock.
     */
    protected Hashtable getDataModifications() {
        if (dataModifications == null) {
            dataModifications = new Hashtable(10);
        }
        return dataModifications;
    }

    protected boolean hasObjectsToDelete() {
        return ((objectsToDelete != null) && (!objectsToDelete.isEmpty()));
    }

    /**
     * Deletion are cached until the end.
     */
    protected Vector getObjectsToDelete() {
        if (objectsToDelete == null) {
            objectsToDelete = new Vector(5);
        }
        return objectsToDelete;
    }

    /**
     * Return any objects that should be written during this commit process.
     */
    protected HardIdentityHashtable getProcessedCommits() {
        if (processedCommits == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            processedCommits = new HardIdentityHashtable();
        }
        return processedCommits;
    }

    /**
     * Return any objects that should be written during this commit process.
     */
    protected HardIdentityHashtable getPendingCommits() {
        if (pendingCommits == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            pendingCommits = new HardIdentityHashtable();
        }
        return pendingCommits;
    }

    /**
     * Return any objects that should be written during post modify commit process.
     * These objects should be order by their ownership constraints to maintain referencial integrity.
     */
    protected HardIdentityHashtable getPostModifyCommits() {
        if (postModifyCommits == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            postModifyCommits = new HardIdentityHashtable();
        }
        return postModifyCommits;
    }

    /**
     * Return any objects that should be written during pre modify commit process.
     * These objects should be order by their ownership constraints to maintain referencial integrity.
     */
    protected HardIdentityHashtable getPreModifyCommits() {
        if (preModifyCommits == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            preModifyCommits = new HardIdentityHashtable();
        }
        return preModifyCommits;
    }

    /**
     * Return the session that this is managing commits for.
     */
    protected AbstractSession getSession() {
        return session;
    }

    /**
     * Return any objects that have been shallow comitted during this commit process.
     */
    protected HardIdentityHashtable getShallowCommits() {
        if (shallowCommits == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            shallowCommits = new HardIdentityHashtable();
        }
        return shallowCommits;
    }

    /**
     * Reset the commit order from the session's descriptors.
     * This uses the constraint dependencies in the descriptor's mappings,
     * to decide which descriptors are dependent on which other descriptors.
     * Multiple computations of the commit order should produce the same ordering.
     * This is done to improve performance on unit of work writes through decreasing the
     * stack size, and acts as a deadlock avoidance mechansim.
     */
    public void initializeCommitOrder() {
        Vector descriptors = Helper.buildVectorFromMapElements(getSession().getDescriptors());

        // Must ensure uniqueness, some descriptor my be register twice for interfaces.
        descriptors = Helper.addAllUniqueToVector(new Vector(descriptors.size()), descriptors);
        Object[] descriptorsArray = new Object[descriptors.size()];
        for (int index = 0; index < descriptors.size(); index++) {
            descriptorsArray[index] = descriptors.elementAt(index);
        }
        TOPSort.quicksort(descriptorsArray, new DescriptorCompare());
        descriptors = new Vector(descriptors.size());
        for (int index = 0; index < descriptorsArray.length; index++) {
            descriptors.addElement(descriptorsArray[index]);
        }

        CommitOrderCalculator calculator = new CommitOrderCalculator(getSession());
        calculator.addNodes(descriptors);
        calculator.calculateMappingDependencies();
        calculator.orderCommits();
        descriptors = calculator.getOrderedDescriptors();

        calculator = new CommitOrderCalculator(getSession());
        calculator.addNodes(descriptors);
        calculator.calculateSpecifiedDependencies();
        calculator.orderCommits();

        setCommitOrder(calculator.getOrderedClasses());
    }

    /**
     * Return if the commit manager is active.
     */
    public boolean isActive() {
        return isActive;
    }

    /**
     * Return if the object has been commited.
     * This should be called by any query that is writing an object,
     * if true the query should not write the object.
     */
    public boolean isCommitCompleted(Object domainObject) {
        return getCompletedCommits().containsKey(domainObject);
    }

    /**
     * Return if the object is being in progress of being post modify commit.
     * This should be called by any query that is writing an object,
     * if true the query must force a shallow insert of the object if it is new.
     */
    public boolean isCommitInPostModify(Object domainObject) {
        return getPostModifyCommits().containsKey(domainObject);
    }

    /**
     * Return if the object is being in progress of being pre modify commit.
     * This should be called by any query that is writing an object,
     * if true the query must force a shallow insert of the object if it is new.
     */
    public boolean isCommitInPreModify(Object domainObject) {
        return getPreModifyCommits().containsKey(domainObject);
    }

    /**
     * Return if the object is shallow committed.
     * This is required to resolve bidirection references.
     */
    public boolean isShallowCommitted(Object domainObject) {
        return getShallowCommits().containsKey(domainObject);
    }

    /**
     * Mark the commit of the object as being fully completed.
     * This should be called by any query that has finished writing an object.
     */
    public void markCommitCompleted(Object domainObject) {
        getPreModifyCommits().remove(domainObject);
        getPostModifyCommits().remove(domainObject);
        // If not in a unit of work commit and the commit of this object is done reset the comitt manager
        if ((!isActive()) && getPostModifyCommits().isEmpty() && getPreModifyCommits().isEmpty()) {
            reinitialize();
            return;
        }
        getCompletedCommits().put(domainObject, domainObject);// Treat as set.
    }

    /**
     * Add an object as being in progress of being commited.
     * This should be called by any query that is writing an object.
     */
    public void markPostModifyCommitInProgress(Object domainObject) {
        getPreModifyCommits().remove(domainObject);
        getPostModifyCommits().put(domainObject, domainObject);// Use as set.
    }

    /**
     * Add an object as being in progress of being commited.
     * This should be called by any query that is writing an object.
     */
    public void markPreModifyCommitInProgress(Object domainObject) {
        removePendingCommit(domainObject);
        addProcessedCommit(domainObject);
        getPreModifyCommits().put(domainObject, domainObject);// Use as set.
    }

    /**
     * Mark the object as shallow committed.
     * This is required to resolve bidirection references.
     */
    public void markShallowCommit(Object domainObject) {
        getShallowCommits().put(domainObject, domainObject);// Use as set.
    }

    /**
     * Reset the commits.
     * This must be done before a new commit process is begun.
     */
    public void reinitialize() {
        setPendingCommits(null);
        setProcessedCommits(null);
        setPreModifyCommits(null);
        setPostModifyCommits(null);
        setCompletedCommits(null);
        setShallowCommits(null);
        setObjectsToDelete(null);
        setDataModifications(null);
    }

    /**
     * Remove the commit of the object from pending.
     */
    protected void removePendingCommit(Object domainObject) {
        getPendingCommits().remove(domainObject);
    }

    /**
     * Set the order in which objects should be commited to the database.
     * This order is based on ownership in the descriptors and is require for referencial integrity.
     * The commit order is a vector of vectors,
     * where the first vector is all root level classes, the second is classes owned by roots and so on.
     */
    public void setCommitOrder(Vector commitOrder) {
        this.commitOrder = commitOrder;
    }

    /**
     * Set the objects that have been written during this commit process.
     */
    protected void setCompletedCommits(HardIdentityHashtable completedCommits) {
        this.completedCommits = completedCommits;
    }

    /**
     * Used to store data querys to be performed at the end of the commit.
     * This is done to decrease dependencies and avoid deadlock.
     */
    protected void setDataModifications(Hashtable dataModifications) {
        this.dataModifications = dataModifications;
    }

    /**
     * Set if the commit manager is active.
     */
    public void setIsActive(boolean isActive) {
        this.isActive = isActive;
    }

    /**
     * Deletion are cached until the end.
     */
    protected void setObjectsToDelete(Vector objectsToDelete) {
        this.objectsToDelete = objectsToDelete;
    }

    /**
     * Set the objects that should be written during this commit process.
     */
    protected void setPendingCommits(HardIdentityHashtable pendingCommits) {
        this.pendingCommits = pendingCommits;
    }

    /**
     * Set the objects that should be written during this commit process.
     */
    protected void setProcessedCommits(HardIdentityHashtable processedCommits) {
        this.processedCommits = processedCommits;
    }

    /**
     * Set any objects that should be written during post modify commit process.
     * These objects should be order by their ownership constraints to maintain referencial integrity.
     */
    protected void setPostModifyCommits(HardIdentityHashtable postModifyCommits) {
        this.postModifyCommits = postModifyCommits;
    }

    /**
     * Set any objects that should be written during pre modify commit process.
     * These objects should be order by their ownership constraints to maintain referencial integrity.
     */
    protected void setPreModifyCommits(HardIdentityHashtable preModifyCommits) {
        this.preModifyCommits = preModifyCommits;
    }

    /**
     * Set the session that this is managing commits for.
     */
    protected void setSession(AbstractSession session) {
        this.session = session;
    }

    /**
     * Set any objects that have been shallow comitted during this commit process.
     */
    protected void setShallowCommits(HardIdentityHashtable shallowCommits) {
        this.shallowCommits = shallowCommits;
    }

    /**
     * Print the in progress depth.
     */
    public String toString() {
        int size = 0;
        if (preModifyCommits != null) {
            size += getPreModifyCommits().size();
        }
        if (postModifyCommits != null) {
            size += getPostModifyCommits().size();
        }
        Object[] args = { new Integer(size) };
        return Helper.getShortClassName(getClass()) + ToStringLocalization.buildMessage("commit_depth", args);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.helper;

import java.io.*;
import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.localization.*;
import oracle.toplink.essentials.internal.identitymaps.CacheKey;
import oracle.toplink.essentials.logging.*;

/**
 * INTERNAL:
 * <p>
 * <b>Purpose</b>: To maintain concurrency for a paticular task.
 * It is a wrappers of a semaphore that allows recursive waits by a single thread.
 * <p>
 * <b>Responsibilities</b>:
 * <ul>
 * <li> Keep track of the active thread.
 * <li> Wait all other threads until the first thread is done.
 * <li> Maintain the depth of the active thread.
 * </ul>
 */
public class ConcurrencyManager implements Serializable {
    protected int numberOfReaders;
    protected int depth;
    protected int numberOfWritersWaiting;
    protected transient Thread activeThread;
    public static Hashtable deferredLockManagers;
    protected boolean lockedByMergeManager;

    /** Cachkey owner set when ConcurrencyMananger is used within an cachekey on an idenity map
     * Used to store the owner so that the object involved can be retrieved from the cachekey
     */
    protected CacheKey ownerCacheKey;

    /**
     * Initialize the newly allocated instance of this class.
     * Set the depth to zero.
     */
    public ConcurrencyManager() {
        this.depth = 0;
        this.numberOfReaders = 0;
        this.numberOfWritersWaiting = 0;
    }

    /**
     * Initialize a new ConcurrencyManger, seting depth to zero and setting the
     * owner cacheKey.
     */
    public ConcurrencyManager(CacheKey cacheKey) {
        this();
        this.ownerCacheKey = cacheKey;
    }

    /**
     * Wait for all threads except the active thread.
     * If the active thread just increament the depth.
     * This should be called before entering a critical section.
     */
    public synchronized void acquire() throws ConcurrencyException {
        this.acquire(false);
    }

    /**
     * Wait for all threads except the active thread.
     * If the active thread just increament the depth.
     * This should be called before entering a critical section.
     * called with true from the merge process, if true then the refresh will not refresh the object
     */
    public synchronized void acquire(boolean forMerge) throws ConcurrencyException {
        while (!((getActiveThread() == Thread.currentThread()) || ((getActiveThread() == null) && (getNumberOfReaders() == 0)))) {
            // This must be in a while as multiple threads may be released, or another thread may rush the acquire after one is released.
            try {
                setNumberOfWritersWaiting(getNumberOfWritersWaiting() + 1);
                wait();
                setNumberOfWritersWaiting(getNumberOfWritersWaiting() - 1);
            } catch (InterruptedException exception) {
                throw ConcurrencyException.waitWasInterrupted(exception.getMessage());
            }
        }
        if (getActiveThread() == null) {
            setActiveThread(Thread.currentThread());
        }
        setIsLockedByMergeManager(forMerge);
        setDepth(getDepth() + 1);

    }

    /**
     * If the lock is not acquired allready acquire it and return true.
     * If it has been acquired allready return false
     * Added for CR 2317
     */
    public synchronized boolean acquireNoWait() throws ConcurrencyException {
        if (!isAcquired() || getActiveThread() == Thread.currentThread()) {
            //if I own the lock increment depth
            acquire(false);
            return true;
        } else {
            return false;
        }
    }

    /**
     * If the lock is not acquired allready acquire it and return true.
     * If it has been acquired allready return false
     * Added for CR 2317
     * called with true from the merge process, if true then the refresh will not refresh the object
     */
    public synchronized boolean acquireNoWait(boolean forMerge) throws ConcurrencyException {
        if (!isAcquired() || getActiveThread() == Thread.currentThread()) {
            //if I own the lock increment depth
            acquire(forMerge);
            return true;
        } else {
            return false;
        }
    }

    /**
     * Add deferred lock into a hashtable to avoid deadlock
     */
    public void acquireDeferredLock() throws ConcurrencyException {
        Thread currentThread = Thread.currentThread();
        DeferredLockManager lockManager = getDeferredLockManager(currentThread);
        if (lockManager == null) {
            lockManager = new DeferredLockManager();
            putDeferredLock(currentThread, lockManager);
        }
        lockManager.incrementDepth();
        synchronized (this) {
            while (!(getNumberOfReaders() == 0)) {
                // There are readers of this object, wait until they are done before determining if
                //there are any other writers. If not we will wait on the readers for acquire. If another
                //thread is also waiting on the acquire then a deadlock could occur. See bug 3049635
                //We could release all active locks before relesing defered but the object may not be finished building
                //we could make the readers get a hard lock, but then we would just build a defered lock even though
                //the object is not being built.
                try {
                    setNumberOfWritersWaiting(getNumberOfWritersWaiting() + 1);
                    wait();
                    setNumberOfWritersWaiting(getNumberOfWritersWaiting() - 1);
                } catch (InterruptedException exception) {
                    throw ConcurrencyException.waitWasInterrupted(exception.getMessage());
                }
            }
            if ((getActiveThread() == currentThread) || (!isAcquired())) {
                lockManager.addActiveLock(this);
                acquire();
            } else {
                lockManager.addDeferredLock(this);
                Object[] params = new Object[2];
                params[0] = this.getOwnerCacheKey().getObject();
                params[1] = currentThread.getName();
                AbstractSessionLog.getLog().log(SessionLog.FINER, "acquiring_deferred_lock", params, true);
            }
        }
    }
    
    /**
     * Check the lock state, if locked, acquire and release a read lock.
     * This optimizes out the normal read-lock check if not locked.
     */
    public void checkReadLock() throws ConcurrencyException {
        // If it is not locked, then just return.
        if (getActiveThread() == null) {
            return;
        }
        acquireReadLock();
        releaseReadLock();
    }
    
    /**
     * Wait on any writer.
     * Allow concurrent reads.
     */
    public synchronized void acquireReadLock() throws ConcurrencyException {
        // Cannot check for starving writers as will lead to deadlocks.
        while (!((getActiveThread() == Thread.currentThread()) || (getActiveThread() == null))) {
            try {
                wait();
            } catch (InterruptedException exception) {
                throw ConcurrencyException.waitWasInterrupted(exception.getMessage());
            }
        }

        setNumberOfReaders(getNumberOfReaders() + 1);
    }

    /**
     * If this is acquired return false otherwise acquire readlock and return true
     */
    public synchronized boolean acquireReadLockNoWait() {
        if (!isAcquired()) {
            acquireReadLock();
            return true;
        } else {
            return false;
        }
    }

    /**
         * Return the active thread.
         */
    public Thread getActiveThread() {
        return activeThread;
    }

    /**
     * Return the deferred lock manager from the thread
     */
    public synchronized static DeferredLockManager getDeferredLockManager(Thread thread) {
        return (DeferredLockManager)getDeferredLockManagers().get(thread);
    }

    /**
     * Return the deferred lock manager hashtable (thread - DeferredLockManager).
     */
    protected static Hashtable getDeferredLockManagers() {
        if (deferredLockManagers == null) {
            deferredLockManagers = new Hashtable(50);
        }

        return deferredLockManagers;
    }

    /**
     * Return the current depth of the active thread.
     */
    public int getDepth() {
        return depth;
    }

    /**
     * Number of writer that want the lock.
     * This is used to ensure that a writer is not starved.
     */
    public int getNumberOfReaders() {
        return numberOfReaders;
    }

    /**
     * Number of writers that want the lock.
     * This is used to ensure that a writer is not starved.
     */
    public int getNumberOfWritersWaiting() {
        return numberOfWritersWaiting;
    }

    /**
     * Returns the owner cache key for this concurrency manager
     */
    public CacheKey getOwnerCacheKey() {
        return this.ownerCacheKey;
    }

    /**
     * Return if a thread has aquire this manager.
     */
    public boolean isAcquired() {
        return depth > 0;
    }

    /**
     * INTERNAL:
     * Used byt the refresh process to determine if this concurrency manager is locked by
     * the merge process. If it is then the refresh should not refresh the object
     */
    public boolean isLockedByMergeManager() {
        return this.lockedByMergeManager;
    }

    /**
     * Check if the deferred locks of a thread are all released
     */
    public synchronized static boolean isBuildObjectOnThreadComplete(Thread thread, HardIdentityHashtable recursiveSet) {
        if (recursiveSet.containsKey(thread)) {
            return true;
        }
        recursiveSet.put(thread, thread);

        DeferredLockManager lockManager = getDeferredLockManager(thread);
        if (lockManager == null) {
            return true;
        }

        Vector deferredLocks = lockManager.getDeferredLocks();
        for (Enumeration deferredLocksEnum = deferredLocks.elements();
                 deferredLocksEnum.hasMoreElements();) {
            ConcurrencyManager deferedLock = (ConcurrencyManager)deferredLocksEnum.nextElement();
            Thread activeThread = null;
            if (deferedLock.isAcquired()) {
                activeThread = deferedLock.getActiveThread();

                // the active thread may be set to null at anypoint
                // if added for CR 2330
                if (activeThread != null) {
                    DeferredLockManager currentLockManager = getDeferredLockManager(activeThread);
                    if (currentLockManager == null) {
                        return false;
                    } else if (currentLockManager.isThreadComplete()) {
                        activeThread = deferedLock.getActiveThread();
                        // The lock may suddenly finish and no longer have an active thread.
                        if (activeThread != null) {
                            if (!isBuildObjectOnThreadComplete(activeThread, recursiveSet)) {
                                return false;
                            }
                        }
                    } else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * Return if this manager is within a nested aquire.
     */
    public boolean isNested() {
        return depth > 1;
    }

    public synchronized void putDeferredLock(Thread thread, DeferredLockManager lockManager) {
        getDeferredLockManagers().put(thread, lockManager);
    }

    /**
     * Decrement the depth for the active thread.
     * Assume the current thread is the active one.
     * Raise an error if the depth become < 0.
     * The notify will release the first thread waiting on the object,
     * if no threads are waiting it will do nothing.
     */
    public synchronized void release() throws ConcurrencyException {
        if (getDepth() == 0) {
            throw ConcurrencyException.signalAttemptedBeforeWait();
        } else {
            setDepth(getDepth() - 1);
        }
        if (getDepth() == 0) {
            setActiveThread(null);
            setIsLockedByMergeManager(false);
            notifyAll();
        }
    }

    /**
     * Release the deferred lock.
     * This uses a deadlock detection and resoultion algorthm to avoid cache deadlocks.
     * The deferred lock manager keeps track of the lock for a thread, so that other
     * thread know when a deadlock has occured and can resolve it.
     */
    public void releaseDeferredLock() throws ConcurrencyException {
        Thread currentThread = Thread.currentThread();
        DeferredLockManager lockManager = getDeferredLockManager(currentThread);
        if (lockManager == null) {
            return;
        }
        int depth = lockManager.getThreadDepth();

        if (depth > 1) {
            lockManager.decrementDepth();
            return;
        }

        // If the set is null or empty, means there is no deferred lock for this thread, return.
        if (!lockManager.hasDeferredLock()) {
            lockManager.releaseActiveLocksOnThread();
            removeDeferredLockManager(currentThread);
            return;
        }

        lockManager.setIsThreadComplete(true);

        // Thread have three stages, one where they are doing work (i.e. building objects)
        // two where they are done their own work but may be waiting on other threads to finish their work,
        // and a third when they and all the threads they are waiting on are done.
        // This is essentially a busy wait to determine if all the other threads are done.
        while (true) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            HardIdentityHashtable recursiveSet = new HardIdentityHashtable();
            if (isBuildObjectOnThreadComplete(currentThread, recursiveSet)) {// Thread job done.
                lockManager.releaseActiveLocksOnThread();
                removeDeferredLockManager(currentThread);
                Object[] params = new Object[1];
                params[0] = currentThread.getName();
                AbstractSessionLog.getLog().log(SessionLog.FINER, "deferred_locks_released", params, true);
                return;
            } else {// Not done yet, wait and check again.
                try {
                    Thread.sleep(10);
                } catch (InterruptedException ignoreAndContinue) {
                }
            }
        }
    }

    /**
     * Decrement the number of readers.
     * Used to allow concurrent reads.
     */
    public synchronized void releaseReadLock() throws ConcurrencyException {
        if (getNumberOfReaders() == 0) {
            throw ConcurrencyException.signalAttemptedBeforeWait();
        } else {
            setNumberOfReaders(getNumberOfReaders() - 1);
        }
        if (getNumberOfReaders() == 0) {
            notifyAll();
        }
    }

    /**
     * Remove the deferred lock manager for the thread
     */
    public synchronized static DeferredLockManager removeDeferredLockManager(Thread thread) {
        return (DeferredLockManager)getDeferredLockManagers().remove(thread);
    }

    /**
     * Set the active thread.
     */
    public void setActiveThread(Thread activeThread) {
        this.activeThread = activeThread;
    }

    /**
     * Set the current depth of the active thread.
     */
    protected void setDepth(int depth) {
        this.depth = depth;
    }

    /**
     * INTERNAL:
     * Used by the mergemanager to let the read know not to refresh this object as it is being
     * loaded by the merge process.
     */
    public void setIsLockedByMergeManager(boolean state) {
        this.lockedByMergeManager = state;
    }

    /**
     * Track the number of readers.
     */
    protected void setNumberOfReaders(int numberOfReaders) {
        this.numberOfReaders = numberOfReaders;
    }

    /**
     * Number of writers that want the lock.
     * This is used to ensure that a writer is not starved.
     */
    protected void setNumberOfWritersWaiting(int numberOfWritersWaiting) {
        this.numberOfWritersWaiting = numberOfWritersWaiting;
    }

    /**
     * Print the nested depth.
     */
    public String toString() {
        Object[] args = { new Integer(getDepth()) };
        return Helper.getShortClassName(getClass()) + ToStringLocalization.buildMessage("nest_level", args);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.queryframework;

import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;
import java.io.Serializable;
import java.lang.reflect.Constructor;

import java.util.IdentityHashMap;

import oracle.toplink.essentials.internal.helper.Helper;
import oracle.toplink.essentials.internal.helper.ClassConstants;
import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;
import oracle.toplink.essentials.internal.sessions.CollectionChangeRecord;
import oracle.toplink.essentials.internal.sessions.MergeManager;
import oracle.toplink.essentials.internal.sessions.ObjectChangeSet;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet;
import oracle.toplink.essentials.internal.sessions.CollectionChangeRecord;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.security.PrivilegedAccessHelper;
import oracle.toplink.essentials.internal.security.PrivilegedNewInstanceFromClass;
import oracle.toplink.essentials.internal.security.PrivilegedInvokeConstructor;
import oracle.toplink.essentials.internal.security.PrivilegedGetConstructorFor;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>:
 * Used to support collections in read queries.
 * <p>
 * <p><b>Responsibilities</b>:
 * Map the results into the appropriate collection instance.
 * Generically support special collections like cursored stream and virtual collection.
 *
 * @author James Sutherland
 * @since TOPLink/Java 1.2
 */
public abstract class ContainerPolicy implements Cloneable, Serializable {
    /** The descriptor is used to wrap and unwrap objects using the wrapper policy. **/
    protected transient ClassDescriptor elementDescriptor;
    protected transient Constructor constructor;

    /**
     * Default constructor.
     */
    public ContainerPolicy() {
    }

    /**
     * INTERNAL:
     * Add element to container however that needs to be done for the type of container.
     * Valid for some subclasses only.
     * Return whether the container changed.
     */
    protected boolean addInto(Object key, Object element, Object container) {
        throw QueryException.cannotAddToContainer(element, container, this);
    }

    /**
     * INTERNAL:
     * Add element to container.
     * This is used to add to a collection independent of JDK 1.1 and 1.2.
     * The session may be required to wrap for the wrapper policy.
     * Return whether the container changed
     */
    public boolean addInto(Object element, Object container, AbstractSession session) {
        return addInto(null, element, container, session);
    }

    /**
     * INTERNAL:
     * Add element to container.
     * This is used to add to a collection independent of JDK 1.1 and 1.2.
     * The session may be required to wrap for the wrapper policy.
     * Return whether the container changed
     */
    public boolean addInto(Object key, Object element, Object container, AbstractSession session) {
        Object elementToAdd = element;
        if (hasElementDescriptor()) {
            elementToAdd = getElementDescriptor().getObjectBuilder().wrapObject(element, session);
        }
        return addInto(key, elementToAdd, container);
    }

    /**
    * INTERNAL:
    * It is illegal to send this message to this receiver. Try one of my subclasses.
    * Throws an exception.
    *
    * @see #ListContainerPolicy
    */
    public void addIntoWithOrder(Integer index, Object element, Object container) {
        throw QueryException.methodDoesNotExistInContainerClass("set", getContainerClass());
    }

    /**
    * INTERNAL:
    * It is illegal to send this message to this receiver. Try one of my subclasses.
    * Throws an exception.
    *
    * @see #ListContainerPolicy
    */
    public void addIntoWithOrder(Integer index, Object element, Object container, AbstractSession session) {
        Object elementToAdd = element;
        if (hasElementDescriptor()) {
            elementToAdd = getElementDescriptor().getObjectBuilder().wrapObject(element, session);
        }
        addIntoWithOrder(index, elementToAdd, container);
    }

    /**
    * INTERNAL:
    * It is illegal to send this message to this receiver. Try one of my subclasses.
    * Throws an exception.
    *
    * @see #ListContainerPolicy
    */
    public void addIntoWithOrder(Vector indexes, Hashtable elements, Object container, AbstractSession session) {
        throw QueryException.methodDoesNotExistInContainerClass("set", getContainerClass());
    }
    
    /**
     * INTERNAL:
     * Return a container populated with the contents of the specified Vector.
     */
    public Object buildContainerFromVector(Vector vector, AbstractSession session) {
        Object container = containerInstance(vector.size());

        for (Enumeration e = vector.elements(); e.hasMoreElements();) {
            addInto(e.nextElement(), container, session);
        }
        return container;
    }

    /**
     * INTERNAL:
     * Return the appropriate container policy for the specified
     * concrete container class.
     */
    public static ContainerPolicy buildPolicyFor(Class concreteContainerClass) {
        return buildPolicyFor(concreteContainerClass, false);
    }
    
    /**
     * INTERNAL:
     * Return the appropriate container policy for the specified
     * concrete container class.
     */
    public static ContainerPolicy buildPolicyFor(Class concreteContainerClass, boolean hasOrdering) {
        if (Helper.classImplementsInterface(concreteContainerClass, ClassConstants.List_Class)) {
            if (hasOrdering) {
                return new OrderedListContainerPolicy(concreteContainerClass);
            } else {
                return new ListContainerPolicy(concreteContainerClass);
            }
        } else if (Helper.classImplementsInterface(concreteContainerClass, ClassConstants.SortedSet_Class)) {
            return new SortedCollectionContainerPolicy(concreteContainerClass);
        } else if (Helper.classImplementsInterface(concreteContainerClass, ClassConstants.Collection_Class)) {
            return new CollectionContainerPolicy(concreteContainerClass);
        } else if (Helper.classImplementsInterface(concreteContainerClass, ClassConstants.Map_Class)) {
            return new MapContainerPolicy(concreteContainerClass);
        }

        throw ValidationException.illegalContainerClass(concreteContainerClass);
    }

    /**
     * INTERNAL:
     * Remove all the elements from the specified container.
     * Valid only for certain subclasses.
     */
    public void clear(Object container) {
        throw QueryException.methodNotValid(this, "clear(Object container)");
    }

    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
    }

    public ContainerPolicy clone(ReadQuery query) {
        return (ContainerPolicy)clone();
    }

    /**
     * INTERNAL:
     * Return a clone of the specified container. Can only be called for select subclasses.
     */
    public Object cloneFor(Object container) {
        throw QueryException.cannotCreateClone(this, container);
    }

    /**
     * INTERNAL:
     * This method is used to calculate the differences between two collections.
     */
    public void compareCollectionsForChange(Object oldCollection, Object newCollection, CollectionChangeRecord changeRecord, AbstractSession session, ClassDescriptor referenceDescriptor) {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        IdentityHashMap originalKeyValues = new IdentityHashMap();
        IdentityHashMap cloneKeyValues = new IdentityHashMap();

        // Collect the values from the oldCollection.
        if (oldCollection != null) {
            Object backUpIter = iteratorFor(oldCollection);
            
            while (hasNext(backUpIter)) {
                Object secondObject = next(backUpIter, session);
    
                // CR2378 null check to prevent a null pointer exception - XC
                if (secondObject != null) {
                    originalKeyValues.put(secondObject, secondObject);
                }
            }
        }
        
        if (newCollection != null){
            // Collect the objects from the new Collection.
            Object cloneIter = iteratorFor(newCollection);
            
            while (hasNext(cloneIter)) {
                Object firstObject = next(cloneIter, session);
    
                // CR2378 null check to prevent a null pointer exception - XC
                // If value is null then nothing can be done with it.
                if (firstObject != null) {
                    if (originalKeyValues.containsKey(firstObject)) {
                        // There is an original in the cache
                        if ((compareKeys(firstObject, session))) {
                            // The keys have not changed
                            originalKeyValues.remove(firstObject);
                        } else {
                            // The keys have changed, create a changeSet
                            // (it will be resused later) and set the old key
                            // value to be used to remove.
                            Object backUpVersion = null;
    
                            // CR4172 compare the keys from the back up to the
                            // clone not from the original to the clone.
                            if (((UnitOfWorkImpl)session).isClassReadOnly(firstObject.getClass())) {
                                backUpVersion = ((UnitOfWorkImpl)session).getOriginalVersionOfObject(firstObject);
                            } else {
                                backUpVersion = ((UnitOfWorkImpl)session).getBackupClone(firstObject);
                            }
                            
                            ObjectChangeSet changeSet = referenceDescriptor.getObjectBuilder().createObjectChangeSet(firstObject, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
                            changeSet.setOldKey(keyFrom(backUpVersion, session));
                            changeSet.setNewKey(keyFrom(firstObject, session));
                            cloneKeyValues.put(firstObject, firstObject);
                        }
                    } else {
                        // Place it in the add collection
                        cloneKeyValues.put(firstObject, firstObject);
                    }
                }
            }
        }

        changeRecord.addAdditionChange(cloneKeyValues, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
        changeRecord.addRemoveChange(originalKeyValues, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
    }
    
    /**
     * INTERNAL:
     * Return true if keys are the same in the source as the backup. False otherwise
     * in the case of readonly compare against the original
     * For non map container policies return true always, because these policies have no concepts of Keys
     */
    public boolean compareKeys(Object sourceKey, AbstractSession session) {
        return true;
    }

    /**
     * INTERNAL:
     * Build a new container, add the contents of each of the specified containers
     * to it, and return it.
     * Both of the containers must use the same container policy (namely, this one).
     */
    public Object concatenateContainers(Object firstContainer, Object secondContainer) {
        Object container = containerInstance(sizeFor(firstContainer) + sizeFor(secondContainer));

        for (Object firstIter = iteratorFor(firstContainer); hasNext(firstIter);) {
            addInto(null, next(firstIter), container);
        }

        for (Object secondIter = iteratorFor(secondContainer); hasNext(secondIter);) {
            addInto(null, next(secondIter), container);
        }
        return container;
    }

    /**
     * INTERNAL:
     * Return an instance of the container class.
     * Null should never be returned.
     * A ValidationException is thrown on error.
     */
    public Object containerInstance() {
        try {
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    return AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(getContainerClass()));
                } catch (PrivilegedActionException exception) {
                    throw QueryException.couldNotInstantiateContainerClass(getContainerClass(), exception.getException());
                }
            } else {
                return PrivilegedAccessHelper.newInstanceFromClass(getContainerClass());
            }
        } catch (Exception ex) {
            throw QueryException.couldNotInstantiateContainerClass(getContainerClass(), ex);
        }
    }

    /**
     * INTERNAL:
     * Return an instance of the container class with the specified initial capacity.
     * Null should never be returned.
     * A ValidationException is thrown on error.
     */
    public Object containerInstance(int initialCapacity) {
        if (getConstructor() == null) {
            return containerInstance();
        }
        try {
            Object[] arguments = new Object[1];

            //Code change for 3732. No longer need to add 1 as this was for JDK 1.1
            arguments[0] = new Integer(initialCapacity);
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    return AccessController.doPrivileged(new PrivilegedInvokeConstructor(getConstructor(), arguments));
                } catch (PrivilegedActionException exception) {
                    throw QueryException.couldNotInstantiateContainerClass(getContainerClass(), exception.getException());
                }
            } else {
                return PrivilegedAccessHelper.invokeConstructor(getConstructor(), arguments);
            }
        } catch (Exception ex) {
            throw QueryException.couldNotInstantiateContainerClass(getContainerClass(), ex);
        }
    }

    /**
     * INTERNAL:
     * Return whether element exists in container.
     */
    protected boolean contains(Object element, Object container) {
        throw QueryException.methodNotValid(this, "contains(Object element, Object container)");
    }

    /**
     * INTERNAL:
     * Check if the object is contained in the collection.
     * This is used to check contains in a collection independent of JDK 1.1 and 1.2.
     * The session may be required to unwrap for the wrapper policy.
     */
    public boolean contains(Object element, Object container, AbstractSession session) {
        if (hasElementDescriptor() && getElementDescriptor().hasWrapperPolicy()) {
            // The wrapper for the object must be removed.
            Object iterator = iteratorFor(container);
            while (hasNext(iterator)) {
                Object next = next(iterator);
                if (getElementDescriptor().getObjectBuilder().unwrapObject(next, session).equals(element)) {
                    return true;
                }
            }
            return false;
        } else {
            return contains(element, container);
        }
    }

    /**
     * INTERNAL:
     * Return whether element exists in container.
     */
    protected boolean containsKey(Object element, Object container) {
        throw QueryException.methodNotValid(this, "containsKey(Object element, Object container)");
    }

    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this ContainerPolicy to actual class-based
     * settings
     * This method is implemented by subclasses as necessary.
     * @param classLoader
     */
    public void convertClassNamesToClasses(ClassLoader classLoader){};

    /**
     * INTERNAL:
     * This can be used by collection such as cursored stream to gain control over execution.
     */
    public Object execute() {
        throw QueryException.methodNotValid(this, "execute()");
    }
    
    /**
     * INTERNAL:
     * Return the size constructor if available.
     */
    protected Constructor getConstructor() {
        return constructor;
    }

    /**
     * INTERNAL:
     * Return the class used for the container.
     */
    public Class getContainerClass() {
        throw QueryException.methodNotValid(this, "getContainerClass()");
    }

    /**
     * INTERNAL:
     * Used by the MW
     */
    public String getContainerClassName() {
        throw QueryException.methodNotValid(this, "getContainerClassName()");
    }

    /**
     * INTERNAL:
     * Used for wrapping and unwrapping with the wrapper policy.
     */
    public ClassDescriptor getElementDescriptor() {
        return elementDescriptor;
    }
    
    /**
     * INTERNAL:
     * Used for wrapping and unwrapping with the wrapper policy.
     */
    public boolean hasElementDescriptor() {
        return getElementDescriptor() != null;
    }

    /**
     * INTERNAL:
     * Return whether the iterator has more objects.
     * The iterator is the one returned from #iteratorFor().
     * Valid for some subclasses only.
     *
     * @see ContainerPolicy#iteratorFor(java.lang.Object)
     */
    public abstract boolean hasNext(Object iterator);

    /**
     * INTERNAL:
     * Returns true if the collection has order
     */
    public boolean hasOrder() {
        return false;
    }

    /**
     * INTERNAL:
     * Find the size constructor.
     * Providing a size is important for performance.
     */
    public void initializeConstructor() {
        try {
            Constructor constructor = null;
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    constructor = (Constructor)AccessController.doPrivileged(new PrivilegedGetConstructorFor(getContainerClass(), new Class[] { ClassConstants.PINT }, false));
                } catch (PrivilegedActionException exception) {
                    // If there is no constructor then the default will be used.
                    return;
                }
            } else {
                constructor = PrivilegedAccessHelper.getConstructorFor(getContainerClass(), new Class[] { ClassConstants.PINT }, false);
            }
            setConstructor(constructor);
        } catch (Exception exception) {
            // If there is no constructor then the default will be used.
            return;
        }
    }

    public boolean isCollectionPolicy() {
        return false;
    }

    /**
     * Is this a Cursored stream?
     *
     */
    public boolean isCursoredStreamPolicy() {
        return false;
    }

    public boolean isCursorPolicy() {
        return false;
    }

    public boolean isCursorStreamPolicy() {
        return false;
    }

    public boolean isDirectMapPolicy() {
        return false;
    }

    /**
     * INTERNAL:
     * Return whether the container is empty.
     */
    public boolean isEmpty(Object container) {
        return sizeFor(container) == 0;
    }

    public boolean isListPolicy() {
        return false;
    }

    public boolean isMapPolicy() {
        return false;
    }
    
    public boolean isScrollableCursorPolicy() {
        return false;
    }

    /**
     * INTERNAL:
     * Return whether the specified object is of a valid container type.
     *
     * @see oracle.toplink.essentials.internal.queryframework.CollectionContainerPolicy#isValidContainer(Object)
     * @see oracle.toplink.essentials.internal.queryframework.MapContainerPolicy#isValidContainer(Object)
     */
    public boolean isValidContainer(Object container) {
        throw QueryException.methodNotValid(this, "isValidContainer(Object container)");
    }

    /**
     * INTERNAL:
     * Return whether the specified type is a valid container type.
     */
    public boolean isValidContainerType(Class containerType) {
        throw QueryException.methodNotValid(this, "isValidContainerType(Class containerType)");
    }

    /**
     * INTERNAL:
     * Return an iterator for the given container.
     * This iterator can then be used as a parameter to #hasNext()
     * and #next().
     *
     * @see ContainerPolicy#hasNext(java.lang.Object)
     * @see ContainerPolicy#next(java.lang.Object)
     */
    public abstract Object iteratorFor(Object container);

    /**
     * INTERNAL:
     * Return the key for the specified element.
     *
     * @param element java.lang.Object
     * @return java.lang.Object
     */
    public Object keyFrom(Object element, AbstractSession session) {
        return null;
    }
    
    /**
     * INTERNAL:
     * Merge changes from the source to the target object. Because this is a
     * collection mapping, values are added to or removed from the collection
     * based on the change set.
     */
    public Object mergeCascadeParts(ObjectChangeSet objectChanges, MergeManager mergeManager, AbstractSession parentSession) {
        Object object = null;
                
        if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
            // CR 2855 - Try to find the object first we may have merged it already.
            object = objectChanges.getTargetVersionOfSourceObject(parentSession);
                        
            if ((object == null) && (objectChanges.isNew() || objectChanges.isAggregate()) && objectChanges.containsChangesFromSynchronization()) {
                if (!mergeManager.getObjectsAlreadyMerged().containsKey(objectChanges)) {
                    // CR 2855 - If we haven't merged this object already then
                    // build a new object otherwise leave it as null which will
                    // stop the recursion.
                    // CR 3424 - Need to build the right instance based on
                    // class type instead of referenceDescriptor.
                    Class objectClass = objectChanges.getClassType(mergeManager.getSession());
                    object = mergeManager.getSession().getDescriptor(objectClass).getObjectBuilder().buildNewInstance();
                    // Store the change set to prevent us from creating this new object again.
                    mergeManager.getObjectsAlreadyMerged().put(objectChanges, object);
                } else {
                    // CR 4012 - We have all ready created the object, must be
                    // in a cyclic merge on a new object so get it out of the
                    // already merged collection
                    object = mergeManager.getObjectsAlreadyMerged().get(objectChanges);
                }
            } else {
                object = objectChanges.getTargetVersionOfSourceObject(parentSession, true);
            }
                        
            if (objectChanges.containsChangesFromSynchronization()) {
                mergeManager.mergeChanges(object, objectChanges);
            }
        } else {
            mergeManager.mergeChanges(objectChanges.getUnitOfWorkClone(), objectChanges);
        }
        
        return object;
    }
    
    /**
     * INTERNAL:
     * Merge changes from the source to the target object. Because this is a
     * collection mapping, values are added to or removed from the collection
     * based on the change set.
     */
    public void mergeChanges(CollectionChangeRecord changeRecord, Object valueOfTarget, boolean shouldMergeCascadeParts, MergeManager mergeManager, AbstractSession parentSession) {
        ObjectChangeSet objectChanges;
        
        // Step 1 - iterate over the removed changes and remove them from the container.
        Enumeration removeObjects = changeRecord.getRemoveObjectList().keys();
            
        while (removeObjects.hasMoreElements()) {
            objectChanges = (ObjectChangeSet) removeObjects.nextElement();
            
            synchronized (valueOfTarget) {
                removeFrom(objectChanges.getOldKey(), objectChanges.getTargetVersionOfSourceObject(mergeManager.getSession()), valueOfTarget, parentSession);
            }
            
            if (!mergeManager.shouldMergeChangesIntoDistributedCache()) {
                mergeManager.registerRemovedNewObjectIfRequired(objectChanges.getUnitOfWorkClone());
            }
        }
            
        // Step 2 - iterate over the added changes and add them to the container.
        Enumeration addObjects = changeRecord.getAddObjectList().keys();
            
        while (addObjects.hasMoreElements()) {
            objectChanges = (ObjectChangeSet) addObjects.nextElement();
            Object object = null;
                
            if (shouldMergeCascadeParts) {
                object = mergeCascadeParts(objectChanges, mergeManager, parentSession);
            }
                
            if (object == null) {
                // Retrieve the object to be added to the collection.
                object = objectChanges.getTargetVersionOfSourceObject(mergeManager.getSession(), false);
            }
    
            synchronized (valueOfTarget) {
                // I am assuming that at this point the above merge will have created a new object if required
                if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
                    //bug#4458089 and 4454532- check if collection contains new item before adding during merge into distributed cache
                    if (!contains(object, valueOfTarget, mergeManager.getSession())) {
                        addInto(objectChanges.getNewKey(), object, valueOfTarget, mergeManager.getSession());
                    }
                } else {
                    addInto(objectChanges.getNewKey(), object, valueOfTarget, mergeManager.getSession());
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     * Return the next object on the queue. The iterator is the one
     * returned from #iteratorFor().
     * Valid for some subclasses only.
     *
     * @see ContainerPolicy#iteratorFor(java.lang.Object)
     */
    protected abstract Object next(Object iterator);

    /**
     * INTERNAL:
     * Return the next object from the iterator.
     * This is used to stream over a collection independent of JDK 1.1 and 1.2.
     * The session may be required to unwrap for the wrapper policy.
     */
    public Object next(Object iterator, AbstractSession session) {
        Object next = next(iterator);
        if (hasElementDescriptor()) {
            next = getElementDescriptor().getObjectBuilder().unwrapObject(next, session);
        }
        return next;
    }

    /**
     * This can be used by collection such as cursored stream to gain control over execution.
     */
    public boolean overridesRead() {
        return false;
    }

    /**
     * Prepare and validate.
     * Allow subclasses to override.
     */
    public void prepare(DatabaseQuery query, AbstractSession session) throws QueryException {
        if (query.isReadAllQuery() && (!query.isReportQuery()) && query.shouldUseWrapperPolicy()) {
            setElementDescriptor(query.getDescriptor());
            //make sure DataReadQuery points to this container policy
        } else if (query.isDataReadQuery()) {
            ((DataReadQuery)query).setContainerPolicy(this);
        }
    }

    /**
     * Prepare and validate.
     * Allow subclasses to override.
     */
    public void prepareForExecution() throws QueryException {
    }

    /**
     * This method is used to bridge the behaviour between Attribute Change Tracking and
     * deferred change tracking with respect to adding the same instance multiple times.
     * Each containerplicy type will implement specific behaviour for the collection
     * type it is wrapping. These methods are only valid for collections containing object references
     */
    public void recordAddToCollectionInChangeRecord(ObjectChangeSet changeSetToAdd, CollectionChangeRecord collectionChangeRecord){
        if (collectionChangeRecord.getRemoveObjectList().containsKey(changeSetToAdd)) {
            collectionChangeRecord.getRemoveObjectList().remove(changeSetToAdd);
        } else {
            collectionChangeRecord.getAddObjectList().put(changeSetToAdd, changeSetToAdd);
        }
    }
    
    /**
     * This method is used to bridge the behaviour between Attribute Change Tracking and
     * deferred change tracking with respect to adding the same instance multiple times.
     * Each containerplicy type will implement specific behaviour for the collection
     * type it is wrapping. These methods are only valid for collections containing object references
     */
    public void recordRemoveFromCollectionInChangeRecord(ObjectChangeSet changeSetToRemove, CollectionChangeRecord collectionChangeRecord){
        if(collectionChangeRecord.getAddObjectList().containsKey(changeSetToRemove)) {
            collectionChangeRecord.getAddObjectList().remove(changeSetToRemove);
        } else {
            collectionChangeRecord.getRemoveObjectList().put(changeSetToRemove, changeSetToRemove);
        }
    }
    
    /**
     * This can be used by collection such as cursored stream to gain control over execution.
     */
    public Object remoteExecute() {
        return null;
    }

    /**
     * INTERNAL:
     * Remove all the elements from container.
     * Valid only for certain subclasses.
     */
    public void removeAllElements(Object container) {
        clear(container);
    }

    /**
     * INTERNAL:
     * Remove element from container.
     * Valid for some subclasses only.
     */
    protected boolean removeFrom(Object key, Object element, Object container) {
        throw QueryException.cannotRemoveFromContainer(element, container, this);
    }

    /**
     * INTERNAL:
     * Remove the object from the collection.
     * This is used to remove from a collection independent of JDK 1.1 and 1.2.
     * The session may be required to unwrap for the wrapper policy.
     */
    public boolean removeFrom(Object key, Object element, Object container, AbstractSession session) {
        Object objectToRemove = element;
        if (hasElementDescriptor() && getElementDescriptor().hasWrapperPolicy()) {
            // The wrapper for the object must be removed.
            Object iterator = iteratorFor(container);
            while (hasNext(iterator)) {
                Object next = next(iterator);
                if (getElementDescriptor().getObjectBuilder().unwrapObject(next, session).equals(element)) {
                    objectToRemove = next;
                    break;
                }
            }
        }

        return removeFrom(key, objectToRemove, container);
    }

    /**
     * INTERNAL:
     * Remove the object from the collection.
     * This is used to remove from a collection independent of JDK 1.1 and 1.2.
     * The session may be required to unwrap for the wrapper policy.
     */
    public boolean removeFrom(Object element, Object container, AbstractSession session) {
        return removeFrom(null, element, container, session);
    }

    /**
    * INTERNAL:
    * It is illegal to send this message to this receiver. Try one of my subclasses.
    * Throws an exception.
    *
    * @see #ListContainerPolicy
    */
    public void removeFromWithOrder(int beginIndex, Object container) {
        throw QueryException.methodDoesNotExistInContainerClass("remove(index)", getContainerClass());
    }

    /**
     * INTERNAL:
     * Set the size constructor if available.
     */
    protected void setConstructor(Constructor constructor) {
        this.constructor = constructor;
    }

    /**
     * INTERNAL:
     * Set the class used for the container.
     */
    public void setContainerClass(Class containerClass) {
        throw QueryException.methodNotValid(this, "getContainerClass()");
    }

    /**
     * INTERNAL:
     * Used by the MW
     */
    public void setContainerClassName(String containerClassName) {
        throw QueryException.methodNotValid(this, "getContainerClassName()");
    }
    
    /**
     * INTERNAL:
     * Used for wrapping and unwrapping with the wrapper policy.
     */
    public void setElementDescriptor(ClassDescriptor elementDescriptor) {
        this.elementDescriptor = elementDescriptor;
    }

    /**
     * INTERNAL:
     * It is illegal to send this message to this receiver. Try one of my
     * subclasses. Throws an exception.
     *
     * @see #MapContainerPolicy
     */
    public void setKeyName(String instanceVariableName, String elementClassName) {
        throw ValidationException.containerPolicyDoesNotUseKeys(this, instanceVariableName);
    }

    /**
     * INTERNAL:
     * Return the size of container.
     */
    public int sizeFor(Object container) {
        throw QueryException.methodNotValid(this, "sizeFor(Object container)");
    }

    public String toString() {
        return Helper.getShortClassName(this.getClass()) + "(" + toStringInfo() + ")";
    }

    protected Object toStringInfo() {
        return "";
    }

    /**
     * INTERNAL:
     * over ride in MapPolicy subclass
     */
    public void validateElementAndRehashIfRequired(Object sourceValue, Object target, AbstractSession session, Object targetVersionOfSource) {
        //do nothing
    }

    /**
     * INTERNAL:
     * Return a Vector populated with the contents of container.
     * Added for bug 2766379, must implement a version of vectorFor that
     * handles wrapped objects.
     */
    public Vector vectorFor(Object container, AbstractSession session) {
        Vector result = new Vector(sizeFor(container));

        for (Object iter = iteratorFor(container); hasNext(iter);) {
            result.addElement(next(iter, session));
        }
        return result;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings;

import java.io.*;
import java.util.*;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.indirection.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.indirection.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;

/**
 * <p><b>Purpose</b>: Defines how an attribute of an object maps to and from the database
 *
 * <p><b>Responsibilities</b>:<ul>
 * <li> Define type of relationship (1:1/1:M/M:M/etc.)
 * <li> Define instance variable name and fields names required
 * <li> Define any additional properties (ownership, indirection, read only, etc.)
 * <li> Control building the value for the instance variable from the database row
 * <li> Control building the database fields from the object
 * <li> Control any pre/post updating/inserting/deleting required to maintain the relationship
 * <li> Merges object changes for unit of work.
 * <li> Clones objects for unit of work.
 * <li> cache computed information to optimize performance
 * </ul>
 *
 * @author Sati
 * @since TOPLink/Java 1.0
 */
public abstract class DatabaseMapping implements Cloneable, Serializable {

    /** Used to reduce memory for mappings with no fields. */
    protected static final Vector NO_FIELDS = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(0);

    /** Used to share integer instance to reduce memory. */
    protected static final Integer NO_WEIGHT = new Integer(Integer.MAX_VALUE);
    protected static final Integer WEIGHT_1 = new Integer(1);

    /** Descriptor to which this mapping belongs to */
    protected ClassDescriptor descriptor;

    /** Wrapper to store the reference objects. */
    protected AttributeAccessor attributeAccessor;

    /** Makes this mapping read only. No write are performed on it. Default is false */
    protected boolean isReadOnly;
    
    /** Specifies whether this mapping is optional (i.e. field may be null). Used for DDL generation. */
    protected boolean isOptional;

    /** Fields associated with the mappings are cached */
    protected Vector<DatabaseField> fields;

    /** It is needed only in remote initialization and mapping is in parent descriptor */
    protected boolean isRemotelyInitialized;

    /** This is a TopLink defined attribute that allows us to sort the mappings */
    protected Integer weight = NO_WEIGHT;

    /** used as a temporary store for custom SDK usage */
    protected Map properties;

    /** used as a quick check to see if this mapping is a primary key mapping,
     * set by the object builder during initialization.
     */
    protected boolean primaryKeyMapping = false;

    /**
     * PUBLIC:
     * Default constructor.
     */
    public DatabaseMapping() {
        this.isOptional = true;
        this.isReadOnly = false;
        this.attributeAccessor = new InstanceVariableAttributeAccessor();
    }

    /**
     * INTERNAL:
     * Clone the attribute from the clone and assign it to the backup.
     */
    public abstract void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork);

    /**
     * INTERNAL:
     * Require for cloning, the part must be cloned.
     */
    public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
        throw DescriptorException.invalidMappingOperation(this, "buildBackupCloneForPartObject");
    }

    /**
     * INTERNAL:
     * Clone the attribute from the original and assign it to the clone.
     */
    public abstract void buildClone(Object original, Object clone, UnitOfWorkImpl unitOfWork);

    /**
     * INTERNAL:
     * A combination of readFromRowIntoObject and buildClone.
     * <p>
     * buildClone assumes the attribute value exists on the original and can
     * simply be copied.
     * <p>
     * readFromRowIntoObject assumes that one is building an original.
     * <p>
     * Both of the above assumptions are false in this method, and actually
     * attempts to do both at the same time.
     * <p>
     * Extract value from the row and set the attribute to this value in the
     * working copy clone.
     * In order to bypass the shared cache when in transaction a UnitOfWork must
     * be able to populate working copies directly from the row.
     */
    public abstract void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession);

    /**
     * INTERNAL:
     * Builds a shallow original object. Only direct attributes and primary
     * keys are populated. In this way the minimum original required for
     * instantiating a working copy clone can be built without placing it in
     * the shared cache (no concern over cycles).
     */
    public void buildShallowOriginalFromRow(AbstractRecord databaseRow, Object original, ObjectBuildingQuery query, AbstractSession executionSession) {
        return;
    }

    /**
     * INTERNAL:
     * Require for cloning, the part must be cloned.
     */
    public Object buildCloneForPartObject(Object attributeValue, Object original, Object clone, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        throw DescriptorException.invalidMappingOperation(this, "buildCloneForPartObject");
    }

    /**
     * INTERNAL:
     * Copy of the attribute of the object.
     * This is NOT used for unit of work but for templatizing an object.
     */
    public void buildCopy(Object copy, Object original, ObjectCopyingPolicy policy) {
    }

    /**
     * INTERNAL:
     * Used to allow object level comparisons.
     */
    public Expression buildObjectJoinExpression(Expression base, Object value, AbstractSession session) {
        throw QueryException.unsupportedMappingForObjectComparison(this, base);
    }

    /**
     * INTERNAL:
     * Used to allow object level comparisons.
     */
    public Expression buildObjectJoinExpression(Expression base, Expression argument, AbstractSession session) {
        throw QueryException.unsupportedMappingForObjectComparison(this, base);
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    abstract public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects);

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    abstract public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects);

    /**
     * INTERNAL:
     * Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes
     * as apposed to detected changes. If an attribute can not be change tracked it's
     * changes can be detected through this process.
     */
    public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session){
        throw DescriptorException.invalidMappingOperation(this, "calculatedDeferredChanges");
    }
    
    /**
     * INTERNAL:
     * Cascade the merge to the component object, if appropriate.
     */
    public void cascadeMerge(Object sourceElement, MergeManager mergeManager) {
        throw DescriptorException.invalidMappingOperation(this, "cascadeMerge");
    }

    /**
     * INTERNAL:
     * Clones itself.
     */
    public Object clone() {
        // Bug 3037701 - clone the AttributeAccessor
        DatabaseMapping mapping = null;
        try {
            mapping = (DatabaseMapping)super.clone();
        } catch (CloneNotSupportedException e) {
            throw new InternalError();
        }
        mapping.setAttributeAccessor((AttributeAccessor)attributeAccessor.clone());
        return mapping;
    }

    /**
     * INTERNAL:
     * Helper method to clone vector of fields (used in aggregate initialization cloning).
     */
    protected Vector cloneFields(Vector fields) {
        Vector clonedFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        for (Enumeration fieldsEnum = fields.elements(); fieldsEnum.hasMoreElements();) {
            clonedFields.addElement(((DatabaseField)fieldsEnum.nextElement()).clone());
        }

        return clonedFields;
    }

    /**
     * This method must be overwritten in the subclasses to return a vector of all the
     * fields this mapping represents.
     */
    protected Vector<DatabaseField> collectFields() {
        return NO_FIELDS;
    }

    /**
     * INTERNAL:
     * This method was created in VisualAge.
     * @return prototype.changeset.ChangeRecord
     */
    abstract public ChangeRecord compareForChange(Object clone, Object backup, ObjectChangeSet owner, AbstractSession session);

    /**
     * INTERNAL:
     * Compare the attributes belonging to this mapping for the objects.
     */
    public abstract boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session);

    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this mapping to actual class-based
     * settings
     * This method is implemented by subclasses as necessary.
     * @param classLoader
     */
    public void convertClassNamesToClasses(ClassLoader classLoader){};

    /**
     * INTERNAL:
     * Builder the unit of work value holder.
     * @param buildDirectlyFromRow indicates that we are building the clone directly
     * from a row as opposed to building the original from the row, putting it in
     * the shared cache, and then cloning the original.
     */
    public UnitOfWorkValueHolder createUnitOfWorkValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractRecord row, UnitOfWorkImpl unitOfWork, boolean buildDirectlyFromRow) {
        throw DescriptorException.invalidMappingOperation(this, "createUnitOfWorkValueHolder");
    }

    /**
     * INTERNAL:
     * Extract the nested attribute expressions that apply to this mapping.
     * This is used for partial objects and joining.
     * @param rootExpressionsAllowed true if newRoot itself can be one of the
     * expressions returned
     */
    protected Vector extractNestedExpressions(List expressions, ExpressionBuilder newRoot, boolean rootExpressionsAllowed) {
        Vector nestedExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(expressions.size());

        for (Iterator expressionsEnum = expressions.iterator();
                 expressionsEnum.hasNext();) {
            Expression next = (Expression)expressionsEnum.next();

            // The expressionBuilder can be one of the locked expressions in
            // the ForUpdateOfClause.
            if (!next.isQueryKeyExpression()) {
                continue;
            }
            QueryKeyExpression expression = (QueryKeyExpression)next;
            QueryKeyExpression base = expression;
            boolean afterBase = false;
            while (!base.getBaseExpression().isExpressionBuilder()) {
                afterBase = true;
                base = (QueryKeyExpression)base.getBaseExpression();
            }
            if (afterBase && base.getName().equals(getAttributeName())) {
                nestedExpressions.addElement(expression.rebuildOn(base, newRoot));
            } else if (rootExpressionsAllowed && expression.getBaseExpression().isExpressionBuilder() && expression.getName().equals(getAttributeName())) {
                nestedExpressions.addElement(newRoot);
            }
        }
        return nestedExpressions;
    }

    /**
     * ADVANCED:
     * Return the attributeAccessor.
     * The attribute accessor is responsible for setting and retrieving the attribute value
     * from the object for this mapping.
     */
    public AttributeAccessor getAttributeAccessor() {
        return attributeAccessor;
    }

    /**
     * PUBLIC:
     * The classification type for the attribute this mapping represents
     */
    public Class getAttributeClassification() {
        return null;
    }

    /**
     * PUBLIC:
     * Return the name of the attribute set in the mapping.
     */
    public String getAttributeName() {
        return getAttributeAccessor().getAttributeName();
    }

    /**
     * INTERNAL:
     * Return the value of an attribute which this mapping represents for an object.
     */
    public Object getAttributeValueFromObject(Object object) throws DescriptorException {
        try {
            return getAttributeAccessor().getAttributeValueFromObject(object);
        } catch (DescriptorException exception) {
            exception.setMapping(this);
            throw exception;
        }
    }

    /**
     * INTERNAL:
     * Return the mapping's containerPolicy.
     */
    public ContainerPolicy getContainerPolicy() {
        throw DescriptorException.invalidMappingOperation(this, "getContainerPolicy");
    }

    /**
     * INTERNAL:
     * Return the descriptor to which this mapping belongs
     */
    public ClassDescriptor getDescriptor() {
        return descriptor;
    }

    /**
     * INTERNAL:
     * Return the field associated with this mapping if there is exactly one.
     * This is required for object relational mapping to print them, but because
     * they are defined in Enterprise they cannot be cast to.
     * Mappings that have a field include direct mappings and object relational mappings.
     */
    public DatabaseField getField() {
        return null;
    }

    /**
     * INTERNAL:
     * Return the classifiction for the field contained in the mapping.
     * This is used to convert the row value to a consistent java value.
     * By default this is unknown.
     */
    public Class getFieldClassification(DatabaseField fieldToClassify) {
        return null;
    }

    /**
     * INTERNAL:
     * Returns a vector of all the fields this mapping represents.
     */
    public Vector<DatabaseField> getFields() {
        return this.fields;
    }

    /**
     * PUBLIC:
     * This method is invoked reflectively on the reference object to return the value of the
     * attribute in the object. This method returns the name of the getMethodName or null if not using method access.
     */
    public String getGetMethodName() {
        if (!(getAttributeAccessor() instanceof MethodAttributeAccessor)) {
            return null;
        }
        return ((MethodAttributeAccessor)getAttributeAccessor()).getGetMethodName();
    }

    /**
     * INTERNAL:
     * used as a temporary store for custom SDK usage
     */
    public Map getProperties() {
        if (properties == null) {//Lazy initialize to conserve space and allocation time.
            properties = new HashMap(5);
        }
        return properties;
    }

    /**
     * INTERNAL:
     * used as a temporary store for custom SDK usage
     */
    public Object getProperty(Object property) {
        if (properties == null) {
            return null;
        }

        return getProperties().get(property);
    }

    /**
     * INTERNAL:
     * Return the value of an attribute unwrapping value holders if required.
     */
    public Object getRealAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException {
        return getAttributeValueFromObject(object);
    }

    /**
     * INTERNAL:
     * Return the value of an attribute, unwrapping value holders if necessary.
     * If the value is null, build a new container.
     */
    public Object getRealCollectionAttributeValueFromObject(Object object, AbstractSession session) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "getRealCollectionAttributeValueFromObject");
    }

    /**
     * INTERNAL:
     * Return the referenceDescriptor. This is a descriptor which is associated with
     * the reference class.
     * Replaced by {_at_link #getReferenceClassDescriptor()}
     */
    public ClassDescriptor getReferenceDescriptor() {
        return null;
    }

    /**
     * PUBLIC:
     * Return the referenceDescriptor. This is a descriptor which is associated with
     * the reference class.
     */
    public ClassDescriptor getReferenceClassDescriptor() {
                ClassDescriptor desc = getReferenceDescriptor();
                if (desc instanceof ClassDescriptor) {
                        return (ClassDescriptor)desc;
                } else {
                        throw ValidationException.cannotCastToClass(desc, desc.getClass(), ClassDescriptor.class);
                }
    }

    /**
     * INTERNAL:
     * Return the relationshipPartner mapping for this bi-directional mapping. If the relationshipPartner is null then
     * this is a uni-directional mapping.
     */
    public DatabaseMapping getRelationshipPartner() {
        return null;
    }

    /**
     * PUBLIC:
     * This method is invoked reflectively on the reference object to set the value of the
     * attribute in the object. This method returns the name of the setMethodName or null if not using method access.
     */
    public String getSetMethodName() {
        if (!(getAttributeAccessor() instanceof MethodAttributeAccessor)) {
            return null;
        }
        return ((MethodAttributeAccessor)getAttributeAccessor()).getSetMethodName();
    }

    /**
     * INTERNAL:
     * Return the weight of the mapping, used to sort mappings to ensure that
     * DirectToField Mappings get merged first
     */
    public Integer getWeight() {
        return this.weight;
    }

    /**
     * INTERNAL:
     * The returns if the mapping has any constraint dependencies, such as foreign keys and join tables.
     */
    public boolean hasConstraintDependency() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if method access is used.
     */
    public boolean isUsingMethodAccess() {
        return getAttributeAccessor() instanceof MethodAttributeAccessor;
    }

    /**
     * INTERNAL:
     * Return if the mapping has any ownership or other dependency over its target object(s).
     */
    public boolean hasDependency() {
        return isPrivateOwned();
    }

    /**
     * INTERNAL:
     * The returns if the mapping has any inverse constraint dependencies, such as foreign keys and join tables.
     */
    public boolean hasInverseConstraintDependency() {
        return false;
    }

    /**
     * INTERNAL:
     * Allow for initialization of properties and validation.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        ;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAggregateCollectionMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAggregateMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAggregateObjectMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isCollectionMapping() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isDatabaseMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isDirectCollectionMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isDirectMapMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isDirectToFieldMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isForeignReferenceMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isManyToManyMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isNestedTableMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isObjectReferenceMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isObjectTypeMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isOneToManyMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isOneToOneMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Return whether the value of this mapping is optional (that is, can be
     * null). This is a hint and is used when generating DDL. It should be
     * disregarded for primitive types.
     */
    public boolean isOptional() {
        return isOptional;
    }
    
    /**
     * INTERNAL:
     * All EIS mappings should implement this method to return true.
     */
    public boolean isEISMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * All relational mappings should implement this method to return true.
     */
    public boolean isRelationalMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * All relational mappings should implement this method to return true.
     */
    public boolean isXMLMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAbstractDirectMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAbstractCompositeDirectCollectionMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAbstractCompositeObjectMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isAbstractCompositeCollectionMapping() {
        return false;
    }
    /**
     * INTERNAL:
     * Return if this mapping support joining.
     */
    public boolean isJoiningSupported() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return if this mapping requires its attribute value to be cloned.
     */
    public boolean isCloningRequired() {
        return true;
    }

    /**
     * INTERNAL:
     * Set by the Object builder during initialization returns true if this mapping
     * is used as a primary key mapping.
     */
    public boolean isPrimaryKeyMapping() {
        return this.primaryKeyMapping;
    }
    /**
     * INTERNAL:
     * Used when determining if a mapping supports cascaded version optimistic
     * locking.
     */
    public boolean isCascadedLockingSupported() {
        return false;
    }
    
    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return false;
    }
    /**
     * INTERNAL:
     * Return if the mapping has ownership over its target object(s).
     */
    public boolean isPrivateOwned() {
        return false;
    }

    /**
     * INTERNAL:
     * Returns true if mapping is read only else false.
     */
    public boolean isReadOnly() {
        return isReadOnly;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isReferenceMapping() {
        return false;
    }

    protected boolean isRemotelyInitialized() {
        return isRemotelyInitialized;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isSerializedObjectMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isStructureMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isTransformationMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isTypeConversionMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isVariableOneToOneMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isDirectToXMLTypeMapping() {
        return false;
    }

    /**
     * INTERNAL:
     * Some mappings support no attribute (transformation).
     */
    public boolean isWriteOnly() {
        return false;
    }

    /**
     * INTERNAL:
     * Iterate on the appropriate attribute value.
     */
    public abstract void iterate(DescriptorIterator iterator);

    /**
     * INTERNAL:
     * Iterate on the attribute value.
     * The value holder has already been processed.
     */
    public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) {
        throw DescriptorException.invalidMappingOperation(this, "iterateOnRealAttributeValue");
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     */
    public abstract void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager);

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     */
    public abstract void mergeIntoObject(Object target, boolean isTargetUninitialized, Object source, MergeManager mergeManager);

    /**
     * INTERNAL:
     * Perform the commit event.
     * This is used in the uow to delay data modifications.
     */
    public void performDataModificationEvent(Object[] event, AbstractSession session) throws DatabaseException, DescriptorException {
        throw DescriptorException.invalidDataModificationEvent(this);
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Recurse thru the parts to delete the reference objects after the actual object is deleted.
     */
    public void postDelete(WriteObjectQuery query) throws DatabaseException {
        return;
    }

    /**
     * INTERNAL:
     * Allow for initialization of properties and validation that have dependecies no the descriptor
     * being initialized.
     */
    public void postInitialize(AbstractSession session) throws DescriptorException {
        // Nothing by default.
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Recurse thru the parts to insert the reference objects after the actual object is inserted.
     */
    public void postInsert(WriteObjectQuery query) throws DatabaseException {
        return;
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Recurse thru the parts to update the reference objects after the actual object is updated.
     */
    public void postUpdate(WriteObjectQuery query) throws DatabaseException {
        return;
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Recurse thru the parts to delete the reference objects before the actual object is deleted.
     */
    public void preDelete(WriteObjectQuery query) throws DatabaseException {
        return;
    }

    /**
     * INTERNAL:
     * Allow for initialization of properties and validation.
     */
    public void preInitialize(AbstractSession session) throws DescriptorException {
        try {
            getAttributeAccessor().initializeAttributes(getDescriptor().getJavaClass());
        } catch (DescriptorException exception) {
            exception.setMapping(this);
            session.getIntegrityChecker().handleError(exception);
        }
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Recurse thru the parts to insert the reference objects before the actual object is inserted.
     */
    public void preInsert(WriteObjectQuery query) throws DatabaseException {
        return;
    }
    
    /**
     * INTERNAL:
     * A subclass that supports cascade version optimistic locking should
     * implement this method to properly prepare the locking policy for their
     * mapping type.
     */
    public void prepareCascadeLockingPolicy() {
        return;
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Recurse thru the parts to update the reference objects before the actual object is updated.
     */
    public void preUpdate(WriteObjectQuery query) throws DatabaseException {
        return;
    }

    /**
     * INTERNAL:
     * Extract value from the row and set the attribute to this value in the object.
     * return value as this value will have been converted to the appropriate type for
     * the object.
     */
    public Object readFromRowIntoObject(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object targetObject, ObjectBuildingQuery sourceQuery) throws DatabaseException {
        // This version can be called directly for reading a sequence number
        // field, a write lock value, or a return row into an object, and hence
        // the query is just a placeholder. Getting the correct execution
        // session will generate an exception, so just pass in any session.
        // In general call this version only if no field conversion needed.
        return readFromRowIntoObject(databaseRow, joinManager, targetObject, sourceQuery, sourceQuery.getSession());
    }

    /**
     * INTERNAL:
     * Extract value from the row and set the attribute to this value in the object.
     * return value as this value will have been converted to the appropriate type for
     * the object.
     */
    public Object readFromRowIntoObject(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object targetObject, ObjectBuildingQuery sourceQuery, AbstractSession executionSession) throws DatabaseException {
        Object attributeValue = valueFromRow(databaseRow, joinManager, sourceQuery, executionSession);
        setAttributeValueInObject(targetObject, attributeValue);
        return attributeValue;
    }

    /**
     * PUBLIC:
     * To make mapping read only.
     * Read-only mappings can be used if two attributes map to the same field.
     * Read-only mappings cannot be used for the primary key or other required fields.
     */
    public void readOnly() {
        setIsReadOnly(true);
    }

    /**
     * PUBLIC:
     * The mapping can be dynamically made either readOnly or readWriteOnly. This makes mapping go back to
     * default mode.
     */
    public void readWrite() {
        setIsReadOnly(false);
    }

    /**
     * INTERNAL:
     * Rehash any hashtables based on fields.
     * This is used to clone descriptors for aggregates, which hammer field names,
     * it is probably better not to hammer the field name and this should be refactored.
     */
    public void rehashFieldDependancies(AbstractSession session) {
        // Should be overwritten by any mapping with fields.
    }

    /**
     * ADVANCED:
     * Set the attributeAccessor.
     * The attribute accessor is responsible for setting and retrieving the attribute value
     * from the object for this mapping.
     * This can be set to an implementor of AttributeAccessor if the attribute
     * requires advanced conversion of the mapping value, or a real attribute does not exist.
     */
    public void setAttributeAccessor(AttributeAccessor attributeAccessor) {
        String attributeName = getAttributeName();
        this.attributeAccessor = attributeAccessor;
        if (attributeAccessor.getAttributeName() == null) {
            attributeAccessor.setAttributeName(attributeName);
        }
    }

    /**
     * PUBLIC:
     * Sets the name of the attribute in the mapping.
     */
    public void setAttributeName(String attributeName) {
        getAttributeAccessor().setAttributeName(attributeName);
    }

    /**
     * INTERNAL:
     * Set the value of the attribute mapped by this mapping.
     */
    public void setAttributeValueInObject(Object object, Object value) throws DescriptorException {
        // PERF: Direct variable access.
        try {
            this.attributeAccessor.setAttributeValueInObject(object, value);
        } catch (DescriptorException exception) {
            exception.setMapping(this);
            throw exception;
        }
    }

    /**
     * INTERNAL:
     * Set the value of the attribute mapped by this mapping,
     * placing it inside a value holder if necessary.
     */
    public void setRealAttributeValueInObject(Object object, Object value) throws DescriptorException {
        try {
            this.setAttributeValueInObject(object, value);
        } catch (DescriptorException exception) {
            exception.setMapping(this);
            throw exception;
        }
    }

    /**
     * INTERNAL:
     * Set the descriptor to which this mapping belongs
     */
    public void setDescriptor(ClassDescriptor descriptor) {
        this.descriptor = descriptor;
    }

    /**
     * INTERNAL:
     * Set the mapping's field collection.
     */
    protected void setFields(Vector<DatabaseField> fields) {
        this.fields = fields;
    }

    /**
     * PUBLIC:
     * This method is invoked reflectively on the reference object to return the value of the
     * attribute in the object. This method sets the name of the getMethodName.
     */
    public void setGetMethodName(String methodName) {
        if (methodName == null) {
            return;
        }

        // This is done because setting attribute name by defaults create InstanceVariableAttributeAccessor
        if (getAttributeAccessor() instanceof InstanceVariableAttributeAccessor) {
            String attributeName = this.attributeAccessor.getAttributeName();
            setAttributeAccessor(new MethodAttributeAccessor());
            getAttributeAccessor().setAttributeName(attributeName);
        }

        ((MethodAttributeAccessor)getAttributeAccessor()).setGetMethodName(methodName);
    }

    /**
     * INTERNAL:
     * Used to specify whether the value of this mapping may be null. It is
     * used when generating DDL.
     */
    public void setIsOptional(boolean isOptional) {
        this.isOptional = isOptional;
    }
    
    /**
     * INTERNAL:
     * Set by the Object builder during initialization returns true if this mapping
     * is used as a primary key mapping.
     */
    public void setIsPrimaryKeyMapping(boolean pkMapping) {
        this.primaryKeyMapping = pkMapping;
    }

    /**
     * PUBLIC:
     * Set this mapping to be read only.
     * Read-only mappings can be used if two attributes map to the same field.
     * Read-only mappings cannot be used for the primary key or other required fields.
     */
    public void setIsReadOnly(boolean aBoolean) {
        isReadOnly = aBoolean;
    }

    /**
     * INTERNAL:
     * used as a temporary store for custom SDK usage
     */
    public void setProperties(Map properties) {
        this.properties = properties;
    }

    /**
     * ADVANCED:
     * Allow user defined properties.
     */
    public void setProperty(Object property, Object value) {
        getProperties().put(property, value);
    }

    /**
     * PUBLIC:
     * This method is invoked reflectively on the reference object to get the value of the attribute.
     * The method defined on the object should actually return the value that needs to be set in the
     * attribute accessor.
     */
    public void setSetMethodName(String methodName) {
        if (methodName == null) {
            return;
        }

        // This is done because setting attribute name by defaults create InstanceVariableAttributeAccessor
        if (!(getAttributeAccessor() instanceof MethodAttributeAccessor)) {
            String attributeName = this.attributeAccessor.getAttributeName();
            setAttributeAccessor(new MethodAttributeAccessor());
            getAttributeAccessor().setAttributeName(attributeName);
        }

        ((MethodAttributeAccessor)getAttributeAccessor()).setSetMethodName(methodName);
    }

    /**
     * ADVANCED:
     * Set the weight of the mapping, used to sort mappings
     * DirectToField Mappings have a default weight of 1 while all other Mappings have a
     * default weight of MAXINT. Ordering of Mappings can be achieved by setting the weight of
     * a particular mapping to a value within the above mentioned limits. By ordering mappings
     * the user can control what order relationships are processed by TopLink.
     */

    // CR 4097
    public void setWeight(Integer newWeight) {
        this.weight = newWeight;
    }

    /**
     * ADVANCED:
     * This method is used to add an object to a collection once the changeSet is applied.
     * The referenceKey parameter should only be used for direct Maps.
     */
    public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "simpleAddToCollectionChangeRecord");
    }

    /**
     * ADVANCED:
     * This method is used to remove an object from a collection once the changeSet is applied.
     * The referenceKey parameter should only be used for direct Maps.
     */
    public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "simpleRemoveFromCollectionChangeRecord");
    }

    /**
     * INTERNAL:
     * Print the mapping attribute name, this is used in error messages.
     */
    public String toString() {
        return getClass().getName() + "[" + getAttributeName() + "]";
    }

    /**
     * INTERNAL:
     * Allow for subclasses to perform validation.
     */
    public void validateAfterInitialization(AbstractSession session) throws DescriptorException {
    }

    /**
     * INTERNAL:
     * Allow for subclasses to perform validation.
     */
    public void validateBeforeInitialization(AbstractSession session) throws DescriptorException {
    }

    /**
     * INTERNAL:
     * A subclass should extract the value from the object for the field, if it does not map the field then
     * it should return null.
     * Return the Value from the object.
     */
    public Object valueFromObject(Object anObject, DatabaseField field, AbstractSession session) {
        return null;
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Returns the value for the mapping from the database row.
     */
    public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query) throws DatabaseException {
        return valueFromRow(row, joinManager, query, query.getSession().getExecutionSession(query));
    }

    /**
     * INTERNAL:
     *
     */
    public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, AbstractSession session) throws DatabaseException {
        return null;
    }

    /**
     * INTERNAL:
     * To verify if the specified object has been deleted or not.
     */
    public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException {
        return true;
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Write the foreign key values from the attribute to the row.
     */
     
    public void writeFromAttributeIntoRow(Object attribute, AbstractRecord row, AbstractSession session)
    {
        // Do nothing by default.
    }
    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Write the attribute value from the object to the row.
     */
    public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session) {
        // Do nothing by default.
    }

    /**
     * INTERNAL:
     * This row is built for shallow insert which happens in case of of circular dependencies bidirectional inserts.
     */
    public void writeFromObjectIntoRowForShallowInsert(Object object, AbstractRecord row, AbstractSession session) {
        writeFromObjectIntoRow(object, row, session);
    }

    /**
     * INTERNAL:
     * This row is built for shallow delete which happens in case of circular dependencies for bidirectional deletes.
     */
    public void writeFromObjectIntoRowForShallowDelete(Object object, AbstractRecord row, AbstractSession session) {
        // Do nothing by default.
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Write the attribute value from the object to the row.
     */
    public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session) {
        // Do nothing by default.
    }

    /**
     * INTERNAL:
     * This row is built for shallow insert which happens in case of bidirectional inserts.
     */
    public void writeFromObjectIntoRowForShallowInsertWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session) {
        writeFromObjectIntoRowWithChangeRecord(changeRecord, row, session);
    }

    /**
     * INTERNAL:
     * Write the attribute value from the object to the row for update.
     */
    public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord row) {
        writeFromObjectIntoRow(query.getObject(), row, query.getSession());
    }

    /**
     * INTERNAL:
     * A subclass should implement this method if it wants different behaviour.
     * Write the attribute value from the object to the row.
     */
    public void writeFromObjectIntoRowForWhereClause(ObjectLevelModifyQuery query, AbstractRecord row) {
        Object object;
        if (query.isDeleteObjectQuery()) {
            object = query.getObject();
        } else {
            object = query.getBackupClone();
        }
        writeFromObjectIntoRow(object, row, query.getSession());
    }

    /**
     * INTERNAL:
     * Write fields needed for insert into the template for with null values.
     */
    public void writeInsertFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) {
        // Do nothing by default.
    }

    /**
     * INTERNAL:
     * Write fields needed for update into the template for with null values.
     * By default inserted fields are used.
     */
    public void writeUpdateFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) {
        writeInsertFieldsIntoRow(databaseRow, session);
    }

    /**
     * INTERNAL:
     * Either create a new change record or update the change record with the new value.
     * This is used by attribute change tracking.
     */
    public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "updateChangeRecord");
    }

    /**
     * INTERNAL:
     * Add a new value and its change set to the collection change record. This is used by
     * attribute change tracking.
     */
    public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "addToCollectionChangeRecord");
    }

    /**
     * INTERNAL:
     * Remove a value and its change set from the collection change record. This is used by
     * attribute change tracking.
     */
    public void removeFromCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "removeFromCollectionChangeRecord");
    }

    /**
     * INTERNAL:
     * Directly build a change record without comparison
     */
    public ChangeRecord buildChangeRecord(Object newValue, ObjectChangeSet owner, AbstractSession session) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "buildChangeRecord");
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.descriptors.changetracking;

import java.util.*;
import oracle.toplink.essentials.internal.sessions.ObjectChangeSet;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.sessions.MergeManager;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.descriptors.DescriptorEvent;
import oracle.toplink.essentials.descriptors.DescriptorEventManager;
import oracle.toplink.essentials.internal.helper.IdentityHashtable;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;

/**
 * PUBLIC:
 * A DeferredChangeDetectionPolicy defers all change detection to the UnitOfWork's
 * change detection process. Essentially, the calculateChanges() method will run
 * for all objects in a UnitOfWork. This is the default ObjectChangePolicy.
 *
 * @author Tom Ware
 */
public class DeferredChangeDetectionPolicy implements ObjectChangePolicy, java.io.Serializable {

    /**
     * INTERNAL:
     * calculateChanges creates a change set for a particular object. In DeferredChangeDetectionPolicy
     * all mappings will be compared against a backup copy of the object.
     * @return oracle.toplink.essentials.changesets.ObjectChangeSet an object change set describing
     * the changes to this object
     * @param java.lang.Object clone the Object to compute a change set for
     * @param java.lang.Object backUp the old version of the object to use for comparison
     * @param oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet the change set to add changes to
     * @param Session the current session
     * @param Descriptor the descriptor for this object
     * @param shouldRiseEvent indicates whether PreUpdate event should be risen (usually true)
     */
    public ObjectChangeSet calculateChanges(Object clone, Object backUp, oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet changeSet, AbstractSession session, ClassDescriptor descriptor, boolean shouldRiseEvent) {
        boolean isNew = ((backUp == null) || ((((UnitOfWorkImpl)session).isObjectNew(clone)) && (!descriptor.isAggregateDescriptor())));
 
        // PERF: Avoid events if no listeners.
        if (descriptor.getEventManager().hasAnyEventListeners() && shouldRiseEvent) {
            // The query is built for compatability to old event mechanism.
            WriteObjectQuery writeQuery = new WriteObjectQuery(clone.getClass());
            writeQuery.setObject(clone);
            writeQuery.setBackupClone(backUp);
            writeQuery.setSession(session);
            writeQuery.setDescriptor(descriptor);

            descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreWriteEvent, writeQuery));

            if (isNew) {
                descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreInsertEvent, writeQuery));
            } else {
                descriptor.getEventManager().executeEvent(new DescriptorEvent(DescriptorEventManager.PreUpdateEvent, writeQuery));
            }
        }

        ObjectChangeSet changes = createObjectChangeSet(clone, backUp, changeSet, isNew, session, descriptor);

        changes.setShouldModifyVersionField((Boolean)((UnitOfWorkImpl)session).getOptimisticReadLockObjects().get(clone));

        if (changes.hasChanges() || changes.hasForcedChanges()) {
            return changes;
        }
        return null;
    }

    /**
     * INTERNAL:
     * This is a place holder for reseting the listener on one of the subclasses
     */
    public void clearChanges(Object object, UnitOfWorkImpl uow, ClassDescriptor descriptor) {
    }

    /**
     * INTERNAL:
     * Create ObjectChangeSet
     */
    public ObjectChangeSet createObjectChangeSet(Object clone, Object backUp, oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet changeSet, boolean isNew, AbstractSession session, ClassDescriptor descriptor) {
        return this.createObjectChangeSetThroughComparison(clone, backUp, changeSet, isNew, session, descriptor);
    }

    /**
     * INTERNAL:
     * Create ObjectChangeSet
     */
    public ObjectChangeSet createObjectChangeSetThroughComparison(Object clone, Object backUp, oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet changeSet, boolean isNew, AbstractSession session, ClassDescriptor descriptor) {
        ObjectBuilder builder = descriptor.getObjectBuilder();
        ObjectChangeSet changes = builder.createObjectChangeSet(clone, changeSet, isNew, session);

        // The following code deals with reads that force changes to the flag associated with optimistic locking.
        if ((descriptor.usesOptimisticLocking()) && (changes.getPrimaryKeys() != null)) {
            changes.setOptimisticLockingPolicyAndInitialWriteLockValue(descriptor.getOptimisticLockingPolicy(), session);
        }

        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = descriptor.getMappings();
        int mappingsSize = mappings.size();
        for (int index = 0; index < mappingsSize; index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            changes.addChange(mapping.compareForChange(clone, backUp, changes, session));
        }

        return changes;
    }

    /**
     * INTERNAL:
     * This method is used to dissable changetracking temporarily
     */
    public void dissableEventProcessing(Object changeTracker){
        //no-op
    }

    /**
     * INTERNAL:
     * This method is used to enable changetracking temporarily
     */
    public void enableEventProcessing(Object changeTracker){
        //no-op
    }
    
    /**
     * INTERNAL:
     * Return true if the Object should be compared, false otherwise. In DeferredChangeDetectionPolicy,
     * true is always returned since always allow the UnitOfWork to calculate changes.
     * @param java.lang.Object object - the object that will be compared
     * @param oracle.toplink.essentials.publicinterface.UnitOfWork unitOfWork - the active unitOfWork
     * @param oracle.toplink.essentials.publicinterface.Descriptor descriptor - the descriptor for the current object
     */
    public boolean shouldCompareForChange(Object object, UnitOfWorkImpl unitOfWork, ClassDescriptor descriptor) {
        return true;
    }

    /**
     * INTERNAL:
     * Build back up clone. Used if clone is new because listener should not be set.
     */
    public Object buildBackupClone(Object clone, ObjectBuilder builder, UnitOfWorkImpl uow) {
        return builder.buildBackupClone(clone, uow);
    }

    /**
     * INTERNAL:
     * Assign Changelistner to an aggregate object
     */
    public void setAggregateChangeListener(Object parent, Object aggregate, UnitOfWorkImpl uow, ClassDescriptor descriptor, String mappingAttribute){
        //no-op
    }
    
    /**
     * INTERNAL:
     * Set ChangeListener for the clone
     */
    public void setChangeListener(Object clone, UnitOfWorkImpl uow, ClassDescriptor descriptor) {
        //no-op
    }

    /**
     * INTERNAL:
     * Set the ObjectChangeSet on the Listener, initially used for aggregate support
     */
    public void setChangeSetOnListener(ObjectChangeSet objectChangeSet, Object clone){
        //no-op
    }
    
    /**
     * INTERNAL:
     * Clear changes in the ChangeListener of the clone
     */
    public void updateWithChanges(Object clone, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow, ClassDescriptor descriptor) {
        if (objectChangeSet == null) {
            return;
        }
        Object backupClone = uow.getCloneMapping().get(clone);
        if (backupClone != null) {
            MergeManager mergeManager = new MergeManager(uow);
            mergeManager.setCascadePolicy(MergeManager.NO_CASCADE);
            descriptor.getObjectBuilder().mergeChangesIntoObject(backupClone, objectChangeSet, clone, mergeManager);
        }
        clearChanges(clone, uow, descriptor);
    }

    /**
     * INTERNAL:
     * This may cause a property change event to be raised to a listner in the case that a listener exists.
     * If there is no listener then this call is a no-op
     */
    public void raiseInternalPropertyChangeEvent(Object source, String propertyName, Object oldValue, Object newValue){
        //no-op
    }

    /**
     * INTERNAL:
     * This method is used to revert an object within the unit of work
     * @param cloneMapping may not be the same as whats in the uow
     */
    public void revertChanges(Object clone, ClassDescriptor descriptor, UnitOfWorkImpl uow, IdentityHashtable cloneMapping) {
        cloneMapping.put(clone, buildBackupClone(clone, descriptor.getObjectBuilder(), uow));
        clearChanges(clone, uow, descriptor);
    }

    /**
     * INTERNAL:
     * initialize the Policy
     */
    public void initialize(AbstractSession session, ClassDescriptor descriptor) {
        //do nothing
    }

    /**
     * Used to track instances of the change policies without doing an instance of check
     */
    public boolean isDeferredChangeDetectionPolicy(){
        return true;
    }

    /**
     * Used to track instances of the change policies without doing an instance of check
     */
    public boolean isObjectChangeTrackingPolicy(){
        return false;
    }

    /**
     * Used to track instances of the change policies without doing an instance of check
     */
    public boolean isAttributeChangeTrackingPolicy(){
        return false;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.descriptors;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.indirection.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * This class provides a generic way of using the descriptor information
 * to traverse an object graph.
 * Define a subclass, or an inner class, that implements at least
 * #iterate(Object) to implement a new traversal
 * feature without having to change the mapping classes or the object builder.
 * It provides functionality such as a cascading depth, a stack of visited object,
 * and a collection of the visited objects.
 *
 * NOTE:
 * If this works nicely the merge manager, remote traversals, and maybe
 * even aspects of the commit manager could be converted to use this class.
 */
public abstract class DescriptorIterator {
    public static final int NoCascading = 1;
    public static final int CascadePrivateParts = 2;
    public static final int CascadeAllParts = 3;
    protected IdentityHashtable visitedObjects;
    protected Stack visitedStack;
    protected AbstractSession session;
    protected DatabaseMapping currentMapping;
    protected ClassDescriptor currentDescriptor;
    protected Object result;// this is a work area, typically used as a Collecting Parm
    protected boolean shouldIterateOverIndirectionObjects;
    protected boolean shouldIterateOverUninstantiatedIndirectionObjects;
    protected boolean shouldIterateOverWrappedObjects;
    protected boolean shouldIterateOnIndirectionObjects;
    protected boolean shouldIterateOnAggregates;
    protected boolean shouldIterateOnPrimitives;
    protected boolean shouldBreak;
    protected int cascadeDepth;// see static constants below

    /**
     * Construct a typical iterator:
     * iterate over all the objects
     * process the objects contained by "value holders"...
     * ...but only if they have already been instantiated...
     * ...and don't process the "value holders" themselves
     * process "wrapped" objects
     * skip aggregate objects
     * skip primitives (Strings, Dates, Integers, etc.)
     */
    public DescriptorIterator() {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        this.visitedObjects = new HardIdentityHashtable();
        this.visitedStack = new Stack();
        this.cascadeDepth = CascadeAllParts;
        this.shouldIterateOverIndirectionObjects = true;// process the objects contained by ValueHolders...
        this.shouldIterateOverUninstantiatedIndirectionObjects = false;// ...but only if they have already been instantiated...
        this.shouldIterateOnIndirectionObjects = false;// ...and don't process the ValueHolders themselves
        this.shouldIterateOverWrappedObjects = true;// process "wrapped" objects
        this.shouldIterateOnAggregates = false;
        this.shouldIterateOnPrimitives = false;
        this.shouldBreak = false;
    }

    public int getCascadeDepth() {
        return cascadeDepth;
    }

    public ClassDescriptor getCurrentDescriptor() {
        return currentDescriptor;
    }

    public DatabaseMapping getCurrentMapping() {
        return currentMapping;
    }

    /**
     * Fetch and return the descriptor for the specified object.
     */
    protected ClassDescriptor getDescriptorFor(Object object) {
        ClassDescriptor result = getSession().getDescriptor(object);
        if (result == null) {
            throw DescriptorException.missingDescriptor(object.getClass().getName());
        }
        return result;
    }

    public Object getResult() {
        return result;
    }

    public AbstractSession getSession() {
        return session;
    }

    /**
     * Return the second-to-last object visited.
     */
    public Object getVisitedGrandparent() {
        Object parent = getVisitedStack().pop();
        Object result = getVisitedStack().peek();
        getVisitedStack().push(parent);
        return result;
    }

    public IdentityHashtable getVisitedObjects() {
        return visitedObjects;
    }

    /**
     * Return the last object visited.
     */
    public Object getVisitedParent() {
        return getVisitedStack().peek();
    }

    public Stack getVisitedStack() {
        return visitedStack;
    }

    /**
     * Iterate an aggregate object
     * (i.e. an object that is the target of an AggregateMapping).
     * Override this method if appropriate.
     */
    protected void internalIterateAggregateObject(Object aggregateObject) {
        iterate(aggregateObject);
    }

    /**
     * Iterate an indirect container (IndirectList or IndirectMap).
     * Override this method if appropriate.
     */
    protected void internalIterateIndirectContainer(IndirectContainer container) {
        iterate(container);
    }

    /**
     * Iterate a primitive object (String, Date, Integer, etc.).
     * Override this method if appropriate.
     */
    protected void internalIteratePrimitive(Object primitiveValue) {
        iterate(primitiveValue);
    }

    /**
     * Iterate a (a non-Aggregate) reference object.
     * Override this method if appropriate.
     */
    protected void internalIterateReferenceObject(Object referenceObject) {
        iterate(referenceObject);
    }

    /**
     * Iterate a value holder.
     * Override this method if appropriate.
     */
    protected void internalIterateValueHolder(ValueHolderInterface valueHolder) {
        iterate(valueHolder);
    }

    /**
     * To define a new iterator create a subclass and define at least this method.
     * Given an object or set of the objects, this method will be called on those
     * objects and any object connected to them by using the descriptors to
     * traverse the object graph.
     * Override the assorted #internalIterate*() methods if appropriate.
     */
    protected abstract void iterate(Object object);

    /**
     * Iterate on the mapping's reference object and
     * recursively iterate on the reference object's
     * reference objects.
     * This is used for aggregate and aggregate collection mappings, which are not iterated on by default.
     */
    public void iterateForAggregateMapping(Object aggregateObject, DatabaseMapping mapping, ClassDescriptor descriptor) {
        if (aggregateObject == null) {
            return;
        }
        setCurrentMapping(mapping);
        // aggregate descriptors are passed in because they could be part of an inheritance tree
        setCurrentDescriptor(descriptor);

        if (shouldIterateOnAggregates()) {// false by default
            internalIterateAggregateObject(aggregateObject);
            if (shouldBreak()) {
                setShouldBreak(false);
                return;
            }
        }

        iterateReferenceObjects(aggregateObject);
    }

    /**
     * Iterate on the indirection object for its mapping.
     */
    public void iterateIndirectContainerForMapping(IndirectContainer container, DatabaseMapping mapping) {
        setCurrentMapping(mapping);
        setCurrentDescriptor(null);

        if (shouldIterateOnIndirectionObjects()) {// false by default
            internalIterateIndirectContainer(container);
        }

        if (shouldIterateOverUninstantiatedIndirectionObjects() || (shouldIterateOverIndirectionObjects() && container.isInstantiated())) {
            // force instantiation only if specified
            mapping.iterateOnRealAttributeValue(this, container);
        }
    }

    /**
     * Iterate on the primitive value for its mapping.
     */
    public void iteratePrimitiveForMapping(Object primitiveValue, DatabaseMapping mapping) {
        if (primitiveValue == null) {
            return;
        }
        setCurrentMapping(mapping);
        setCurrentDescriptor(null);

        if (shouldIterateOnPrimitives()) {// false by default
            internalIteratePrimitive(primitiveValue);
        }
    }

    /**
     * Iterate on the mapping's reference object and
     * recursively iterate on the reference object's
     * reference objects.
     */
    public void iterateReferenceObjectForMapping(Object referenceObject, DatabaseMapping mapping) {
        if (!(shouldCascadeAllParts() || (shouldCascadePrivateParts() && mapping.isPrivateOwned()))) {
            return;
        }

        // When using wrapper policy in EJB the iteration can stop in certain cases,
        // this is because EJB forces beans to be registered anyway and clone identity can be violated
        // and the violated clones references to session objects should not be traversed.
        ClassDescriptor rd = mapping.getReferenceDescriptor();
        if ((!shouldIterateOverWrappedObjects()) && (rd != null) && (rd.hasWrapperPolicy())) {
            return;
        }
        if (referenceObject == null) {
            return;
        }

        // Check if already processed.
        if (getVisitedObjects().containsKey(referenceObject)) {
            return;
        }

        getVisitedObjects().put(referenceObject, referenceObject);
        setCurrentMapping(mapping);
        setCurrentDescriptor(getDescriptorFor(referenceObject));

        internalIterateReferenceObject(referenceObject);
        if (shouldBreak()) {
            setShouldBreak(false);
            return;
        }

        iterateReferenceObjects(referenceObject);
    }

    /**
     * Iterate over the sourceObject's reference objects,
     * updating the visited stack appropriately.
     */
    protected void iterateReferenceObjects(Object sourceObject) {
        getVisitedStack().push(sourceObject);
        getCurrentDescriptor().getObjectBuilder().iterate(this);
        getVisitedStack().pop();
    }

    /**
     * Iterate on the value holder for its mapping.
     */
    public void iterateValueHolderForMapping(ValueHolderInterface valueHolder, DatabaseMapping mapping) {
        setCurrentMapping(mapping);
        setCurrentDescriptor(null);

        if (shouldIterateOnIndirectionObjects()) {// false by default
            internalIterateValueHolder(valueHolder);
        }

        if (shouldIterateOverUninstantiatedIndirectionObjects() || (shouldIterateOverIndirectionObjects() && valueHolder.isInstantiated())) {
            // force instantiation only if specified
            mapping.iterateOnRealAttributeValue(this, valueHolder.getValue());
        }
    }

    public void setCascadeDepth(int cascadeDepth) {
        this.cascadeDepth = cascadeDepth;
    }

    public void setCurrentDescriptor(ClassDescriptor currentDescriptor) {
        this.currentDescriptor = currentDescriptor;
    }

    public void setCurrentMapping(DatabaseMapping currentMapping) {
        this.currentMapping = currentMapping;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    public void setSession(AbstractSession session) {
        this.session = session;
    }

    public void setShouldBreak(boolean shouldBreak) {
        this.shouldBreak = shouldBreak;
    }

    /**
     * Set whether the aggregate reference objects themselves
     * should be processed. (The objects referenced by the aggregate
     * objects will be processed either way.)
     */
    public void setShouldIterateOnAggregates(boolean shouldIterateOnAggregates) {
        this.shouldIterateOnAggregates = shouldIterateOnAggregates;
    }

    /**
     * Set whether the indirection objects themselves (e.g. the ValueHolders)
     * should be processed.
     */
    public void setShouldIterateOnIndirectionObjects(boolean shouldIterateOnIndirectionObjects) {
        this.shouldIterateOnIndirectionObjects = shouldIterateOnIndirectionObjects;
    }

    /**
     * Set whether to process primitive reference objects
     * (e.g. Strings, Dates, ints).
     */
    public void setShouldIterateOnPrimitives(boolean shouldIterateOnPrimitives) {
        this.shouldIterateOnPrimitives = shouldIterateOnPrimitives;
    }

    /**
     * Set whether to process the objects contained by indirection objects
     * (e.g. a ValueHolder's value) - but *without* instantiating them.
     * @see #setShouldIterateOverUninstantiatedIndirectionObjects()
     */
    public void setShouldIterateOverIndirectionObjects(boolean shouldIterateOverIndirectionObjects) {
        this.shouldIterateOverIndirectionObjects = shouldIterateOverIndirectionObjects;
    }

    /**
     * Set whether to *instantiate* and process the objects
     * contained by indirection objects (e.g. a ValueHolder's value).
     */
    public void setShouldIterateOverUninstantiatedIndirectionObjects(boolean shouldIterateOverUninstantiatedIndirectionObjects) {
        this.shouldIterateOverUninstantiatedIndirectionObjects = shouldIterateOverUninstantiatedIndirectionObjects;
    }

    public void setShouldIterateOverWrappedObjects(boolean shouldIterateOverWrappedObjects) {
        this.shouldIterateOverWrappedObjects = shouldIterateOverWrappedObjects;
    }

    public void setVisitedObjects(IdentityHashtable visitedObjects) {
        this.visitedObjects = visitedObjects;
    }

    protected void setVisitedStack(Stack visitedStack) {
        this.visitedStack = visitedStack;
    }

    public boolean shouldBreak() {
        return shouldBreak;
    }

    public boolean shouldCascadeAllParts() {
        return getCascadeDepth() == CascadeAllParts;
    }

    public boolean shouldCascadeNoParts() {
        return (getCascadeDepth() == NoCascading);
    }

    public boolean shouldCascadePrivateParts() {
        return (getCascadeDepth() == CascadeAllParts) || (getCascadeDepth() == CascadePrivateParts);
    }

    /**
     * Return whether the aggregate reference objects themselves
     * should be processed. (The objects referenced by the aggregate
     * objects will be processed either way.)
     */
    public boolean shouldIterateOnAggregates() {
        return shouldIterateOnAggregates;
    }

    /**
     * Return whether the indirection objects themselves (e.g. the ValueHolders)
     * should be processed.
     */
    public boolean shouldIterateOnIndirectionObjects() {
        return shouldIterateOnIndirectionObjects;
    }

    /**
     * Return whether to process primitive reference objects
     * (e.g. Strings, Dates, ints).
     */
    public boolean shouldIterateOnPrimitives() {
        return shouldIterateOnPrimitives;
    }

    /**
     * Return whether to process the objects contained by indirection objects
     * (e.g. a ValueHolder's value) - but *without* instantiating them.
     * @see #shouldIterateOverUninstantiatedIndirectionObjects()
     */
    public boolean shouldIterateOverIndirectionObjects() {
        return shouldIterateOverIndirectionObjects;
    }

    /**
     * Return whether to *instantiate* and process the objects
     * contained by indirection objects (e.g. a ValueHolder's value).
     */
    public boolean shouldIterateOverUninstantiatedIndirectionObjects() {
        return shouldIterateOverUninstantiatedIndirectionObjects;
    }

    public boolean shouldIterateOverWrappedObjects() {
        return shouldIterateOverWrappedObjects;
    }

    /**
     * This is the root method called to start the iteration.
     */
    public void startIterationOn(Object sourceObject) {
        if (getVisitedObjects().containsKey(sourceObject)) {
            return;
        }
        getVisitedObjects().put(sourceObject, sourceObject);
        setCurrentMapping(null);
        setCurrentDescriptor(getSession().getDescriptor(sourceObject));

        iterate(sourceObject);

        // start the recursion
        if ((getCurrentDescriptor() != null) && (!shouldCascadeNoParts()) && !this.shouldBreak()) {
            iterateReferenceObjects(sourceObject);
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.databaseaccess.Platform;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.mappings.converters.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.sessions.DatabaseRecord;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: This mapping is used to store a collection of simple types (String, Number, Date, etc.)
 * into a single table. The table must store the value and a foreign key to the source object.
 * A converter can be used if the desired object type and the data type do not match.
 *
 * @see Converter
 * @see ObjectTypeConverter
 * @see TypeConversionConverter
 * @see SerializedObjectConverter
 *
 * @author Sati
 * @since TOPLink/Java 1.0
 */
public class DirectCollectionMapping extends CollectionMapping implements RelationalMapping {

    /** Used for data modification events. */
    protected static final String Delete = "delete";
    protected static final String Insert = "insert";
    protected static final String DeleteAll = "deleteAll";

    /** Allows user defined conversion between the object value and the database value. */
    protected Converter valueConverter;

    /** Stores the reference table*/
    protected transient DatabaseTable referenceTable;

    /** The direct field name is converted and stored */
    protected transient DatabaseField directField;
    protected transient Vector<DatabaseField> sourceKeyFields;
    protected transient Vector<DatabaseField> referenceKeyFields;

    /** Used for insertion for m-m and dc, not used in 1-m. */
    protected transient DataModifyQuery insertQuery;
    /** Used for deletion when ChangeSets are used */
    protected transient ModifyQuery changeSetDeleteQuery;
    
    protected transient boolean hasCustomDeleteQuery;
    protected transient boolean hasCustomInsertQuery;

    /**
     * PUBLIC:
     * Default constructor.
     */
    public DirectCollectionMapping() {
        this.insertQuery = new DataModifyQuery();
        this.sourceKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.referenceKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.selectionQuery = new DirectReadQuery();
        this.hasCustomInsertQuery = false;
        this.isPrivateOwned = true;
    }

    public boolean isRelationalMapping() {
        return true;
    }

    /**
     * PUBLIC:
     * Return the converter on the mapping.
     * A converter can be used to convert between the direct collection's object value and database value.
     */
    public Converter getValueConverter() {
        return valueConverter;
    }

    /**
     * PUBLIC:
     * Set the converter on the mapping.
     * A converter can be used to convert between the direct collection's object value and database value.
     */
    public void setValueConverter(Converter valueConverter) {
        this.valueConverter = valueConverter;
    }

    /**
     * PUBLIC:
     * Add the reference key field.
     * This is used for composite reference keys.
     * This is the foreign key field in the direct table referencing the primary key of the source object.
     * Both the reference field and the source field that it references must be provided.
     */
    public void addReferenceKeyField(DatabaseField referenceForeignKeyField, DatabaseField sourcePrimaryKeyField) {
        getSourceKeyFields().addElement(sourcePrimaryKeyField);
        getReferenceKeyFields().addElement(referenceForeignKeyField);
    }
    
    /**
     * PUBLIC:
     * Add the name of the reference key field.
     * This is used for composite reference keys.
     * This is the foreign key field in the direct table referencing the primary key of the source object.
     * Both the reference field name and the name of the source field that it references must be provided.
     */
    public void addReferenceKeyFieldName(String referenceForeignKeyFieldName, String sourcePrimaryKeyFieldName) {
        addReferenceKeyField(new DatabaseField(referenceForeignKeyFieldName), new DatabaseField(sourcePrimaryKeyFieldName));
    }

    /**
     * INTERNAL:
     * Copy of the attribute of the object.
     * This is NOT used for unit of work but for templatizing an object.
     */
    public void buildCopy(Object copy, Object original, ObjectCopyingPolicy policy) {
        Object attributeValue = getRealCollectionAttributeValueFromObject(original, policy.getSession());
        attributeValue = getContainerPolicy().cloneFor(attributeValue);
        setRealAttributeValueInObject(copy, attributeValue);
    }

    /**
     * INTERNAL:
     * Clone the element, if necessary.
     * DirectCollections hold on to objects that do not have Descriptors
     * (e.g. int, String). These objects do not need to be cloned, unless they use a converter - they
     * are immutable.
     */
    protected Object buildElementClone(Object element, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        Object cloneValue = element;
        if ((getValueConverter() != null) && getValueConverter().isMutable()) {
            cloneValue = getValueConverter().convertDataValueToObjectValue(getValueConverter().convertObjectValueToDataValue(cloneValue, unitOfWork), unitOfWork);
        }
        return cloneValue;
    }

    /**
     * INTERNAL:
     * Cascade perform delete through mappings that require the cascade
     */
    public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, HardIdentityHashtable visitedObjects) {
        //as this mapping type references primitive objects this method does not apply
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, HardIdentityHashtable visitedObjects) {
        //as this mapping type references primitive objects this method does not apply
    }

    /**
     * INTERNAL:
     * The mapping clones itself to create deep copy.
     */
    public Object clone() {
        DirectCollectionMapping clone = (DirectCollectionMapping)super.clone();

        clone.setSourceKeyFields(cloneFields(getSourceKeyFields()));
        clone.setReferenceKeyFields(cloneFields(getReferenceKeyFields()));

        return clone;
    }

    /**
     * INTERNAL:
     * This method is used to calculate the differences between two collections.
     */
    public void compareCollectionsForChange(Object oldCollection, Object newCollection, ChangeRecord changeRecord, AbstractSession session){
        ContainerPolicy cp = getContainerPolicy();
        int numberOfNewNulls = 0;

        HashMap originalKeyValues = new HashMap(10);
        HashMap cloneKeyValues = new HashMap(10);
        
        if (oldCollection != null){
            Object backUpIter = cp.iteratorFor(oldCollection);

            while (cp.hasNext(backUpIter)) {// Make a lookup of the objects
                Object secondObject = cp.next(backUpIter, session);

                // For CR#2258/CR#2378 handle null values inserted in a collection.
                if (secondObject == null) {
                    numberOfNewNulls--;
                } else {
                    Integer count = (Integer)originalKeyValues.get(secondObject);
                    if (count == null) {
                        originalKeyValues.put(secondObject, new Integer(1));
                    } else {
                        originalKeyValues.put(secondObject, new Integer(count.intValue() + 1));
                    }
                }
            }
        }
        // should a removal occur this is the original count of objects on the database.
        // this value is used to determine how many objects to re-insert after the delete as a
        // delete will delete all of the objects not just one.
        HashMap databaseCount = (HashMap)originalKeyValues.clone();
        int databaseNullCount = Math.abs(numberOfNewNulls);

        if (newCollection != null){
            Object cloneIter = cp.iteratorFor(newCollection);

            /* The following code is used to compare objects in a direct collection.
               Because objects in a direct collection are primitives and may be the same object
               the following code must count the number of instances in the collection not just the
               existence of an object.
            */
            while (cp.hasNext(cloneIter)) {//Compare them with the objects from the clone
                Object firstObject = cp.next(cloneIter, session);
    
                // For CR#2258/CR#2378 handle null values inserted in a collection.
                if (firstObject == null) {
                    numberOfNewNulls++;
                } else {
                    Integer count = (Integer)originalKeyValues.get(firstObject);
                    if (count == null) {//the object was not in the backup
                        Integer cloneCount = (Integer)cloneKeyValues.get(firstObject);
    
                        //Add it to the additions hashtable
                        if (cloneCount == null) {
                            cloneKeyValues.put(firstObject, new Integer(1));
                        } else {
                            cloneKeyValues.put(firstObject, new Integer(cloneCount.intValue() + 1));
                        }
                    } else if (count.intValue() == 1) {
                        //There is only one object so remove the whole reference
                        originalKeyValues.remove(firstObject);
                    } else {
                        originalKeyValues.put(firstObject, new Integer(count.intValue() - 1));
                    }
                }
            }
        }
        if (cloneKeyValues.isEmpty() && originalKeyValues.isEmpty() && (numberOfNewNulls == 0) && (!changeRecord.getOwner().isNew())) {
            return;
        }
        ((DirectCollectionChangeRecord)changeRecord).addAdditionChange(cloneKeyValues, databaseCount);
        ((DirectCollectionChangeRecord)changeRecord).addRemoveChange(originalKeyValues, databaseCount);
        //For CR#2258, produce a changeRecord which reflects the addition and removal of null values.
        if (numberOfNewNulls != 0) {
            Vector changeList = null;
            ((DirectCollectionChangeRecord)changeRecord).getCommitAddMap().put(DirectCollectionChangeRecord.Null, new Integer(databaseNullCount));
            if (numberOfNewNulls > 0) {
                ((DirectCollectionChangeRecord)changeRecord).addAdditionChange(DirectCollectionChangeRecord.Null, new Integer(numberOfNewNulls));
            } else {
                numberOfNewNulls *= -1;
                ((DirectCollectionChangeRecord)changeRecord).addRemoveChange(DirectCollectionChangeRecord.Null, new Integer(numberOfNewNulls));
            }
        }
    }
    
    /**
     * INTERNAL:
     * This method compares the changes between two direct collections. Comparisons are made on equality
     * not identity.
     * @return prototype.changeset.ChangeRecord
     */
    public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
        Object cloneAttribute = null;
        Object backUpAttribute = null;
        int numberOfNewNulls = 0;

        ContainerPolicy cp = getContainerPolicy();

        cloneAttribute = getAttributeValueFromObject(clone);

        if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            return null;
        }

        Object cloneObjectCollection = getRealCollectionAttributeValueFromObject(clone, session);

        Object backUpCollection = null;

        if (!owner.isNew()) {
            backUpAttribute = getAttributeValueFromObject(backUp);

            if ((backUpAttribute == null) && (cloneAttribute == null)) {
                return null;
            }

            backUpCollection = getRealCollectionAttributeValueFromObject(backUp, session);
        }
        DirectCollectionChangeRecord changeRecord = new DirectCollectionChangeRecord(owner);
        changeRecord.setAttribute(getAttributeName());
        changeRecord.setMapping(this);
        compareCollectionsForChange(backUpCollection, cloneObjectCollection, changeRecord, session);
        if (changeRecord.hasChanges()){
            return changeRecord;
        }
        return null;
    }

    /**
     * INTERNAL:
     * Compare the attributes belonging to this mapping for the objects.
     */
    public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
        Object firstCollection = getRealCollectionAttributeValueFromObject(firstObject, session);
        Object secondCollection = getRealCollectionAttributeValueFromObject(secondObject, session);
        ContainerPolicy containerPolicy = getContainerPolicy();

        if (containerPolicy.sizeFor(firstCollection) != containerPolicy.sizeFor(secondCollection)) {
            return false;
        }

        HashMap firstCounter = new HashMap();
        HashMap secondCounter = new HashMap();
        for (Object iter = containerPolicy.iteratorFor(firstCollection);containerPolicy.hasNext(iter);) {
            Object object = containerPolicy.next(iter, session);
            if (firstCounter.containsKey(object)){
                int count = ((Integer)firstCounter.get(object)).intValue();
                firstCounter.put(object, new Integer(++count));
            }else{
                firstCounter.put(object, new Integer(1));
            }
        }
        for (Object iter = containerPolicy.iteratorFor(secondCollection);containerPolicy.hasNext(iter);) {
            Object object = containerPolicy.next(iter, session);
            if (secondCounter.containsKey(object)){
                int count = ((Integer)secondCounter.get(object)).intValue();
                secondCounter.put(object, new Integer(++count));
            }else{
                secondCounter.put(object, new Integer(1));
            }
        }
        for (Iterator iterator = firstCounter.keySet().iterator(); iterator.hasNext();){
            Object object = iterator.next();
            
            if (!secondCounter.containsKey(object) || ( ((Integer)secondCounter.get(object)).intValue() != ((Integer)firstCounter.get(object)).intValue()) ) {
                return false;
            }else{
                iterator.remove();
                secondCounter.remove(object);
            }
        }
        if ( !firstCounter.isEmpty() || !secondCounter.isEmpty() ) {
            return false;
        }
        return true;
    }

    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this mapping to actual class-based
     * settings
     * This method is implemented by subclasses as necessary.
     * @param classLoader
     */
    public void convertClassNamesToClasses(ClassLoader classLoader){
        super.convertClassNamesToClasses(classLoader);
        
        if (valueConverter != null) {
            if (valueConverter instanceof TypeConversionConverter){
                ((TypeConversionConverter)valueConverter).convertClassNamesToClasses(classLoader);
            } else if (valueConverter instanceof ObjectTypeConverter) {
                // To avoid 1.5 dependencies with the EnumTypeConverter check
                // against ObjectTypeConverter.
                ((ObjectTypeConverter) valueConverter).convertClassNamesToClasses(classLoader);
            }
        }
    };

    /**
     * INTERNAL:
     * Extract the source primary key value from the reference direct row.
     * Used for batch reading, most following same order and fields as in the mapping.
     */
    protected Vector extractKeyFromReferenceRow(AbstractRecord row, AbstractSession session) {
        Vector key = new Vector(getReferenceKeyFields().size());

        for (int index = 0; index < getReferenceKeyFields().size(); index++) {
            DatabaseField relationField = (DatabaseField)getReferenceKeyFields().elementAt(index);
            DatabaseField sourceField = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object value = row.get(relationField);

            // Must ensure the classificatin to get a cache hit.
            try {
                value = session.getDatasourcePlatform().getConversionManager().convertObject(value, getDescriptor().getObjectBuilder().getFieldClassification(sourceField));
            } catch (ConversionException e) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
            }

            key.addElement(value);
        }

        return key;
    }

    /**
     * INTERNAL:
     * Extract the primary key value from the source row.
     * Used for batch reading, most following same order and fields as in the mapping.
     */
    protected Vector extractPrimaryKeyFromRow(AbstractRecord row, AbstractSession session) {
        Vector key = new Vector(getSourceKeyFields().size());

        for (Enumeration fieldEnum = getSourceKeyFields().elements(); fieldEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)fieldEnum.nextElement();
            Object value = row.get(field);

            // Must ensure the classificatin to get a cache hit.
            try {
                value = session.getDatasourcePlatform().getConversionManager().convertObject(value, getDescriptor().getObjectBuilder().getFieldClassification(field));
            } catch (ConversionException e) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
            }

            key.addElement(value);
        }

        return key;
    }

    protected ModifyQuery getDeleteQuery() {
        if (changeSetDeleteQuery == null) {
            changeSetDeleteQuery = new DataModifyQuery();
        }
        return changeSetDeleteQuery;
    }

    /**
     * INTERNAL:
     * Return the direct field.
     * This is the field in the direct table to store the values.
     */
    public DatabaseField getDirectField() {
        return directField;
    }

    /**
     * PUBLIC:
     * Returns the name of the field name in the reference table.
     */
    public String getDirectFieldName() {
        if (getDirectField() == null) {
            return null;
        }
        return getDirectField().getQualifiedName();
    }

    protected DataModifyQuery getInsertQuery() {
        return insertQuery;
    }

    /**
     * INTERNAL:
     * This cannot be used with direct collection mappings.
     */
    public Class getReferenceClass() {
        return null;
    }

    public String getReferenceClassName() {
        return null;
    }

    /**
     * INTERNAL:
     * There is none on direct collection.
     */
    public ClassDescriptor getReferenceDescriptor() {
        return null;
    }

    /**
     * INTERNAL:
     * Return the reference key field names associated with the mapping.
     * These are in-order with the sourceKeyFieldNames.
     */
    public Vector getReferenceKeyFieldNames() {
        Vector fieldNames = new Vector(getReferenceKeyFields().size());
        for (Enumeration fieldsEnum = getReferenceKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    }

    /**
     * INTERNAL:
     * Return the reference key fields.
     */
    public Vector<DatabaseField> getReferenceKeyFields() {
        return referenceKeyFields;
    }

    /**
     * INTERNAL:
     * Return the direct table.
     * This is the table to store the values.
     */
    public DatabaseTable getReferenceTable() {
        return referenceTable;
    }

    /**
     * PUBLIC:
     * Returns the name of the reference table
     */
    public String getReferenceTableName() {
        if (getReferenceTable() == null) {
            return null;
        }
        return getReferenceTable().getName();
    }

    //This method is added to include table qualifier.

    /**
     * PUBLIC:
     * Returns the qualified name of the reference table
     */
    public String getReferenceTableQualifiedName() {//CR#2407
        if (getReferenceTable() == null) {
            return null;
        }
        return getReferenceTable().getQualifiedName();
    }

    /**
     * INTERNAL:
     * Return the relationshipPartner mapping for this bi-directional mapping. If the relationshipPartner is null then
     * this is a uni-directional mapping.
     * DirectCollectionMapping can not be part of a bi-directional mapping
     */
    public DatabaseMapping getRelationshipPartner() {
        return null;
    }

    /**
     * PUBLIC:
     * Return the source key field names associated with the mapping.
     * These are in-order with the referenceKeyFieldNames.
     */
    public Vector getSourceKeyFieldNames() {
        Vector fieldNames = new Vector(getSourceKeyFields().size());
        for (Enumeration fieldsEnum = getSourceKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    }

    /**
     * INTERNAL:
     * Return the source key fields.
     */
    public Vector<DatabaseField> getSourceKeyFields() {
        return sourceKeyFields;
    }

    protected boolean hasCustomDeleteQuery() {
        return hasCustomDeleteQuery;
    }

    protected boolean hasCustomInsertQuery() {
        return hasCustomInsertQuery;
    }

    /**
     * INTERNAL:
     * Initialize and validate the mapping properties.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        if (isKeyForSourceSpecified()) {
            initializeSourceKeys(session);
        } else {
            initializeSourceKeysWithDefaults(session);
        }

        initializeReferenceTable(session);
        initializeReferenceKeys(session);
        initializeDirectField(session);
        if (shouldInitializeSelectionCriteria()) {
            initializeSelectionCriteria(session);
            initializeSelectionStatement(session);
        }
        if (!getSelectionQuery().hasSessionName()) {
            getSelectionQuery().setSessionName(session.getName());
        }
        if ((getValueConverter() != null) && (getSelectionQuery() instanceof DirectReadQuery)) {
            ((DirectReadQuery)getSelectionQuery()).setValueConverter(getValueConverter());
        }
        initializeDeleteAllQuery(session);
        initializeDeleteQuery(session);
        initializeInsertQuery(session);
        if (getValueConverter() != null) {
            getValueConverter().initialize(this, session);
        }
        super.initialize(session);
    }

    /**
     * Initialize delete all query. This query is used to delete the collection of objects from the
     * reference table.
     */
    protected void initializeDeleteAllQuery(AbstractSession session) {
        if (!getDeleteAllQuery().hasSessionName()) {
            getDeleteAllQuery().setSessionName(session.getName());
        }

        if (hasCustomDeleteAllQuery()) {
            return;
        }

        Expression expression = null;
        Expression subExp1;
        Expression subExp2;
        Expression subExpression;
        Expression builder = new ExpressionBuilder();
        SQLDeleteStatement statement = new SQLDeleteStatement();

        // Construct an expression to delete from the relation table.
        for (int index = 0; index < getReferenceKeyFields().size(); index++) {
            DatabaseField referenceKey = (DatabaseField)getReferenceKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);

            subExp1 = builder.getField(referenceKey);
            subExp2 = builder.getParameter(sourceKey);
            subExpression = subExp1.equal(subExp2);

            if (expression == null) {
                expression = subExpression;
            } else {
                expression = expression.and(subExpression);
            }
        }

        statement.setWhereClause(expression);
        statement.setTable(getReferenceTable());
        getDeleteAllQuery().setSQLStatement(statement);
    }

    protected void initializeDeleteQuery(AbstractSession session) {
        if (!getDeleteQuery().hasSessionName()) {
            getDeleteQuery().setSessionName(session.getName());
        }

        if (hasCustomDeleteQuery()) {
            return;
        }

        Expression builder = new ExpressionBuilder();
        Expression directExp = builder.getField(getDirectField()).equal(builder.getParameter(getDirectField()));
        Expression expression = null;
        SQLDeleteStatement statement = new SQLDeleteStatement();

        // Construct an expression to delete from the relation table.
        for (int index = 0; index < getReferenceKeyFields().size(); index++) {
            DatabaseField referenceKey = (DatabaseField)getReferenceKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);

            Expression subExp1 = builder.getField(referenceKey);
            Expression subExp2 = builder.getParameter(sourceKey);
            Expression subExpression = subExp1.equal(subExp2);

            expression = subExpression.and(expression);
        }
        expression = expression.and(directExp);
        statement.setWhereClause(expression);
        statement.setTable(getReferenceTable());
        getDeleteQuery().setSQLStatement(statement);
    }

    /**
     * The field name on the reference table is initialized and cached.
     */
    protected void initializeDirectField(AbstractSession session) throws DescriptorException {
        if (getDirectField() == null) {
            throw DescriptorException.directFieldNameNotSet(this);
        }

        getDirectField().setTable(getReferenceTable());
        getDirectField().setIndex(0);
    }

    /**
     * Initialize insert query. This query is used to insert the collection of objects into the
     * reference table.
     */
    protected void initializeInsertQuery(AbstractSession session) {
        if (!getInsertQuery().hasSessionName()) {
            getInsertQuery().setSessionName(session.getName());
        }

        if (hasCustomInsertQuery()) {
            return;
        }

        SQLInsertStatement statement = new SQLInsertStatement();
        statement.setTable(getReferenceTable());
        AbstractRecord directRow = new DatabaseRecord();
        for (Enumeration referenceEnum = getReferenceKeyFields().elements();
                 referenceEnum.hasMoreElements();) {
            directRow.put((DatabaseField)referenceEnum.nextElement(), null);
        }
        directRow.put(getDirectField(), null);
        statement.setModifyRow(directRow);
        getInsertQuery().setSQLStatement(statement);
        getInsertQuery().setModifyRow(directRow);
    }

    /**
     * There is no reference descriptor
     */
    protected void initializeReferenceDescriptor(AbstractSession session) {
        ;
    }

    /**
     * The reference keys on the reference table are initalized
     */
    protected void initializeReferenceKeys(AbstractSession session) throws DescriptorException {
        if (getReferenceKeyFields().size() == 0) {
            throw DescriptorException.noReferenceKeyIsSpecified(this);
        }

        for (Enumeration referenceEnum = getReferenceKeyFields().elements();
                 referenceEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)referenceEnum.nextElement();
            if (field.hasTableName() && (!(field.getTableName().equals(getReferenceTable().getName())))) {
                throw DescriptorException.referenceKeyFieldNotProperlySpecified(field, this);
            }
            field.setTable(getReferenceTable());
        }
    }

    /*
     * Set the table qualifier on the reference table if required
     */
    protected void initializeReferenceTable(AbstractSession session) throws DescriptorException {
        Platform platform = session.getDatasourcePlatform();

        if (getReferenceTable() == null) {
            throw DescriptorException.referenceTableNotSpecified(this);
        }

        if (platform.getTableQualifier().length() == 0) {
            return;
        }

        if (getReferenceTable().getTableQualifier().length() == 0) {
            getReferenceTable().setTableQualifier(platform.getTableQualifier());
        }
    }

    protected void initializeSelectionCriteria(AbstractSession session) {
        Expression exp1;
        Expression exp2;
        Expression expression;
        Expression criteria = null;
        Enumeration referenceKeysEnum;
        Enumeration sourceKeysEnum;
        ExpressionBuilder base = new ExpressionBuilder();
        TableExpression table = (TableExpression)base.getTable(getReferenceTable());

        referenceKeysEnum = getReferenceKeyFields().elements();
        sourceKeysEnum = getSourceKeyFields().elements();

        for (; referenceKeysEnum.hasMoreElements();) {
            DatabaseField referenceKey = (DatabaseField)referenceKeysEnum.nextElement();
            DatabaseField sourceKey = (DatabaseField)sourceKeysEnum.nextElement();

            exp1 = table.getField(referenceKey);
            exp2 = base.getParameter(sourceKey);
            expression = exp1.equal(exp2);

            if (criteria == null) {
                criteria = expression;
            } else {
                criteria = expression.and(criteria);
            }
        }

        setSelectionCriteria(criteria);
    }

    /**
     * The selection query is initialized
     */
    protected void initializeSelectionQuery(AbstractSession session) {
        // Nothing required.
    }

    protected void initializeSelectionStatement(AbstractSession session) {
        SQLSelectStatement statement = new SQLSelectStatement();
        statement.addTable(getReferenceTable());
        statement.addField((DatabaseField)getDirectField().clone());
        statement.setWhereClause(getSelectionCriteria());
        statement.normalize(session, null);
        getSelectionQuery().setSQLStatement(statement);
    }

    /**
     * The source keys are initalized
     */
    protected void initializeSourceKeys(AbstractSession session) {
        for (Enumeration sourceKeyEnum = getSourceKeyFields().elements();
                 sourceKeyEnum.hasMoreElements();) {
            getDescriptor().buildField((DatabaseField)sourceKeyEnum.nextElement());
        }
    }

    /**
     * INTERNAL:
     * If a user does not specify the source key then the primary keys of the source table are used.
     */
    protected void initializeSourceKeysWithDefaults(AbstractSession session) {
        List<DatabaseField> primaryKeyFields = getDescriptor().getPrimaryKeyFields();
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            getSourceKeyFields().addElement(primaryKeyFields.get(index));
        }
    }

    /**
     * INTERNAL:
     */
    public boolean isDirectCollectionMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Checks if source and target keys are mentioned by the user or not.
     */
    protected boolean isKeyForSourceSpecified() {
        return !getSourceKeyFields().isEmpty();
    }

    /**
     * INTERNAL:
     * Return true if referenced objects are provately owned else false.
     */
    public boolean isPrivateOwned() {
        return true;
    }

    /**
     * INTERNAL:
     * Iterate on the attribute value.
     * The value holder has already been processed.
     * PERF: Avoid iteration if not required.
     */
    public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) {
        if (iterator.shouldIterateOnPrimitives()) {
            super.iterateOnRealAttributeValue(iterator, realAttributeValue);
        }
    }

    /**
     * INTERNAL:
     * Iterate on the specified element.
     */
    public void iterateOnElement(DescriptorIterator iterator, Object element) {
        iterator.iteratePrimitiveForMapping(element, this);
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     * Because this is a collection mapping, values are added to or removed from the
     * collection based on the changeset
     */
    public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager) {
        ContainerPolicy containerPolicy = getContainerPolicy();
        Object valueOfTarget = null;
        AbstractSession session = mergeManager.getSession();

        //collect the changes into a vector
        HashMap addObjects = ((DirectCollectionChangeRecord)changeRecord).getAddObjectMap();
        HashMap removeObjects = ((DirectCollectionChangeRecord)changeRecord).getRemoveObjectMap();

        //Check to see if the target has an instantiated collection
        if ((isAttributeValueInstantiated(target)) && (!changeRecord.getOwner().isNew())) {
            valueOfTarget = getRealCollectionAttributeValueFromObject(target, session);
        } else {
            //if not create an instance of the collection
            valueOfTarget = containerPolicy.containerInstance(addObjects.size());
        }
        if (!isAttributeValueInstantiated(target)) {
            if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
                return;
            }
            for (Object iterator = containerPolicy.iteratorFor(getRealCollectionAttributeValueFromObject(source, session));
                     containerPolicy.hasNext(iterator);) {
                containerPolicy.addInto(containerPolicy.next(iterator, session), valueOfTarget, session);
            }
        } else {
            synchronized (valueOfTarget) {
                // Next iterate over the changes and add them to the container
                for (Iterator iterator = addObjects.keySet().iterator(); iterator.hasNext(); ){
                    Object object = iterator.next();
                    int objectCount = ((Integer)addObjects.get(object)).intValue();
                    for (int i = 0; i < objectCount; ++i) {
                        if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
                            //bug#4458089 and 4454532- check if collection contains new item before adding during merge into distributed cache
                            if (!containerPolicy.contains(object, valueOfTarget, session)) {
                                containerPolicy.addInto(object, valueOfTarget, session);
                            }
                        } else {
                            containerPolicy.addInto(object, valueOfTarget, session);
                        }
                    }
                }
                for (Iterator iterator = removeObjects.keySet().iterator(); iterator.hasNext(); ){
                    Object object = iterator.next();
                    int objectCount = ((Integer)removeObjects.get(object)).intValue();
                    for (int i = 0; i < objectCount; ++i) {
                        containerPolicy.removeFrom(object, valueOfTarget, session);
                    }
                }
            }
        }
        setRealAttributeValueInObject(target, valueOfTarget);
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     */
    public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) {
        if (isTargetUnInitialized) {
            // This will happen if the target object was removed from the cache before the commit was attempted
            if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) {
                setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), mergeManager.getSession()));
                return;
            }
        }
        if (!shouldMergeCascadeReference(mergeManager)) {
            // This is only going to happen on mergeClone, and we should not attempt to merge the reference
            return;
        }
        if (mergeManager.shouldMergeOriginalIntoWorkingCopy()) {
            if (!isAttributeValueInstantiated(target)) {
                // This will occur when the clone's value has not been instantiated yet and we do not need
                // the refresh that attribute
                return;
            }
        } else if (!isAttributeValueInstantiated(source)) {
            // I am merging from a clone into an original. No need to do merge if the attribute was never
            // modified
            return;
        }

        ContainerPolicy containerPolicy = getContainerPolicy();
        Object valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession());

        // trigger instantiation of target attribute
        Object valueOfTarget = getRealCollectionAttributeValueFromObject(target, mergeManager.getSession());
        Object newContainer = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource));
        boolean fireChangeEvents = false;
        valueOfTarget = newContainer;
        for (Object sourceValuesIterator = containerPolicy.iteratorFor(valueOfSource);
                 containerPolicy.hasNext(sourceValuesIterator);) {
            Object sourceValue = containerPolicy.next(sourceValuesIterator, mergeManager.getSession());
            containerPolicy.addInto(sourceValue, valueOfTarget, mergeManager.getSession());
        }

        // Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly.
        setRealAttributeValueInObject(target, valueOfTarget);
    }

    /**
     * INTERNAL:
     * Perform the commit event.
     * This is used in the uow to delay data modifications.
     */
    public void performDataModificationEvent(Object[] event, AbstractSession session) throws DatabaseException, DescriptorException {
        // Hey I might actually want to use an inner class here... ok array for now.
        if (event[0] == Delete){
            session.executeQuery((DataModifyQuery)event[1], (AbstractRecord)event[(2)]);
        } else if (event[0] == Insert) {
            session.executeQuery((DataModifyQuery)event[1], (AbstractRecord)event[(2)]);
        } else if (event[0] == DeleteAll) {
            preDelete((DeleteObjectQuery)event[1]);
        } else {
            throw DescriptorException.invalidDataModificationEventCode(event[0], this);
        }
    }

    /**
     * INTERNAL:
     * Insert the private owned object.
     */
    public void postInsert(WriteObjectQuery query) throws DatabaseException {
        Object objects;
        AbstractRecord databaseRow = new DatabaseRecord();

        if (isReadOnly()) {
            return;
        }

        objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());
        ContainerPolicy containerPolicy = getContainerPolicy();
        if (containerPolicy.isEmpty(objects)) {
            return;
        }

        prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getSession());
        // Extract primary key and value from the source.
        for (int index = 0; index < getReferenceKeyFields().size(); index++) {
            DatabaseField referenceKey = (DatabaseField)getReferenceKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object sourceKeyValue = query.getTranslationRow().get(sourceKey);
            databaseRow.put(referenceKey, sourceKeyValue);
        }

        // Extract target field and its value. Construct insert statement and execute it
        for (Object iter = containerPolicy.iteratorFor(objects); containerPolicy.hasNext(iter);) {
            Object object = containerPolicy.next(iter, query.getSession());
            if (getValueConverter() != null) {
                object = getValueConverter().convertObjectValueToDataValue(object, query.getSession());
            }
            databaseRow.put(getDirectField(), object);

            // In the uow data queries are cached until the end of the commit.
            if (query.shouldCascadeOnlyDependentParts()) {
                // Hey I might actually want to use an inner class here... ok array for now.
                Object[] event = new Object[3];
                event[0] = Insert;
                event[1] = getInsertQuery();
                event[2] = databaseRow.clone();
                query.getSession().getCommitManager().addDataModificationEvent(this, event);
            } else {
                query.getSession().executeQuery(getInsertQuery(), databaseRow);
            }
        }
    }

    /**
     * INTERNAL:
     * Update private owned part.
     */
    public void postUpdate(WriteObjectQuery writeQuery) throws DatabaseException {
        if (isReadOnly()) {
            return;
        }

        if (writeQuery.getObjectChangeSet() != null){
            postUpdateWithChangeSet(writeQuery);
            return;
        }
        // If objects are not instantiated that means they are not changed.
        if (!isAttributeValueInstantiated(writeQuery.getObject())) {
            return;
        }

        if (writeQuery.getSession().isUnitOfWork()) {
            if (compareObjects(writeQuery.getObject(), writeQuery.getBackupClone(), writeQuery.getSession())) {
                return;// Nothing has changed, no work required
            }
        }

        DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
        deleteQuery.setObject(writeQuery.getObject());
        deleteQuery.setSession(writeQuery.getSession());
        deleteQuery.setTranslationRow(writeQuery.getTranslationRow());

        if (writeQuery.shouldCascadeOnlyDependentParts()) {
            // Hey I might actually want to use an inner class here... ok array for now.
            Object[] event = new Object[3];
            event[0] = DeleteAll;
            event[1] = deleteQuery;
            writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event);
        } else {
            preDelete(deleteQuery);
        }
        postInsert(writeQuery);
    }

    /**
     * INTERNAL:
     * Update private owned part.
     */
    protected void postUpdateWithChangeSet(WriteObjectQuery writeQuery) throws DatabaseException {

        ObjectChangeSet changeSet = writeQuery.getObjectChangeSet();
        DirectCollectionChangeRecord changeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (changeRecord == null){
            return;
        }
        for (int index = 0; index < getReferenceKeyFields().size(); index++) {
            DatabaseField referenceKey = (DatabaseField)getReferenceKeyFields().elementAt(index);
            DatabaseField sourceKey = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object sourceKeyValue = writeQuery.getTranslationRow().get(sourceKey);
            writeQuery.getTranslationRow().put(referenceKey, sourceKeyValue);
        }
        for (Iterator iterator = changeRecord.getRemoveObjectMap().keySet().iterator(); iterator.hasNext();){
            Object object = iterator.next();
            AbstractRecord thisRow = (AbstractRecord)writeQuery.getTranslationRow().clone();
            Object value = object;
            if (getValueConverter() != null){
                value = getValueConverter().convertObjectValueToDataValue(value, writeQuery.getSession());
            }
            if (value == DirectCollectionChangeRecord.Null){
                thisRow.add(getDirectField(), null);
            }else{
                thisRow.add(getDirectField(), value);
            }
            // Hey I might actually want to use an inner class here... ok array for now.
            Object[] event = new Object[3];
            event[0] = Delete;
            event[1] = getDeleteQuery();
            event[2] = thisRow;
            writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event);
            Integer count = (Integer)changeRecord.getCommitAddMap().get(object);
            if (count != null){
                for (int counter = count.intValue(); counter > 0; --counter){
                    thisRow = (AbstractRecord)writeQuery.getTranslationRow().clone();
                    thisRow.add(getDirectField(), value);
                    // Hey I might actually want to use an inner class here... ok array for now.
                    event = new Object[3];
                    event[0] = Insert;
                    event[1] = getInsertQuery();
                    event[2] = thisRow;
                    writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event);
                }
            }
        }
        for (Iterator iterator = changeRecord.getAddObjectMap().keySet().iterator(); iterator.hasNext();){
            Object object = iterator.next();
            Integer count = (Integer)changeRecord.getAddObjectMap().get(object);
            for (int counter = count.intValue(); counter > 0; --counter){
                AbstractRecord thisRow = (AbstractRecord)writeQuery.getTranslationRow().clone();
                Object value = object;
                if (getValueConverter() != null){
                    value = getValueConverter().convertObjectValueToDataValue(value, writeQuery.getSession());
                }
                if (value == DirectCollectionChangeRecord.Null){ //special placeholder for nulls
                    thisRow.add(getDirectField(), null);
                }else{
                    thisRow.add(getDirectField(), value);
                }
                // Hey I might actually want to use an inner class here... ok array for now.
                Object[] event = new Object[3];
                event[0] = Insert;
                event[1] = getInsertQuery();
                event[2] = thisRow;
                writeQuery.getSession().getCommitManager().addDataModificationEvent(this, event);
            }
        }
    }

    /**
     * INTERNAL:
     * Delete private owned part. Which is a collection of objects from the reference table.
     */
    public void preDelete(WriteObjectQuery query) throws DatabaseException {
        if (isReadOnly()) {
            return;
        }

        prepareTranslationRow(query.getTranslationRow(), query.getObject(), query.getSession());
        query.getSession().executeQuery(getDeleteAllQuery(), query.getTranslationRow());
    }
    
    /**
     * INTERNAL:
     * The translation row may require additional fields than the primary key if the mapping in not on the primary key.
     */
    protected void prepareTranslationRow(AbstractRecord translationRow, Object object, AbstractSession session) {
        // Make sure that each source key field is in the translation row.
        for (Enumeration sourceFieldsEnum = getSourceKeyFields().elements();
                 sourceFieldsEnum.hasMoreElements();) {
            DatabaseField sourceKey = (DatabaseField)sourceFieldsEnum.nextElement();
            if (!translationRow.containsKey(sourceKey)) {
                Object value = getDescriptor().getObjectBuilder().extractValueFromObjectForField(object, sourceKey, session);
                translationRow.put(sourceKey, value);
            }
        }
    }

    protected void setDeleteQuery(ModifyQuery query) {
        this.changeSetDeleteQuery = query;
    }

    /**
     * PUBLIC:
     * Set the receiver's delete SQL string. This allows the user to override the SQL
     * generated by TopLink, with there own SQL or procedure call. The arguments are
     * translated from the fields of the source row, through replacing the field names
     * marked by '#' with the values for those fields.
     * This SQL is responsible for doing the deletion required by the mapping,
     * such as deletion from join table for M-M.
     * Example, 'delete from RESPONS where EMP_ID = #EMP_ID and DESCRIP = #DESCRIP'.
     */
    public void setDeleteSQLString(String sqlString) {
        DataModifyQuery query = new DataModifyQuery();
        query.setSQLString(sqlString);
        setCustomDeleteQuery(query);
    }

    /**
     * ADVANCED:
     * Configure the mapping to use a container policy.
     * The policy manages the access to the collection.
     */
    public void setContainerPolicy(ContainerPolicy containerPolicy) {
        this.containerPolicy = containerPolicy;
        ((DataReadQuery)getSelectionQuery()).setContainerPolicy(containerPolicy);
    }

    /**
     * PUBLIC:
     * The default delete query for this mapping can be overridden by specifying the new query.
     * This query is responsible for doing the deletion required by the mapping,
     * such as deletion from join table for M-M. The query should delete a specific row from the
     * DirectCollectionTable bases on the DirectField.
     */
    public void setCustomDeleteQuery(ModifyQuery query) {
        setDeleteQuery(query);
        setHasCustomDeleteQuery(true);
    }

    /**
     * PUBLIC:
     * The default insert query for mapping can be overridden by specifying the new query.
     * This query inserts the row into the direct table.
     */
    public void setCustomInsertQuery(DataModifyQuery query) {
        setInsertQuery(query);
        setHasCustomInsertQuery(true);
    }

    /**
     * PUBLIC:
     * Set the direct field in the reference table.
     * This is the field that the primitive data value is stored in.
     */
    public void setDirectField(DatabaseField field) {
        directField = field;
    }
    
    /**
     * ADVANCED:
     * Set the class type of the field value.
     * This can be used if field value differs from the object value,
     * has specific typing requirements such as usage of java.sql.Blob or NChar.
     * This must be called after the field name has been set.
     */
    public void setDirectFieldClassification(Class fieldType) {
        getDirectField().setType(fieldType);
    }

    /**
     * PUBLIC:
     * Set the direct field name in the reference table.
     * This is the field that the primitive data value is stored in.
     */
    public void setDirectFieldName(String fieldName) {
        setDirectField(new DatabaseField(fieldName));
    }

    protected void setHasCustomDeleteQuery(boolean bool) {
        hasCustomDeleteQuery = bool;
    }

    protected void setHasCustomInsertQuery(boolean bool) {
        hasCustomInsertQuery = bool;
    }

    protected void setInsertQuery(DataModifyQuery insertQuery) {
        this.insertQuery = insertQuery;
    }

    /**
     * PUBLIC:
     * Set the receiver's insert SQL string. This allows the user to override the SQL
     * generated by TopLink, with there own SQL or procedure call. The arguments are
     * translated from the fields of the source row, through replacing the field names
     * marked by '#' with the values for those fields.
     * This is used to insert an entry into the direct table.
     * Example, 'insert into RESPONS (EMP_ID, RES_DESC) values (#EMP_ID, #RES_DESC)'.
     */
    public void setInsertSQLString(String sqlString) {
        DataModifyQuery query = new DataModifyQuery();
        query.setSQLString(sqlString);
        setCustomInsertQuery(query);
    }

    /**
     * INTERNAL:
     * This cannot be used with direct collection mappings.
     */
    public void setReferenceClass(Class referenceClass) {
        return;
    }

    public void setReferenceClassName(String referenceClassName) {
        return;
    }

    /**
     * PUBLIC:
     * Set the name of the reference key field.
     * This is the foreign key field in the direct table referencing the primary key of the source object.
     * This method is used if the reference key consists of only a single field.
     */
    public void setReferenceKeyFieldName(String fieldName) {
        getReferenceKeyFields().addElement(new DatabaseField(fieldName));
    }

    /**
     * INTERNAL:
     * Set the reference key field names associated with the mapping.
     * These must be in-order with the sourceKeyFieldNames.
     */
    public void setReferenceKeyFieldNames(Vector fieldNames) {
        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setReferenceKeyFields(fields);
    }

    /**
     * INTERNAL:
     * Set the reference fields.
     */
    public void setReferenceKeyFields(Vector<DatabaseField> aVector) {
        this.referenceKeyFields = aVector;
    }

    /**
     * INTERNAL:
     * Set the reference table.
     */
    public void setReferenceTable(DatabaseTable table) {
        referenceTable = table;
    }

    /**
     * PUBLIC:
     * Set the reference table name.
     */
    public void setReferenceTableName(String tableName) {
        if (tableName == null) {
            setReferenceTable(null);
        } else {
            setReferenceTable(new DatabaseTable(tableName));
        }
    }

    /**
     * PUBLIC:
     * Set the name of the session to execute the mapping's queries under.
     * This can be used by the session broker to override the default session
     * to be used for the target class.
     */
    public void setSessionName(String name) {
        super.setSessionName(name);
        getInsertQuery().setSessionName(name);
    }

    /**
     * INTERNAL:
     * Set the source key field names associated with the mapping.
     * These must be in-order with the referenceKeyFieldNames.
     */
    public void setSourceKeyFieldNames(Vector fieldNames) {
        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setSourceKeyFields(fields);
    }

    /**
     * INTERNAL:
     * Set the source fields.
     */
    public void setSourceKeyFields(Vector<DatabaseField> sourceKeyFields) {
        this.sourceKeyFields = sourceKeyFields;
    }

    /**
     * INTERNAL:
     * Used by AttributeLevelChangeTracking to update a changeRecord with calculated changes
     * as apposed to detected changes. If an attribute can not be change tracked it's
     * changes can be detected through this process.
     */
    public void calculateDeferredChanges(ChangeRecord changeRecord, AbstractSession session){
        DirectCollectionChangeRecord collectionRecord = (DirectCollectionChangeRecord) changeRecord;
        compareCollectionsForChange(collectionRecord.getOriginalCollection(), collectionRecord.getLatestCollection(), collectionRecord, session);
    }

    /**
     * ADVANCED:
     * This method is used to have an object add to a collection once the changeSet is applied
     * The referenceKey parameter should only be used for direct Maps.

     */
    public void simpleAddToCollectionChangeRecord(Object referenceKey, Object objectToAdd, ObjectChangeSet changeSet, AbstractSession session) {
        DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new DirectCollectionChangeRecord(changeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            changeSet.addChange(collectionChangeRecord);
            Object collection = getRealAttributeValueFromObject(changeSet.getUnitOfWorkClone(), session);
            collectionChangeRecord.storeDatabaseCounts(collection, getContainerPolicy(), session);
        }
        collectionChangeRecord.addAdditionChange(objectToAdd, new Integer(1));
    }

   /**
     * ADVANCED:
     * This method is used to have an object removed from a collection once the changeSet is applied
     * The referenceKey parameter should only be used for direct Maps.
     */
    public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object objectToRemove, ObjectChangeSet changeSet, AbstractSession session) {
        DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)changeSet.getChangesForAttributeNamed(getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new DirectCollectionChangeRecord(changeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            changeSet.addChange(collectionChangeRecord);
            Object collection = getRealAttributeValueFromObject(changeSet.getUnitOfWorkClone(), session);
            collectionChangeRecord.storeDatabaseCounts(collection, getContainerPolicy(), session);
        }
        collectionChangeRecord.addRemoveChange(objectToRemove, new Integer(1));
    }

    /**
     * INTERNAL:
     * Either create a new change record or update with the new value. This is used
     * by attribute change tracking.
     * Specifically in a collection mapping this will be called when the customer
     * Set a new collection. In this case we will need to mark the change record
     * with the new and the old versions of the collection.
     * And mark the ObjectChangeSet with the attribute name then when the changes are calculated
     * force a compare on the collections to determine changes.
     */
    public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new DirectCollectionChangeRecord(objectChangeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            objectChangeSet.addChange(collectionChangeRecord);
        }
        if (collectionChangeRecord.getOriginalCollection() == null){
            collectionChangeRecord.setOriginalCollection(oldValue);
        }
        collectionChangeRecord.setLatestCollection(newValue);
        
        objectChangeSet.deferredDetectionRequiredOn(getAttributeName());
    }

    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container class
     * to hold the target objects.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface.
     * <p>jdk1.1.x: The container class must be a subclass of Vector.
     */
    public void useCollectionClass(Class concreteClass) {
        ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass);
        setContainerPolicy(policy);
    }

    /**
     * PUBLIC:
     * It is illegal to use a Map as the container of a DirectCollectionMapping. Only
     * Collection containers are supported for DirectCollectionMappings.
     * @see oracle.toplink.essentials.mappings.DirectMapMapping
     */
    public void useMapClass(Class concreteClass, String methodName) {
        throw ValidationException.illegalUseOfMapInDirectCollection(this, concreteClass, methodName);
    }

    /**
     * INTERNAL:
     * Return the value of the reference attribute or a value holder.
     * Check whether the mapping's attribute should be optimized through batch and joining.
     * Overridden to support flasback/historical queries.
     */
    public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, AbstractSession session) throws DatabaseException {
        ReadQuery targetQuery = getSelectionQuery();
        return getIndirectionPolicy().valueFromQuery(targetQuery, row, query.getSession());
    }

    /**
     * INTERNAL:
     * Checks if object is deleted from the database or not.
     */
    public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException {
        // Row is built for translation
        if (isReadOnly()) {
            return true;
        }

        AbstractRecord row = getDescriptor().getObjectBuilder().buildRowForTranslation(object, session);
        Object value = session.executeQuery(getSelectionQuery(), row);

        return getContainerPolicy().isEmpty(value);
    }

    /**
     * INTERNAL:
     * Add a new value and its change set to the collection change record. This is used by
     * attribute change tracking.
     */
    public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        if (newValue == null) {
            newValue = DirectCollectionChangeRecord.Null;
        }
        ClassDescriptor descriptor;
        DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new DirectCollectionChangeRecord(objectChangeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            objectChangeSet.addChange(collectionChangeRecord);
            Object collection = getRealAttributeValueFromObject(objectChangeSet.getUnitOfWorkClone(), uow);
            collectionChangeRecord.storeDatabaseCounts(collection, getContainerPolicy(), uow);
        }
        collectionChangeRecord.addAdditionChange(newValue, new Integer(1));
    }
    
    /**
     * INTERNAL
     * Return true if this mapping supports cascaded version optimistic locking.
     */
    public boolean isCascadedLockingSupported() {
        return true;
    }
    
    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return true;
    }

    /**
     * INTERNAL:
     * Remove a value and its change set from the collection change record. This is used by
     * attribute change tracking.
     */
    public void removeFromCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        if (newValue == null) {
            newValue = DirectCollectionChangeRecord.Null;
        }
        ClassDescriptor descriptor;
        DirectCollectionChangeRecord collectionChangeRecord = (DirectCollectionChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            collectionChangeRecord = new DirectCollectionChangeRecord(objectChangeSet);
            collectionChangeRecord.setAttribute(getAttributeName());
            collectionChangeRecord.setMapping(this);
            objectChangeSet.addChange(collectionChangeRecord);
            Object collection = getRealAttributeValueFromObject(objectChangeSet.getUnitOfWorkClone(), uow);
            collectionChangeRecord.storeDatabaseCounts(collection, getContainerPolicy(), uow);
        }
        collectionChangeRecord.addRemoveChange(newValue, new Integer(1));
    }

}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package oracle.toplink.essentials.internal.ejb.cmp3.base;

import java.util.Vector;
import java.util.Map;

import javax.persistence.EntityExistsException;
import javax.persistence.EntityNotFoundException;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceException;

import java.util.HashMap;

import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.exceptions.TopLinkException;
import oracle.toplink.essentials.exceptions.ValidationException;
import oracle.toplink.essentials.expressions.Expression;
import oracle.toplink.essentials.internal.ejb.cmp3.transaction.base.TransactionWrapperImpl;
import oracle.toplink.essentials.internal.localization.ExceptionLocalization;
import oracle.toplink.essentials.internal.sessions.IsolatedClientSession;
import oracle.toplink.essentials.internal.sessions.MergeManager;
import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;
import oracle.toplink.essentials.internal.descriptors.OptimisticLockingPolicy;
import oracle.toplink.essentials.descriptors.VersionLockingPolicy;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.Session;
import oracle.toplink.essentials.sessions.UnitOfWork;
import oracle.toplink.essentials.threetier.ServerSession;
import oracle.toplink.essentials.tools.sessionmanagement.SessionManager;
import oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider;
import oracle.toplink.essentials.config.TopLinkProperties;


/**
* <p>
* <b>Purpose</b>: Contains the implementation of the EntityManager.
* <p>
* <b>Description</b>: This class provides the implementation for the combined TopLink
* and EJB3.0 EntityManager class.
* <p>
* <b>Responsibilities</b>:It is responcible for tracking transaction state and the
* objects within that transaction.
* @see javax.persistence.EntityManager
* @see oracle.toplink.essentials.ejb.cmp3.EntityManager
*/

/* @author gyorke
 * @since TopLink 10.1.3 EJB 3.0 Preview
 */


public abstract class EntityManagerImpl {
   
    protected TransactionWrapperImpl transaction = null;
    protected boolean isOpen = true;
    
    protected RepeatableWriteUnitOfWork extendedPersistenceContext;
    //THis attribute references the ServerSession that this deployement is using.
    //This is a simple mechanism to reduce the number of SessionManager accesses.
    protected ServerSession serverSession;
    // References the factory that has created this entity manager
    // to make sure that the factory is not garbage collected
    protected EntityManagerFactoryImpl factory;
    
    //We have not removed these flags because we may want to use them at a later date to provide transactional EntityManagers in JAVA SE
    protected boolean extended;
    protected boolean propagatePersistenceContext;
    
    //gf3334, force begin early transaction flag.
    protected boolean beginEarlyTransaction = false;
    
    //gf3334, this is place holder for properties that passed from createEntityManager
    protected Map properties;


    protected abstract void setJTATransactionWrapper();
    protected abstract void setEntityTransactionWrapper();
    /**
    * Internal method. Indicates whether flushMode is AUTO.
    * @return boolean
    */
    public abstract boolean isFlushModeAUTO();
    
    /**
     * Constructor returns an EntityManager assigned to the a particular ServerSession.
     * @param sessionName the ServerSession name that should be used.
     * This constructor can potentially throw TopLink exceptions regarding the existence, or
     * errors with the specified session.
     */
    public EntityManagerImpl(String sessionName, boolean propagatePersistenceContext, boolean extended){
        this((ServerSession) SessionManager.getManager().getSession(sessionName), null, propagatePersistenceContext, extended);
    }

    /**
     * Constructor called from the EntityManagerFactory to create an EntityManager
     * @param serverSession the serverSession assigned to this deployment.
     */
    public EntityManagerImpl(ServerSession serverSession, boolean propagatePersistenceContext, boolean extended){
        this(serverSession, null, propagatePersistenceContext, extended);
    }


    /**
     * Constructor called from the EntityManagerFactory to create an EntityManager
     * @param serverSession the serverSession assigned to this deployment.
     * Note: The properties argument is provided to allow properties to be passed into this EntityManager,
     * but there are currently no such properties implemented
     */
    public EntityManagerImpl(ServerSession serverSession, Map properties, boolean propagatePersistenceContext, boolean extended){
        this.serverSession = serverSession;
        detectTransactionWrapper();
        this.extended = true;
        this.propagatePersistenceContext = false;
        this.properties=properties;
        setBeginEarlyTransaction();
    }

    /**
     * Constructor called from the EntityManagerFactory to create an EntityManager
     * @param factory the EntityMangerFactoryImpl that created this entity manager.
     * Note: The properties argument is provided to allow properties to be passed into this EntityManager,
     * but there are currently no such properties implemented
     */
    public EntityManagerImpl(EntityManagerFactoryImpl factory, Map properties, boolean propagatePersistenceContext, boolean extended){
        this.factory = factory;
        this.serverSession = factory.getServerSession();
        detectTransactionWrapper();
        this.extended = true;
        this.propagatePersistenceContext = false;
        this.properties=properties;
        setBeginEarlyTransaction();
    }

    /**
    * Clear the persistence context, causing all managed
    * entities to become detached. Changes made to entities that
    * have not been flushed to the database will not be
    * persisted.
    */
    public void clear(){
        try {
            verifyOpen();
            if (this.isExtended()){
                if(this.extendedPersistenceContext != null){
                    if (checkForTransaction(false) == null){
                        // clear all change sets and cache
                        this.extendedPersistenceContext.clearForClose(true);
                        this.extendedPersistenceContext = null;
                    } else {
                        // clear all change sets created after the last flush and cache
                        this.extendedPersistenceContext.clear(true);
                    }
                }

            } else {
                transaction.clear();
            }
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
    }

    /**
    * Internal method called by EntityTransactionImpl class in case of transaction rollback.
    * The caller is responsible for releasing extendedPersistenceContext and it's parent.
    */
    public void removeExtendedPersistenceContext(){
        this.extendedPersistenceContext = null;
    }

    /**
         * If in a transaction this method will check for existence and register the object if
         * it is new. The instance of the entity provided will become managed.
         * @param entity
         * @throws IllegalArgumentException if the given Object is not an entity
         */
        public void persist(Object entity){
        try {
            verifyOpen();
            if (entity == null){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] {entity}));
            }
            try {
                getActivePersistenceContext(checkForTransaction(!isExtended())).registerNewObjectForPersist(entity, new HardIdentityHashtable());
            } catch (RuntimeException e) {
                if (ValidationException.class.isAssignableFrom(e.getClass())){
                    throw new EntityExistsException(e.getLocalizedMessage() , e);
                }
                throw e;
            }
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
    }
        
        /**
        * Merge the state of the given entity into the
        * current persistence context, using the unqualified
        * class name as the entity name.
        * @param entity
        * @return the instance that the state was merged to
        * @throws IllegalArgumentException if given Object is not an entity or is a removed entity
        */
        protected Object mergeInternal(Object entity){
        if (entity == null){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] {entity}));
        }
        //gf830 - merging a removed entity should throw exception
        if (getActivePersistenceContext(checkForTransaction(!isExtended())).getDeletedObjects().contains(entity)){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_merge_removed_entity", new Object[]{entity}));
        }
        try {
                return getActivePersistenceContext(checkForTransaction(!isExtended())).mergeCloneWithReferences(entity, MergeManager.CASCADE_BY_MAPPING, true);
        } catch (oracle.toplink.essentials.exceptions.OptimisticLockException ole) {
            throw new javax.persistence.OptimisticLockException(ole);
        }
        }
        
        /**
        * Remove the instance.
        * @param entity
        * @throws IllegalArgumentException if Object passed in is not an entity
        */
        public void remove(Object entity){
        try {
            verifyOpen();
                        if (entity == null){ //gf732 - check for null
                    throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] {entity}));
            }
            try{
                getActivePersistenceContext(checkForTransaction(!isExtended())).performRemove(entity, new HardIdentityHashtable());
            }catch (RuntimeException e){
                throw e;
            }
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
        }
        
        /**
        * Find by primary key.
        * @param entityName
        * @param primaryKey
        * @return the found entity instance
        * @throws IllegalArgumentException if the first argument does not indicate an entity or if the
        * second argument is not a valid type for that entity's primaryKey
        */
        public Object find(String entityName, Object primaryKey){
        try {
            verifyOpen();
            Session session = getActiveSession();
            ClassDescriptor descriptor = session.getDescriptorForAlias(entityName);
            if (descriptor == null || descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_entitybean_name", new Object[] {entityName}));
            }
            if (primaryKey == null){ //gf721 - check for null PK
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_pk"));
            }
            if ( ((CMP3Policy)descriptor.getCMPPolicy()).getPKClass() != null && !((CMP3Policy)descriptor.getCMPPolicy()).getPKClass().isAssignableFrom(primaryKey.getClass())){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_pk_class", new Object[] {((CMP3Policy)descriptor.getCMPPolicy()).getPKClass(), primaryKey.getClass()}));
            }
            return findInternal(descriptor, session, primaryKey);
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
        }
        
        /**
        * Find by primary key.
        * @param entityClass
        * @param primaryKey
        * @return the found entity instance or null
        * if the entity does not exist
        * @throws IllegalArgumentException if the first argument does
        * not denote an entity type or the second argument is not a valid type for that
        * entity's primary key
        */
        protected Object findInternal(Class entityClass, Object primaryKey) {
            Session session = getActiveSession();
            ClassDescriptor descriptor = session.getDescriptor(entityClass);
            if (descriptor == null || descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("unknown_bean_class", new Object[]{ entityClass}));
            }
            if (primaryKey == null){ //gf721 - check for null PK
                    throw new IllegalArgumentException(ExceptionLocalization.buildMessage("null_pk"));
            }
        if ( ((CMP3Policy)descriptor.getCMPPolicy()).getPKClass() != null && !((CMP3Policy)descriptor.getCMPPolicy()).getPKClass().isAssignableFrom(primaryKey.getClass())){
            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("invalid_pk_class", new Object[] {((CMP3Policy)descriptor.getCMPPolicy()).getPKClass(), primaryKey.getClass()}));
        }
        return findInternal(descriptor, session, primaryKey);
        }
        
        /**
        * Find by primary key.
        * @param descriptor
        * @param session
        * @param primaryKey
        * @return the found entity instance or null
        * if the entity does not exist
        * @throws IllegalArgumentException if the first argument does
        * not denote an entity type or the second argument is not a valid type for that
        * entity's primary key
        */
        protected static Object findInternal(ClassDescriptor descriptor, Session session, Object primaryKey){
        Vector pk;
        if(primaryKey instanceof Vector) {
            pk = (Vector)primaryKey;
        } else {
            pk = ((CMP3Policy) descriptor.getCMPPolicy()).createPkVectorFromKey(primaryKey, (oracle.toplink.essentials.internal.sessions.AbstractSession)session);
        }
            ReadObjectQuery query = new ReadObjectQuery(descriptor.getJavaClass());
            query.setSelectionKey(pk);
        query.conformResultsInUnitOfWork();
            return session.executeQuery(query);
        }
        
        /**
        * Synchronize the persistence context with the
        * underlying database.
        */
        public void flush(){
        try {
            verifyOpen();
        
            try {
                getActivePersistenceContext(checkForTransaction(true)).writeChanges();
            }catch (RuntimeException e) {
                if (TopLinkException.class.isAssignableFrom(e.getClass())){
                    throw new PersistenceException(e);
                }
                throw e;
            }
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
        }

    protected void detectTransactionWrapper(){
        if (this.serverSession.hasExternalTransactionController()){
            setJTATransactionWrapper();
        } else {
            setEntityTransactionWrapper();
        }
     }

        /**
        * Refresh the state of the instance from the
        * database.
        * @param entity
        */
        public void refresh(Object entity){
        try {
            verifyOpen();
            UnitOfWork uow = getActivePersistenceContext(checkForTransaction(!isExtended()));
            if(!contains(entity, uow)) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cant_refresh_not_managed_object", new Object[]{entity}));
            }
            ReadObjectQuery query = new ReadObjectQuery();
            query.setSelectionObject(entity);
            query.refreshIdentityMapResult();
            query.cascadeByMapping();
            query.setLockMode(ObjectBuildingQuery.NO_LOCK);
            Object refreshedEntity = null;
            refreshedEntity = uow.executeQuery(query);
            if(refreshedEntity == null) {
                //bug3323, should also invalidate the shared cached object if object not exists in DB.
                uow.getParent().getIdentityMapAccessor().invalidateObject(entity);
                throw new EntityNotFoundException(ExceptionLocalization.buildMessage("entity_no_longer_exists_in_db", new Object[]{entity}));
            }
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
        }
        
        /**
        * Check if the instance belongs to the current persistence
        * context.
        * @param entity
        * @return
        * @throws IllegalArgumentException if given Object is not an entity
        */
        public boolean contains(Object entity){
        try {
            verifyOpen();
            if (entity == null){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[] {entity}));
            }
            ClassDescriptor descriptor = (ClassDescriptor)getServerSession().getDescriptors().get(entity.getClass());
            if (descriptor == null || descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[]{entity}));
            }
            
            if( (!hasActivePersistenceContext())) {
                return false;
            }
            
            return contains(entity,getActivePersistenceContext(checkForTransaction(false)));
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
        }

    /**
    * Check if the instance belongs to the current persistence
    * context.
    * @param entity
    * @param uow
    * @return
    */
    protected boolean contains(Object entity, UnitOfWork uow){
        
        return ((oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl)uow).isObjectRegistered(entity) &&
                !((oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl)uow).isObjectDeleted(entity);
    }

    /**
         * This method returns the current session to the requestor. The current session
         * will be a the active UnitOfWork within a transaction and will be a 'scrap'
         * UnitOfWork outside of a transaction. The caller is conserned about the results
         * then the getSession() or getUnitOfWork() API should be called.
         */
    public Session getActiveSession(){
        Object txn = checkForTransaction(false);
        if ( txn == null && ! this.isExtended() ){
            return this.serverSession.acquireNonSynchronizedUnitOfWork();
        }else{
            return getActivePersistenceContext(txn);
        }
        
    }
    
    /**
     * Return the underlying provider object for the EntityManager,
     * if available. The result of this method is implementation
     * specific.
     */
    public Object getDelegate(){
        try {
            verifyOpen();
            return this;
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
    }

    /**
     * The method search for user defined property passed in from EntityManager, if it is not found then
     * search for it from EntityManagerFactory properties.
     * @param name
     * @return
     */
    public Object getProperty(String name) {
        Object propertyValue=null;
        if(name==null){
            return null;
        }
        if(this.properties!=null){
            propertyValue=this.properties.get(name);
        }
        if(propertyValue==null){
            propertyValue=this.factory.getProperty(name);
        }
        return propertyValue;
    }

    /**
     * This method will return the active UnitOfWork
     */
    public UnitOfWork getUnitOfWork(){
        return getActivePersistenceContext(checkForTransaction(false));
    }
    
    /**
     * This method will return a Session outside of a transaction and null within a transaction.
     */
    public Session getSession(){
        if (checkForTransaction(false) == null){
            return this.serverSession.acquireNonSynchronizedUnitOfWork();
        }
        return null;
    }
    
    /**
     * Return the underlying server session
     */
    public ServerSession getServerSession(){
        return this.serverSession;
    }
    /**
     * This method is used to create a query using SQL. The class, must be the expected
     * return type.
     */
    protected DatabaseQuery createNativeQueryInternal(String sqlString, Class resultType){
        ReadAllQuery query = new ReadAllQuery(resultType);
        query.setSQLString(sqlString);
        query.setIsUserDefined(true);
        return query;
    }
     
    /**
     * This method is used to create a query using a Toplink Expression and the return type.
     */
    protected DatabaseQuery createQueryInternal(Expression expression, Class resultType){
        ReadAllQuery query = new ReadAllQuery(resultType);
        query.setSelectionCriteria(expression);
        return query;
    }
    

           /**
         * <p>Closes this EntityManager.
     *
         * <p>After invoking this method, all methods on the instance will throw an
         * {_at_link IllegalStateException} except for {_at_link #isOpen}, which will return
         * <code>false</code> .</p>
         *
         * <p>This should be called when a method is finished with the EntityManager in a
         * bean-managed transaction environment or when executed outside a container. Closing
         * of the EntityManager is handled by the container when using container-managed
         * transactions.</p>
         */
        public void close(){
        try {
            verifyOpen();
            isOpen = false;
            factory = null;
            serverSession = null;
            if(extendedPersistenceContext != null) {
                if (checkForTransaction(false) == null){
                    // clear change sets but keep the cache
                    extendedPersistenceContext.clearForClose(false);
                } else {
                    // when commit will be called, all change sets will be cleared, but the cache will be kept
                    extendedPersistenceContext.setShouldClearForCloseInsteadOfResume(true);
                }
                extendedPersistenceContext = null;
            }
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
    }


    /**
     * Indicates if this EntityManager is an extended Persistence Context
     */
    public boolean isExtended(){
        return this.extended;
    }
    
    /**
         * Indicates whether or not this entity manager is open. Returns <code>true</code> until
         * a call to {_at_link #close} is made.
         */
        public boolean isOpen(){
        return isOpen && factory.isOpen();
    }
 
    /**
    * Set the lock mode for an entity object contained in the persistence context.
    * @param entity
    * @param lockMode
    * @throws PersistenceException if an unsupported lock call is made
    * @throws IllegalArgumentException if the instance is not an entity or is a detached entity
    * @throws TransactionRequiredException if there is no transaction
    */
    public void lock(Object entity, LockModeType lockMode){
        try {
            verifyOpen();
            RepeatableWriteUnitOfWork context = getActivePersistenceContext(checkForTransaction(!isExtended()));
            ClassDescriptor descriptor = context.getDescriptor(entity);
            OptimisticLockingPolicy lockingPolicy = descriptor.getOptimisticLockingPolicy();
            if ((lockingPolicy == null) || !(lockingPolicy instanceof VersionLockingPolicy)){
                throw new PersistenceException(ExceptionLocalization.buildMessage("ejb30-wrong-lock_called_without_version_locking-index", null));
            }
            context.forceUpdateToVersionField(entity, (lockMode == LockModeType.WRITE));
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
    }

    public void verifyOpen(){
        if (!isOpen()){
            throw new IllegalStateException(ExceptionLocalization.buildMessage("operation_on_closed_entity_manager"));
        }
    }
    
    public RepeatableWriteUnitOfWork getActivePersistenceContext(Object txn) {
        if (this.isExtended()){
            // use local uow as it will be local to this EM and not on the txn
            if (this.extendedPersistenceContext == null || !this.extendedPersistenceContext.isActive()){
                this.extendedPersistenceContext = new RepeatableWriteUnitOfWork(this.serverSession.acquireClientSession());
                this.extendedPersistenceContext.setResumeUnitOfWorkOnTransactionCompletion(true);
                this.extendedPersistenceContext.setShouldCascadeCloneToJoinedRelationship(true);
                this.extendedPersistenceContext.setProperties(properties);
                if (txn != null) {
                    // if there is an active txn we must register with it on creation of PC
                    transaction.registerUnitOfWorkWithTxn(this.extendedPersistenceContext);
                }
            }
            //gf3334, force persistencecontext begin early transaction if conditions meet.
            if (this.beginEarlyTransaction && txn != null && !this.extendedPersistenceContext.isInTransaction() ) {
                this.extendedPersistenceContext.beginEarlyTransaction();
            }
            return this.extendedPersistenceContext;
        }else{
            return getTransactionalUnitOfWork_new(txn);
        }
    }
    
    /*
     * This method is used in contains to check if we already have a persistence context.
     * If there is no active persistence context the method returns false
     */
    private boolean hasActivePersistenceContext()
    {
        if (isExtended() && (this.extendedPersistenceContext == null || !this.extendedPersistenceContext.isActive()))
            return false;
        else
            return true;
    }
    
    protected RepeatableWriteUnitOfWork getTransactionalUnitOfWork_new(Object tnx) {
        return transaction.getTransactionalUnitOfWork(tnx);
    }
    
    protected Object checkForTransaction(boolean validateExistence) {
        return transaction.checkForTransaction(validateExistence);
    }
    
    public boolean shouldFlushBeforeQuery(){
        Object foundTransaction = checkForTransaction(false);
        if ((foundTransaction!=null) && transaction.shouldFlushBeforeQuery(getActivePersistenceContext(foundTransaction))){
            return true;
        }
        return false;
    }
    
    //This is used to determine if the Persistence Contexts (in the form of UOWs)
    //should be propagated or not, which effectively means we will use the
    //active unit of work
    public boolean shouldPropagatePersistenceContext(){
        return this.propagatePersistenceContext;
    }

    //Indicate the early transaction should be forced to start.
    public boolean shouldBeginEarlyTransaction(){
        return this.beginEarlyTransaction;
    }

    
    /**
    * Indicate to the EntityManager that a JTA transaction is
    * active. This method should be called on a JTA application
    * managed EntityManager that was created outside the scope
    * of the active transaction to associate it with the current
    * JTA transaction.
    * @throws TransactionRequiredException if there is
    * no transaction.
    */
    public void joinTransaction(){
        try {
            verifyOpen();
            transaction.registerUnitOfWorkWithTxn(getActivePersistenceContext(checkForTransaction(true)));
        } catch (RuntimeException e) {
            this.setRollbackOnly();
            throw e;
        }
    }

    /**
    * Internal method. Sets transaction to rollback only.
    */
    protected void setRollbackOnly() {
        this.transaction.setRollbackOnlyInternal();
    }

    /**
     * Internal method, set begin early transaction if property has been specified.
     */
    private void setBeginEarlyTransaction(){
        String beginEarlyTransactionProperty = (String)getProperty(TopLinkProperties.JOIN_EXISTING_TRANSACTION);
        if(beginEarlyTransactionProperty!=null){
            this.beginEarlyTransaction="true".equalsIgnoreCase(beginEarlyTransactionProperty);
        }
    }
    
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package oracle.toplink.essentials.expressions;

import java.util.*;
import java.io.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.internal.localization.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p>
 * <b>Purpose</b>: Define an object-level representation of a database query where clause.</p>
 * <p>
 * <b>Description</b>: An expression is a tree-like structure that defines the selection
 * criteria for a query against objects in the database. The expression has the advantage
 * over SQL by being at the object-level, i.e. the object model attributes and relationships
 * can be used to be query on instead of the database field names.
 * Because it is an object, not a string the expression has the advantage that is can be
 * easily manipulated through code to easily build complex selection criterias.</p>
 * <p>
 * <b>Responsibilities</b>:
 * <ul>
 * <li> Store the selection criteria in a tree-like structure.
 * <li> Support public manipulation protocols for all comparison and function opperators.
 * <li> Use opperator overloading to support all primitive types as well as objects.
 * </ul></p>
 */
public abstract class Expression implements Serializable, Cloneable {

    /** Required for serialization compatibility. */
    static final long serialVersionUID = -5979150600092006081L;

    /** Temporary values for table aliasing */
    protected transient DatabaseTable lastTable;
    protected transient DatabaseTable currentAlias;
    protected boolean selectIfOrderedBy = true;

    /**
     * Base Expression Constructor. Not generally used by Developers
     */
    public Expression() {
        super();
    }

    /**
     * PUBLIC:
     * Function, return an expression that adds to a date based on
     * the specified datePart. This is eqivalent to the Sybase DATEADD funtion.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").addDate("year", 2)
     * Java: NA
     * SQL: DATEADD(date, 2, year)
     * </pre></blockquote>
     */
    public Expression addDate(String datePart, int numberToAdd) {
        return addDate(datePart, new Integer(numberToAdd));
    }

    /**
     * PUBLIC:
     * Function, return an expression that adds to a date based on
     * the specified datePart. This is eqivalent to the Sybase DATEADD funtion.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").addDate("year", 2)
     * Java: NA
     * SQL: DATEADD(date, 2, year)
     * </pre></blockquote>
     */
    public Expression addDate(String datePart, Object numberToAdd) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.AddDate);
        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(this);
        expression.addChild(Expression.fromLiteral(datePart, this));
        expression.addChild(Expression.from(numberToAdd, this));
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * PUBLIC:
     * Function, to add months to a date.
     */
    public Expression addMonths(int months) {
        return addMonths(new Integer(months));
    }

    /**
     * PUBLIC:
     * Function, to add months to a date.
     */
    public Expression addMonths(Object months) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.AddMonths);
        return anOperator.expressionFor(this, months);
    }

    /**
     * INTERNAL:
     * Find the alias for a given table
     */
    public DatabaseTable aliasForTable(DatabaseTable table) {
        return null;
    }

    /**
     * PUBLIC: Returns an expression equivalent to all of <code>attributeName</code>
     * holding true for <code>criteria</code>.
     * <p>
     * For every expression with an anyOf, its negation has either an allOf or a
     * noneOf. The following two examples will illustrate as the second is the
     * negation of the first:
     * <p>
     * AnyOf Example: Employees with a non '613' area code phone number.
     * <pre><blockquote>
     * ReadAllQuery query = new ReadAllQuery(Employee.class);
     * ExpressionBuilder employee = new ExpressionBuilder();
     * Expression exp = employee.anyOf("phoneNumbers").get("areaCode").notEqual("613");
     * </blockquote></pre>
     * <p>
     * AllOf Example: Employees with all '613' area code phone numbers.
     * <pre><blockquote>
     * ExpressionBuilder employee = new ExpressionBuilder();
     * ExpressionBuilder phones = new ExpressionBuilder();
     * Expression exp = employee.allOf("phoneNumbers", phones.get("areaCode").equal("613"));
     * SQL:
     * SELECT ... EMPLOYEE t0 WHERE NOT EXISTS (SELECT ... PHONE t1 WHERE
     * (t0.EMP_ID = t1.EMP_ID) AND NOT (t1.AREACODE = '613'))
     * </blockquote></pre>
     * <p>
     * allOf is the universal counterpart to the existential anyOf. To have the
     * condition evaluated for each instance it must be put inside of a subquery,
     * which can be expressed as not exists (any of attributeName some condition).
     * (All x such that y = !Exist x such that !y).
     * <p>Likewise the syntax employee.allOf("phoneNumbers").get("areaCode").equal("613")
     * is not supported for the <code>equal</code> must go inside a subQuery.
     * <p>
     * This method saves you from writing the sub query yourself. The above is
     * equivalent to the following expression:
     * <pre><blockquote>
     * ExpressionBuilder employee = new ExpressionBuilder();
     * ExpressionBuilder phone = new ExpressionBuilder();
     * ReportQuery subQuery = new ReportQuery(Phone.class, phone);
     * subQuery.retreivePrimaryKeys();
     * subQuery.setSelectionCriteria(phone.equal(employee.anyOf("phoneNumbers").and(
     * phone.get("areaCode").notEqual("613")));
     * Expression exp = employee.notExists(subQuery);
     * </blockquote></pre>
     * <p>
     * Note if employee has no phone numbers allOf ~ noneOf.
     * @param criteria must have its own builder, as it will become the
     * seperate selection criteria of a subQuery.
     * @return a notExists subQuery expression
     */
    public Expression allOf(String attributeName, Expression criteria) {
        ReportQuery subQuery = new ReportQuery();
        subQuery.setShouldRetrieveFirstPrimaryKey(true);
        Expression builder = criteria.getBuilder();
        criteria = builder.equal(anyOf(attributeName)).and(criteria.not());
        subQuery.setSelectionCriteria(criteria);
        return notExists(subQuery);
    }

    /**
     * PUBLIC:
     * Return an expression that is the boolean logical combination of both expressions.
     * This is equivalent to the SQL "AND" operator and the Java "&&" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").equal("Bob").and(employee.get("lastName").equal("Smith"))
     * Java: (employee.getFirstName().equals("Bob")) && (employee.getLastName().equals("Smith"))
     * SQL: F_NAME = 'Bob' AND L_NAME = 'Smith'
     * </blockquote></pre>
     */
    public Expression and(Expression theExpression) {
        // Allow ands with null.
        if (theExpression == null) {
            return this;
        }

        ExpressionBuilder base = getBuilder();
        Expression expressionToUse = theExpression;

        // Ensure the same builder, unless a parralel query is used.
        // For flashback added an extra condition: if left side is a 'NullExpression'
        // then rebuild on it regardless.
        if ((theExpression.getBuilder() != base) && ((base == this) || (theExpression.getBuilder().getQueryClass() == null))) {
            expressionToUse = theExpression.rebuildOn(base);
        }

        if (base == this) {// Allow and to be sent to the builder.
            return expressionToUse;
        }

        ExpressionOperator anOperator = getOperator(ExpressionOperator.And);
        return anOperator.expressionFor(this, expressionToUse);
    }

    /**
     * PUBLIC:
     * Return an expression representing traversal of a 1:many or many:many relationship.
     * This allows you to query whether any of the "many" side of the relationship satisfies the remaining criteria.
     * <p>Example:
     * <p>
     * <table border=0 summary="This table compares an example TopLink anyOf Expression to Java and SQL">
     * <tr>
     * <th id="c1">Format</th>
     * <th id="c2">Equivalent</th>
     * </tr>
     * <tr>
     * <td headers="c1">TopLink</td>
     * <td headers="c2">
     * <code>
     * ReadAllQuery query = new ReadAllQuery(Employee.class);</br>
     * ExpressionBuilder builder = new ExpressionBuilder();</br>
     * Expression exp = builder.get("id").equal("14858");</br>
     * exp = exp.or(builder.anyOf("managedEmployees").get("firstName").equal("Bob"));</br>
     * </code>
     * </td>
     * </tr>
     * <tr>
     * <td headers="c1">Java</td>
     * <td headers="c2">No direct equivalent</td>
     * </tr>
     * <tr>
     * <td headers="c1">SQL</td>
     * <td headers="c2">SELECT DISTINCT ... WHERE (t2.MGR_ID (+) = t1.ID) AND (t2.F_NAME = 'Bob')</td>
     * </tr>
     * </table>
     */
    public Expression anyOf(String attributeName) {
        QueryKeyExpression queryKey = (QueryKeyExpression)get(attributeName);

        queryKey.doQueryToManyRelationship();
        return queryKey;

    }

    /**
     * ADVANCED:
     * Return an expression representing traversal of a 1:many or many:many relationship.
     * This allows you to query whether any of the "many" side of the relationship satisfies the remaining criteria.
     * This version of the anyOf operation performs an outer join.
     * Outer joins allow the join to performed even if the target of the relationship is empty.
     * NOTE: outer joins are not supported on all database and have differing symantics.
     * <p>Example:
     * <p>
     * <table border=0 summary="This table compares an example TopLink anyOfAllowingNone Expression to Java and SQL">
     * <tr>
     * <th id="c1">Format</th>
     * <th id="c2">Equivalent</th>
     * </tr>
     * <tr>
     * <td headers="c1">TopLink</td>
     * <td headers="c2">
     * <code>
     * ReadAllQuery query = new ReadAllQuery(Employee.class);</br>
     * ExpressionBuilder builder = new ExpressionBuilder();</br>
     * Expression exp = builder.get("id").equal("14858");</br>
     * exp = exp.or(builder.anyOfAllowingNone("managedEmployees").get("firstName").equal("Bob"));</br>
     * </code>
     * </td>
     * </tr>
     * <tr>
     * <td headers="c1">Java</td>
     * <td headers="c2">No direct equivalent</td>
     * </tr>
     * <tr>
     * <td headers="c1">SQL</td>
     * <td headers="c2">SELECT DISTINCT ... WHERE (t2.MGR_ID (+) = t1.ID) AND (t2.F_NAME = 'Bob')</td>
     * </tr>
     * </table>
     */
    public Expression anyOfAllowingNone(String attributeName) {
        QueryKeyExpression queryKey = (QueryKeyExpression)getAllowingNull(attributeName);

        queryKey.doQueryToManyRelationship();
        return queryKey;

    }

    /**
     * PUBLIC:
     * This can only be used within an ordering expression.
     * It will order the result ascending.
     * Example:
     * <pre><blockquote>
     * readAllQuery.addOrderBy(expBuilder.get("address").get("city").ascending())
     * </blockquote></pre>
     */
    public Expression ascending() {
        return getFunction(ExpressionOperator.Ascending);
    }

    /**
     * PUBLIC:
     * Function, returns the single character strings ascii value.
     */
    public Expression asciiValue() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Ascii);
        return anOperator.expressionFor(this);
    }

    /**
     * INTERNAL:
     * Alias a particular table within this node
     */
    protected void assignAlias(String name, DatabaseTable tableOrExpression) {
        // By default, do nothing.
    }

    /**
     * INTERNAL:
     * Assign aliases to any tables which I own. Start with t<initialValue>,
     * and return the new value of the counter , i.e. if initialValue is one
     * and I have tables ADDRESS and EMPLOYEE I will assign them t1 and t2 respectively, and return 3.
     */
    public int assignTableAliasesStartingAt(int initialValue) {
        if (hasBeenAliased()) {
            return initialValue;
        }
        int counter = initialValue;
        Vector ownedTables = getOwnedTables();
        if (ownedTables != null) {
            for (Enumeration e = ownedTables.elements(); e.hasMoreElements();) {
                assignAlias("t" + counter, (DatabaseTable)e.nextElement());
                counter++;
            }
        }
        return counter;
    }

    /**
     * PUBLIC:
     * Function, This represents the aggregate function Average. Can be used only within Report Queries.
     */
    public Expression average() {
        return getFunction(ExpressionOperator.Average);
    }

    /**
     * PUBLIC:
     * Function, between two bytes
     */
    public Expression between(byte leftValue, byte rightValue) {
        return between(new Byte(leftValue), new Byte(rightValue));
    }

    /**
     * PUBLIC:
     * Function, between two chars
     */
    public Expression between(char leftChar, char rightChar) {
        return between(new Character(leftChar), new Character(rightChar));
    }

    /**
     * PUBLIC:
     * Function, between two doubles
     */
    public Expression between(double leftValue, double rightValue) {
        return between(new Double(leftValue), new Double(rightValue));
    }

    /**
     * PUBLIC:
     * Function, between two floats
     */
    public Expression between(float leftValue, float rightValue) {
        return between(new Float(leftValue), new Float(rightValue));
    }

    /**
     * PUBLIC:
     * Function, between two ints
     */
    public Expression between(int leftValue, int rightValue) {
        return between(new Integer(leftValue), new Integer(rightValue));
    }

    /**
     * PUBLIC:
     * Function, between two longs
     */
    public Expression between(long leftValue, long rightValue) {
        return between(new Long(leftValue), new Long(rightValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receiver's value is between two other values.
     * This means the receiver's value is greater than or equal to the leftValue argument and less than or equal to the
     * rightValue argument.
     * <p>
     * This is equivalent to the SQL "BETWEEN AND" operator and Java ">=", "<=;" operators.
     * <p>Example:
     * <pre>
     * TopLink: employee.get("age").between(19,50)
     * Java: (employee.getAge() >= 19) && (employee.getAge() <= 50)
     * SQL: AGE BETWEEN 19 AND 50
     * </pre>
     */
    public Expression between(Object leftValue, Object rightValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Between);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        args.addElement(leftValue);
        args.addElement(rightValue);
        return anOperator.expressionForArguments(this, args);
    }

    public Expression between(Expression leftExpression, Expression rightExpression) {
        return between((Object)leftExpression, (Object)rightExpression);

    }

    /**
     * PUBLIC:
     * Function, between two shorts
     */
    public Expression between(short leftValue, short rightValue) {
        return between(new Short(leftValue), new Short(rightValue));
    }

    /**
     * PUBLIC:
     * Function Convert values returned by the query to values
     * given in the caseItems hashtable. The equivalent of
     * the Oracle CASE function
     * <p>Example:
     * <pre><blockquote>
     * Hashtable caseTable = new Hashtable();
     * caseTable.put("Robert", "Bob");
     * caseTable.put("Susan", "Sue");
     *
     * TopLink: employee.get("name").caseStatement(caseTable, "No-Nickname")
     * Java: NA
     * SQL: CASE name WHEN "Robert" THEN "Bob"
     * WHEN "Susan" THEN "Sue"
     * ELSE "No-Nickname"
     * </pre></blockquote>
     * @param caseItems java.util.Hashtable
     * a hashtable containing the items to be processed. Keys represent
     * the items to match coming from the query. Values represent what
     * a key will be changed to.
     * @param defaultItem java.lang.String the default value that will be used if none of the keys in the
     * hashtable match
     **/
    public Expression caseStatement(Hashtable caseItems, String defaultItem) {

        /**
         * case (like decode) works differently than most of the functionality in the expression
         * framework. It takes a variable number of arguments and as a result, the printed strings
         * for a case call have to be built when the number of arguments are known.
         * As a result, we do not look up case in the ExpressionOperator. Instead we build
         * the whole operator here. The side effect of this is that case will not throw
         * an invalid operator exception for any platform. (Even the ones that do not support
         * case)
         */
        ExpressionOperator anOperator = new ExpressionOperator();
        anOperator.setSelector(ExpressionOperator.Case);
        anOperator.setNodeClass(FunctionExpression.class);
        anOperator.setType(ExpressionOperator.FunctionOperator);
        anOperator.bePrefix();

        Vector v = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(caseItems.size() + 1);
        v.addElement("CASE ");

        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(this);
        Enumeration enumeration = caseItems.keys();
        while (enumeration.hasMoreElements()) {
            Object key = enumeration.nextElement();
            expression.addChild(Expression.from(key, this));
            expression.addChild(Expression.from(caseItems.get(key), this));
            v.addElement(" WHEN ");
            v.addElement(" THEN ");
        }
        ;
        v.addElement(" ELSE ");
        expression.addChild(Expression.from(defaultItem, this));
        v.addElement(" END");
        anOperator.printsAs(v);
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * INTERNAL:
     * Clone the expression maintaining clone identity in the inter-connected expression graph.
     */
    public Object clone() {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        Dictionary alreadyDone = new HardIdentityHashtable();
        return copiedVersionFrom(alreadyDone);
    }

    /**
     * INTERNAL:
     * This expression is built on a different base than the one we want. Rebuild it and
     * return the root of the new tree.
     * This method will rebuildOn the receiver even it is a parallel select or a
     * sub select: it will not replace every base with newBase.
     * Also it will rebuild using anyOf as appropriate not get.
     * @see oracle.toplink.essentials.mappings.ForeignReferenceMapping#batchedValueFromRow
     * @see #rebuildOn(Expression)
     * @bug 2637484 INVALID QUERY KEY EXCEPTION THROWN USING BATCH READS AND PARALLEL EXPRESSIONS
     * @bug 2612567 CR4298- NULLPOINTEREXCEPTION WHEN USING SUBQUERY AND BATCH READING IN 4.6
     * @bug 2612140 CR2973- BATCHATTRIBUTE QUERIES WILL FAIL WHEN THE INITIAL QUERY HAS A SUBQUERY
     * @bug 2720149 INVALID SQL WHEN USING BATCH READS AND MULTIPLE ANYOFS
     */
    public Expression cloneUsing(Expression newBase) {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        Dictionary alreadyDone = new HardIdentityHashtable();

        // cloneUsing is identical to cloning save that the primary builder
        // will be replaced not with its clone but with newBase.
        // ExpressionBuilder.registerIn() will check for this newBase with
        // alreadyDone.get(alreadyDone);
        // copiedVersionFrom() must be called on the primary builder before
        // other builders.
        alreadyDone.put(alreadyDone, newBase);
        return copiedVersionFrom(alreadyDone);
    }

    /**
     * PUBLIC:
     * Function, returns the concatenation of the two string values.
     */
    public Expression concat(Object left) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Concat);
        return anOperator.expressionFor(this, left);
    }

    /**
     * PUBLIC:
     * Return an expression that performs a key word search.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: project.get("description").containsAllKeyWords("TopLink rdbms java")
     * </blockquote></pre>
     */
    public Expression containsAllKeyWords(String spaceSeperatedKeyWords) {
        StringTokenizer tokenizer = new StringTokenizer(spaceSeperatedKeyWords);
        Expression expression = null;
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (expression == null) {
                expression = containsSubstringIgnoringCase(token);
            } else {
                expression = expression.and(containsSubstringIgnoringCase(token));
            }
        }

        if (expression == null) {
            return like("%");
        } else {
            return expression;
        }
    }

    /**
     * PUBLIC:
     * Return an expression that performs a key word search.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: project.get("description").containsAllKeyWords("TopLink rdbms java")
     * </blockquote></pre>
     */
    public Expression containsAnyKeyWords(String spaceSeperatedKeyWords) {
        StringTokenizer tokenizer = new StringTokenizer(spaceSeperatedKeyWords);
        Expression expression = null;
        while (tokenizer.hasMoreTokens()) {
            String token = tokenizer.nextToken();
            if (expression == null) {
                expression = containsSubstringIgnoringCase(token);
            } else {
                expression = expression.or(containsSubstringIgnoringCase(token));
            }
        }

        if (expression == null) {
            return like("%");
        } else {
            return expression;
        }
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value contains the substring.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").containsSubstring("Bob")
     * Java: employee.getFirstName().indexOf("Bob") != -1
     * SQL: F_NAME LIKE '%BOB%'
     * </blockquote></pre>
     */
    public Expression containsSubstring(String theValue) {
        return like("%" + theValue + "%");
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value contains the substring.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").containsSubstring("Bob")
     * Java: employee.getFirstName().indexOf("Bob") != -1
     * SQL: F_NAME LIKE '%BOB%'
     * </blockquote></pre>
     */
    public Expression containsSubstring(Expression expression) {
        return like((value("%").concat(expression)).concat("%"));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value contains the substring, ignoring case.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").containsSubstringIgnoringCase("Bob")
     * Java: employee.getFirstName().toUpperCase().indexOf("BOB") != -1
     * SQL: F_NAME LIKE '%BOB%'
     * </blockquote></pre>
     */
    public Expression containsSubstringIgnoringCase(String theValue) {
        return toUpperCase().containsSubstring(theValue.toUpperCase());
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value contains the substring, ignoring case.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").containsSubstringIgnoringCase("Bob")
     * Java: employee.getFirstName().toUpperCase().indexOf("BOB") != -1
     * SQL: F_NAME LIKE '%BOB%'
     * </blockquote></pre>
     */
    public Expression containsSubstringIgnoringCase(Expression expression) {
        return toUpperCase().containsSubstring(expression.toUpperCase());
    }

    /*
     * Modify this individual expression node to use outer joins wherever there are
     * equality operations between two field nodes.
     */
    protected void convertNodeToUseOuterJoin() {
    }

    /**
     * INTERNAL:
     * Modify this expression to use outer joins wherever there are
     * equality operations between two field nodes.
     */
    public Expression convertToUseOuterJoin() {
        ExpressionIterator iterator = new ExpressionIterator() {
            public void iterate(Expression each) {
                each.convertNodeToUseOuterJoin();
            }
        };
        iterator.iterateOn(this);
        return this;
    }

    /**
     * INTERNAL:
     */
    public Expression copiedVersionFrom(Dictionary alreadyDone) {
        Expression existing = (Expression)alreadyDone.get(this);
        if (existing == null) {
            return registerIn(alreadyDone);
        } else {
            return existing;
        }
    }

    /**
     * PUBLIC:
     * This represents the aggregate function Average. Can be used only within Report Queries.
     */
    public Expression count() {
        return getFunction(ExpressionOperator.Count);
    }

    /**
     * INTERNAL:
     */
    public Expression create(Expression base, Object singleArgument, ExpressionOperator anOperator) {
        // This is a replacement for real class methods in Java. Instead of returning a new instance we create it, then
        // mutate it using this method.
        return this;
    }

    /**
     * INTERNAL:
     */
    public Expression createWithBaseLast(Expression base, Object singleArgument, ExpressionOperator anOperator) {
        // This is a replacement for real class methods in Java. Instead of returning a new instance we create it, then
        // mutate it using this method.
        return this;
    }

    /**
     * INTERNAL:
     */
    public Expression create(Expression base, Vector arguments, ExpressionOperator anOperator) {
        // This is a replacement for real class methods in Java. Instead of returning a new instance we create it, then
        // mutate it using this method.
        return this;
    }

    /**
     * PUBLIC:
     * This gives access to the current timestamp on the database through expression.
     * Please note, this method is added for consistency and returns the same
     * result as currentDate.
     */
    public Expression currentTimeStamp() {
        return currentDate();
    }

    /**
     * PUBLIC:
     * This gives access to the current date on the database through expression.
     */
    public Expression currentDate() {
        return getFunction(ExpressionOperator.Today);
    }

   /**
    * PUBLIC:
    * This gives access to the current date only on the database through expression.
    * Note the difference between currentDate() and this method. This method does
    * not return the time portion of current date where as currentDate() does.
    */
   public Expression currentDateDate() {
       return getFunction(ExpressionOperator.CurrentDate);
   }

    /**
     * PUBLIC:
     * This gives access to the current time only on the database through expression.
     * Note the difference between currentDate() and this method. This method does
     * not return the date portion where as currentDate() does.
     */
    public Expression currentTime() {
        return getFunction(ExpressionOperator.CurrentTime);
    }
    
    /**
     * PUBLIC:
     * Function, Return the difference between the queried part of a date(i.e. years, days etc.)
     * and same part of the given date. The equivalent of the Sybase function DateDiff
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").dateDifference("year", new Date(System.currentTimeMillis()))
     * Java: NA
     * SQL: DATEADD(date, 2, GETDATE)
     * </pre></blockquote> *
     */
    public Expression dateDifference(String datePart, java.util.Date date) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.DateDifference);
        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(Expression.fromLiteral(datePart, this));
        expression.addChild(Expression.from(date, this));
        expression.addChild(this);
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * PUBLIC:
     * Function, Return the difference between the queried part of a date(i.e. years, days etc.)
     * and same part of the given date. The equivalent of the Sybase function DateDiff
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").dateDifference("year", new Date(System.currentTimeMillis()))
     * Java: NA
     * SQL: DATEADD(date, 2, GETDATE)
     * </pre></blockquote> *
     */
    public Expression dateDifference(String datePart, Expression comparisonExpression) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.DateDifference);
        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(Expression.fromLiteral(datePart, this));
        expression.addChild(comparisonExpression);
        expression.addChild(this);
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * PUBLIC:
     * return a string that represents the given part of a date. The equivalent
     * of the Sybase DATENAME function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").dateName("year")
     * Java: new String(date.getYear())
     * SQL: DATENAME(date, year)
     * </pre></blockquote> *
     */
    public Expression dateName(String datePart) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.DateName);
        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(Expression.fromLiteral(datePart, this));
        expression.addChild(this);
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * PUBLIC:
     * Function return an integer which represents the requested
     * part of the date. Equivalent of the Sybase function DATEPART
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").datePart("year")
     * Java: date.getYear()
     * SQL: DATEPART(date, year)
     * </pre></blockquote> * */
    public Expression datePart(String datePart) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.DatePart);
        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(Expression.fromLiteral(datePart, this));
        expression.addChild(this);
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * PUBLIC:
     * Function, returns the date converted to the string value in the default database format.
     */
    public Expression dateToString() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.DateToString);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function Convert values returned by the query to values
     * given in the decodeableItems hashtable. The equivalent of
     * the Oracle DECODE function. Note: This will only work on databases that support
     * Decode with the syntax below.
     * <p>Example:
     * <pre><blockquote>
     * Hashtable decodeTable = new Hashtable();
     * decodeTable.put("Robert", "Bob");
     * decodeTable.put("Susan", "Sue");
     *
     * TopLink: employee.get("name").Decode(decodeTable, "No-Nickname")
     * Java: NA
     * SQL: DECODE(name, "Robert", "Bob", "Susan", "Sue", "No-Nickname")
     * </pre></blockquote>
     * @param decodeableItems java.util.Hashtable
     * a hashtable containing the items to be decoded. Keys represent
     * the items to match coming from the query. Values represent what
     * a key will be changed to.
     * @param defaultItem
     * the default value that will be used if none of the keys in the
     * hashtable match
     **/
    public Expression decode(Hashtable decodeableItems, String defaultItem) {

        /**
         * decode works differently than most of the functionality in the expression framework.
         * It takes a variable number of arguments and as a result, the printed strings for
         * a decode call have to be built when the number of arguments are known.
         * As a result, we do not look up decode in the ExpressionOperator. Instead we build
         * the whole operator here. The side effect of this is that decode will not thrown
         * an invalid operator exception for any platform. (Even the ones that do not support
         * decode)
         */
        ExpressionOperator anOperator = new ExpressionOperator();
        anOperator.setSelector(ExpressionOperator.Decode);
        anOperator.setNodeClass(ClassConstants.FunctionExpression_Class);
        anOperator.setType(ExpressionOperator.FunctionOperator);
        anOperator.bePrefix();

        Vector v = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(decodeableItems.size() + 1);
        v.addElement("DECODE(");
        for (int i = 0; i < ((decodeableItems.size() * 2) + 1); i++) {
            v.addElement(", ");
        }
        ;
        v.addElement(")");
        anOperator.printsAs(v);

        FunctionExpression expression = new FunctionExpression();
        expression.setBaseExpression(this);
        expression.addChild(this);
        Enumeration enumeration = decodeableItems.keys();
        while (enumeration.hasMoreElements()) {
            Object key = enumeration.nextElement();
            expression.addChild(Expression.from(key, this));
            expression.addChild(Expression.from(decodeableItems.get(key), this));
        }
        ;
        expression.addChild(Expression.from(defaultItem, this));
        expression.setOperator(anOperator);
        return expression;
    }

    /**
     * PUBLIC:
     * This can only be used within an ordering expression.
     * It will order the result descending.
     * <p>Example:
     * <pre><blockquote>
     * readAllQuery.addOrderBy(expBuilder.get("address").get("city").descending())
     * </blockquote></pre>
     */
    public Expression descending() {
        return getFunction(ExpressionOperator.Descending);
    }

    /**
     * INTERNAL:
     * Used in debug printing of this node.
     */
    public String descriptionOfNodeType() {
        return "Expression";
    }


    /**
     * INTERNAL:
     * Check if any element in theObjects is Expression.
     */
    public boolean detectExpression(Vector theObjects) {
        boolean foundExpression = false;
        int size = theObjects.size();
        for (int i = 0; i < size; i++) {
            Object element = theObjects.get(i);
            if (element instanceof Expression) {
                foundExpression = true;
                break;
            }
        }
        return foundExpression;
    }
    
    /**
     * PUBLIC:
     * Function return a value which indicates how much difference there is
     * between two expressions. Equivalent of the Sybase DIFFERENCE function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").difference("Frank")
     * SQL: DIFFERENCE(name, 'Frank')
     * </pre></blockquote> * */
    public Expression difference(String expression) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Difference);
        return anOperator.expressionFor(this, expression);
    }

    /**
     * PUBLIC:
     * Function, This represents the distinct option inside an aggregate function. Can be used only within Report Queries.
     */
    public Expression distinct() {
        return getFunction(ExpressionOperator.Distinct);
    }

    /**
     * INTERNAL:
     * Check if the object conforms to the expression in memory.
     * This is used for in-memory querying.
     * By default throw an exception as all valid root expressions must override.
     * If the expression in not able to determine if the object conform throw a not supported exception.
     */
    public boolean doesConform(Object object, AbstractSession session, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy) throws QueryException {
        return doesConform(object, session, translationRow, valueHolderPolicy, false);
    }

    /**
     * INTERNAL:
     * New parameter added to doesConform for feature 2612601
     * @param isObjectRegistered true if object possibly not a clone, but is being
     * conformed against the unit of work cache; if object is not in the UOW cache
     * but some of its attributes are, use the registered versions of
     * object's attributes for the purposes of this method.
     */
    public boolean doesConform(Object object, AbstractSession session, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy, boolean objectIsUnregistered) throws QueryException {
        throw QueryException.cannotConformExpression();
    }

    public Expression equal(byte theValue) {
        return equal(new Byte(theValue));
    }

    public Expression equal(char theChar) {
        return equal(new Character(theChar));
    }

    public Expression equal(double theValue) {
        return equal(new Double(theValue));
    }

    public Expression equal(float theValue) {
        return equal(new Float(theValue));
    }

    public Expression equal(int theValue) {
        return equal(new Integer(theValue));
    }

    public Expression equal(long theValue) {
        return equal(new Long(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receiver's value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").equal("Bob")
     * Java: employee.getFirstName().equals("Bob")
     * SQL: F_NAME = 'Bob'
     * </blockquote></pre>
     */
    public Expression equal(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Equal);
        return anOperator.expressionFor(this, theValue);
    }

    /**
     * Returns an expression that compares if the receiver's value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     * <p>Since OracleAS TopLink 10<i>g</i> (9.0.4) if <code>this</code> is an <code>ExpressionBuilder</code> and <code>theValue</code>
     * is not used elsewhere, both will be translated to the same table. This can
     * generate SQL with one less join for most exists subqueries.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("manager").equal(employee)
     * Java: employee.getManager().equals(employee)
     * SQL (optimized): EMP_ID = MANAGER_ID
     * SQL (unoptimized): t0.MANAGER_ID = t1.EMP_ID AND t0.EMP_ID = t1.EMP_ID
     * </blockquote></pre>
     * @see #equal(Object)
     */
    public Expression equal(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Equal);
        return anOperator.expressionFor(this, theValue);

    }

    public Expression equal(short theValue) {
        return equal(new Short(theValue));
    }

    public Expression equal(boolean theBoolean) {
        return equal(new Boolean(theBoolean));
    }

    /**
     * INTERNAL:
     * Return an expression representing an outer join comparison
     */

    // cr3546
    public Expression equalOuterJoin(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.EqualOuterJoin);
        return anOperator.expressionFor(this, theValue);
    }

    /**
     * INTERNAL:
     * Return an expression representing an outer join comparison
     */
    public Expression equalOuterJoin(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.EqualOuterJoin);
        return anOperator.expressionFor(this, theValue);

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receiver's value is equal to the other value, ignoring case.
     * This is equivalent to the Java "equalsIgnoreCase" method.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").equalsIgnoreCase("Bob")
     * Java: employee.getFirstName().equalsIgnoreCase("Bob")
     * SQL: UPPER(F_NAME) = 'BOB'
     * </blockquote></pre>
     */
    public Expression equalsIgnoreCase(String theValue) {
        return toUpperCase().equal(theValue.toUpperCase());
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receiver's value is equal to the other value, ignoring case.
     * This is equivalent to the Java "equalsIgnoreCase" method.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").equalsIgnoreCase("Bob")
     * Java: employee.getFirstName().equalsIgnoreCase("Bob")
     * SQL: UPPER(F_NAME) = 'BOB'
     * </blockquote></pre>
     */
    public Expression equalsIgnoreCase(Expression theValue) {
        return toUpperCase().equal(theValue.toUpperCase());
    }

    /**
     * PUBLIC:
     * Return a sub query expression.
     * A sub query using a report query to define a subselect within another queries expression or select's where clause.
     * The sub query (the report query) will use its own expression builder be can reference expressions from the base expression builder.
     * <p>Example:
     * <pre><blockquote>
     * ExpressionBuilder builder = new ExpressionBuilder();
     * ReportQuery subQuery = new ReportQuery(Employee.class, new ExpressionBuilder());
     * subQuery.setSelectionCriteria(subQuery.getExpressionBuilder().get("name").equal(builder.get("name")));
     * builder.exists(subQuery);
     * </blockquote></pre>
     */
    public Expression exists(ReportQuery subQuery) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Exists);
        return anOperator.expressionFor(subQuery(subQuery));
    }

    /**
     * INTERNAL:
     * Extract the primary key from the expression into the row.
     * Ensure that the query is quering the exact primary key.
     * Return false if not on the primary key.
     */
    public boolean extractPrimaryKeyValues(boolean requireExactMatch, ClassDescriptor descriptor, AbstractRecord primaryKeyRow, AbstractRecord translationRow) {
        return false;
    }

    /**
     * INTERNAL:
     * Create an expression node.
     */
    public static Expression from(Object value, Expression base) {
        //CR#... null value used to return null, must build a null constant expression.
        if (value instanceof Expression) {
            Expression exp = (Expression)value;
            if (exp.isValueExpression()) {
                exp.setLocalBase(base);
            } else {
                // We don't know which side of the relationship cares about the other one, so notify both
                // However for 3107049 value.equal(value) would cause infinite loop if did that.
                base.setLocalBase(exp);
            }
            return exp;
        }
        if (value instanceof ReportQuery) {
            Expression exp = base.subQuery((ReportQuery)value);
            exp.setLocalBase(base);// We don't know which side of the relationship cares about the other one, so notify both
            base.setLocalBase(exp);
            return exp;
        }
        return fromConstant(value, base);

    }

    /**
     * INTERNAL:
     * Create an expression node.
     */
    public static Expression fromConstant(Object value, Expression base) {
        return new ConstantExpression(value, base);
    }

    /**
     * INTERNAL:
     * Create an expression node.
     */
    public static Expression fromLiteral(String value, Expression base) {
        return new LiteralExpression(value, base);
    }

    /**
     * PUBLIC:
     * Return an expression that wraps the attribute or query key name.
     * This method is used to construct user-defined queries containing joins.
     * <p>Example:
     * <pre><blockquote>
     * builder.get("address").get("city").equal("Ottawa");
     * </blockquote></pre>
     */
    public Expression get(String attributeName) {
        return get(attributeName, null);
    }

    /**
     * INTERNAL:
     */
    public Expression get(String attributeName, Vector arguments) {
        return null;
    }

    /**
     * ADVANCED:
     * Return an expression that wraps the attribute or query key name.
     * This is only applicable to 1:1 relationships, and allows the target of
     * the relationship to be null if there is no correspondingn relationship in the database.
     * Implemented via an outer join in the database.
     * <p>Example:
     * <pre><blockquote>
     * builder.getAllowingNull("address").get("city").equal("Ottawa");
     * </blockquote></pre>
     */
    public Expression getAllowingNull(String attributeName) {
        return getAllowingNull(attributeName, null);
    }

    /**
     * INTERNAL:
     */
    public Expression getAllowingNull(String attributeName, Vector arguments) {

        /* this is meaningless for expressions in general */
        return get(attributeName, arguments);
    }

    /**
     * INTERNAL:
     * Return the expression builder which is the ultimate base of this expression, or
     * null if there isn't one (shouldn't happen if we start from a root)
     */
    public abstract ExpressionBuilder getBuilder();

    /**
     * INTERNAL:
     * If there are any fields associated with this expression, return them
     */
    public DatabaseField getClonedField() {
        return null;
    }

    /**
     * ADVANCED:
     * Return an expression representing a field in a data-level query.
     * This is used internally in TopLink, or to construct queries involving
     * fields and/or tables that are not mapped.
     * <p> Example:
     * <pre><blockquote>
     * builder.getField("ADDR_ID").greaterThan(100);
     * builder.getTable("PROJ_EMP").getField("TYPE").equal("S");
     * </blockquote></pre>
     */
    public Expression getField(String fieldName) {
        throw QueryException.illegalUseOfGetField(fieldName);
    }

    /**
     * ADVANCED: Return an expression representing a field in a data-level query.
     * This is used internally in TopLink, or to construct queries involving
     * fields and/or tables that are not mapped.
     * <p> Example:
     * <pre><blockquote>
     * builder.getField(aField).greaterThan(100);
     * </blockquote></pre>
     */
    public Expression getField(DatabaseField field) {
        throw QueryException.illegalUseOfGetField(field);
    }

    /**
     * INTERNAL:
     */
    public Vector getFields() {
        return oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
    }

    /**
     * INTERNAL:
     * Transform the object-level value into a database-level value
     */
    public Object getFieldValue(Object objectValue) {
        return objectValue;

    }

    /**
     * ADVANCED:
     * This can be used for accessing user defined functions.
     * The operator must be defined in ExpressionOperator to be able to reference it.
     * @see ExpressionOperator
     * <p> Example:
     * <pre><blockquote>
     * builder.get("name").getFunction(MyFunctions.FOO_BAR).greaterThan(100);
     * </blockquote></pre>
     */
    public Expression getFunction(int selector) {
        ExpressionOperator anOperator = getOperator(selector);
        return anOperator.expressionFor(this);
    }

    /**
     * ADVANCED:
     * This can be used for accessing user defined functions that have arguments.
     * The operator must be defined in ExpressionOperator to be able to reference it.
     * @see ExpressionOperator
     * <p> Example:
     * <pre><blockquote>
     * Vector arguments = new Vector();
     * arguments.addElement("blee");
     * builder.get("name").getFunction(MyFunctions.FOO_BAR, arguments).greaterThan(100);
     * </blockquote></pre>
     */
    public Expression getFunction(int selector, Vector arguments) {
        ExpressionOperator anOperator = getOperator(selector);
        return anOperator.expressionForArguments(this, arguments);
    }

    /**
     * ADVANCED:
     * Return a user defined function accepting the argument.
     * The function is assumed to be a normal prefix function and will print like, UPPER(base).
     * <p> Example:
     * <pre><blockquote>
     * builder.get("firstName").getFunction("UPPER");
     * </blockquote></pre>
     */
    public Expression getFunction(String functionName) {
        ExpressionOperator anOperator = ExpressionOperator.simpleFunction(0, functionName);
        return anOperator.expressionFor(this);
    }

    /**
     * ADVANCED:
     * Return a user defined function accepting the argument.
     * The function is assumed to be a normal prefix function and will print like, CONCAT(base, argument).
     */
    public Expression getFunction(String functionName, Object argument) {
        ExpressionOperator anOperator = ExpressionOperator.simpleTwoArgumentFunction(0, functionName);
        return anOperator.expressionFor(this, argument);
    }

    /**
     * ADVANCED:
     * Return a user defined function accepting all of the arguments.
     * The function is assumed to be a normal prefix function like, CONCAT(base, value1, value2, value3, ...).
     */
    public Expression getFunctionWithArguments(String functionName, Vector arguments) {
        ExpressionOperator anOperator = new ExpressionOperator();
        anOperator.setType(ExpressionOperator.FunctionOperator);
        Vector v = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(arguments.size());
        v.addElement(functionName + "(");
        for (int index = 0; index < arguments.size(); index++) {
            v.addElement(", ");
        }
        v.addElement(")");
        anOperator.printsAs(v);
        anOperator.bePrefix();
        anOperator.setNodeClass(ClassConstants.FunctionExpression_Class);

        return anOperator.expressionForArguments(this, arguments);
    }

    /**
     * INTERNAL:
     */
    public String getName() {
        return "";
    }

    /**
     * INTERNAL:
     * Most expression have operators, so this is just a convenience method.
     */
    public ExpressionOperator getOperator() {
        return null;
    }

    /**
     * INTERNAL:
     * Create a new expression tree with the named operator. Part of the implementation of user-level "get"
     */
    public ExpressionOperator getOperator(int selector) {
        ExpressionOperator result = ExpressionOperator.getOperator(new Integer(selector));
        if (result != null) {
            return result;
        }

        // Make a temporary operator which we expect the platform
        // to supply later.
        result = new ExpressionOperator();
        result.setSelector(selector);
        result.setNodeClass(ClassConstants.FunctionExpression_Class);
        return result;
    }

    /**
     * INTERNAL:
     * Return the tables that this node owns for purposes of table aliasing.
     */
    public Vector getOwnedTables() {
        return null;
    }

    /**
     * INTERNAL:
     * Return an expression representing a parameter with the given name and type
     */
    public Expression getParameter(String parameterName, Object type) {
        return new ParameterExpression(parameterName, this, type);

    }

    /**
     * ADVANCED:
     * Return an expression representing a parameter with the given name.
     */
    public Expression getParameter(String parameterName) {
        return new ParameterExpression(parameterName, this, null);

    }

    /**
     * ADVANCED:
     * Return an expression representing a parameter with the given name.
     */
    public Expression getParameter(DatabaseField field) {
        return new ParameterExpression(field, this);

    }

    /**
     * INTERNAL:
     */
    public AbstractSession getSession() {
        return getBuilder().getSession();
    }

    /**
     * ADVANCED: Return an expression representing a table in a data-level query.
     * This is used internally in TopLink, or to construct queries involving
     * fields and/or tables that are not mapped.
     * <p> Example:
     * <pre><blockquote>
     * builder.getTable("PROJ_EMP").getField("TYPE").equal("S");
     * </blockquote></pre>
     */
    public Expression getTable(String tableName) {
        DatabaseTable table = new DatabaseTable(tableName);
        return getTable(table);
    }

    /**
     * ADVANCED: Return an expression representing a table in a data-level query.
     * This is used internally in TopLink, or to construct queries involving
     * fields and/or tables that are not mapped.
     * <p> Example:
     * <pre><blockquote>
     * builder.getTable(linkTable).getField("TYPE").equal("S");
     * </blockquote></pre>
     */
    public Expression getTable(DatabaseTable table) {
        throw QueryException.illegalUseOfGetTable(table);
    }

    /**
     * INTERNAL:
     * Return the aliases used. By default, return null, since we don't have tables.
     */
    public TableAliasLookup getTableAliases() {
        return null;

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(byte theValue) {
        return greaterThan(new Byte(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(char theChar) {
        return greaterThan(new Character(theChar));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(double theValue) {
        return greaterThan(new Double(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(float theValue) {
        return greaterThan(new Float(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(int theValue) {
        return greaterThan(new Integer(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(long theValue) {
        return greaterThan(new Long(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receiver's value is greater than the other value.
     * This is equivalent to the SQL ">" operator.
     */
    public Expression greaterThan(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.GreaterThan);
        return anOperator.expressionFor(this, theValue);
    }

    public Expression greaterThan(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.GreaterThan);
        return anOperator.expressionFor(this, theValue);

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(short theValue) {
        return greaterThan(new Short(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is equal to the other value.
     * This is equivalent to the SQL "=" operator and Java "equals" method.
     */
    public Expression greaterThan(boolean theBoolean) {
        return greaterThan(new Boolean(theBoolean));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(byte theValue) {
        return greaterThanEqual(new Byte(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(char theChar) {
        return greaterThanEqual(new Character(theChar));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(double theValue) {
        return greaterThanEqual(new Double(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(float theValue) {
        return greaterThanEqual(new Float(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(int theValue) {
        return greaterThanEqual(new Integer(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(long theValue) {
        return greaterThanEqual(new Long(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.GreaterThanEqual);
        return anOperator.expressionFor(this, theValue);

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.GreaterThanEqual);
        return anOperator.expressionFor(this, theValue);

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(short theValue) {
        return greaterThanEqual(new Short(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is greater and equal to the other value.
     * This is equivalent to the SQL ">=" operator .
     */
    public Expression greaterThanEqual(boolean theBoolean) {
        return greaterThanEqual(new Boolean(theBoolean));
    }

    /**
     * ADVANCED:
     * Answers true if <code>this</code> is to be queried as of a past time.
     * @return false from <code>asOf(null); hasAsOfClause()</code>.
     * @see #getAsOfClause
     */
    public boolean hasAsOfClause() {
        return false;
    }

    /**
     * INTERNAL:
     * Answers if the database tables associated with this expression have been
     * aliased. This insures the same tables are not aliased twice.
     */
    public boolean hasBeenAliased() {
        return false;
    }

    /**
     * PUBLIC:
     * Function, returns binary array value for the hex string.
     */
    public Expression hexToRaw() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.HexToRaw);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function return the a specific value if item returned from the
     * query is null. Equivalent of the oracle NVL function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").ifNull("no-name")
     * Java: NA
     * SQL: NVL(name, 'no-name')
     * </pre></blockquote> *
     */
    public Expression ifNull(Object nullValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Nvl);
        return anOperator.expressionFor(this, nullValue);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(byte[] theBytes) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBytes.length; index++) {
            vector.addElement(new Byte(theBytes[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(char[] theChars) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theChars.length; index++) {
            vector.addElement(new Character(theChars[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(double[] theDoubles) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theDoubles.length; index++) {
            vector.addElement(new Double(theDoubles[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(float[] theFloats) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theFloats.length; index++) {
            vector.addElement(new Float(theFloats[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(int[] theInts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theInts.length; index++) {
            vector.addElement(new Integer(theInts[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(long[] theLongs) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theLongs.length; index++) {
            vector.addElement(new Long(theLongs[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(Object[] theObjects) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theObjects.length; index++) {
            vector.addElement(theObjects[index]);
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(short[] theShorts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theShorts.length; index++) {
            vector.addElement(new Short(theShorts[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression in(boolean[] theBooleans) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBooleans.length; index++) {
            vector.addElement(new Boolean(theBooleans[index]));
        }

        return in(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * The collection can be a collection of constants or expressions.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").in(agesVector)
     * Java: agesVector.contains(employee.getAge())
     * SQL: AGE IN (55, 18, 30)
     * </blockquote></pre>
     */
    public Expression in(Vector theObjects) {
        //If none of the elements in theObjects is expression, build a ConstantExpression with theObjects.
        if (!detectExpression(theObjects)) {
            return in(new ConstantExpression(theObjects, this));
        }
        //Otherwise build a collection of expressions.
        ExpressionOperator anOperator = getOperator(ExpressionOperator.In);
        return anOperator.expressionForArguments(this, theObjects);
    }

    public Expression in(Expression arguments) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.In);
        return anOperator.expressionFor(this, arguments);
    }

    public Expression in(ReportQuery subQuery) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.InSubQuery);
        return anOperator.expressionFor(this, subQuery);
    }

    /**
     * PUBLIC:
     * Function, returns the integer index of the substring within the source string.
     */
    public Expression indexOf(Object substring) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Instring);
        return anOperator.expressionFor(this, substring);
    }

    /**
     * INTERNAL:
     */
    public boolean isCompoundExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isConstantExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isDataExpression() {
        return false;
    }

    /**
     * PUBLIC: A logical expression for the collection <code>attributeName</code>
     * being empty.
     * Equivalent to <code>size(attributeName).equal(0)</code>
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.isEmpty("phoneNumbers")
     * Java: employee.getPhoneNumbers().size() == 0
     * SQL: SELECT ... FROM EMP t0 WHERE (
     * (SELECT COUNT(*) FROM PHONE t1 WHERE (t0.EMP_ID = t1.EMP_ID)) = 0)
     * </blockquote></pre>
     * This is a case where a fast operation in java does not translate to an
     * equally fast operation in SQL, requiring a correlated subselect.
     * @see #size(java.lang.String)
     */
    public Expression isEmpty(String attributeName) {
        return size(attributeName).equal(0);
    }

    /**
     * INTERNAL:
     */
    public boolean isExpressionBuilder() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isFieldExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isFunctionExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isLiteralExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isLogicalExpression() {
        return false;
    }

    /**
     * PUBLIC:
     * Compare to null.
     */
    public Expression isNull() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.IsNull);
        return anOperator.expressionFor(this);
    }

    /**
     * INTERNAL:
     */
    public boolean isObjectExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isParameterExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isQueryKeyExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isRelationExpression() {
        return false;
    }

    /**
     * INTERNAL:
     */
    public boolean isTableExpression() {
        return false;
    }

    /**
     * INTERNAL:
     * Subclasses implement (isParameterExpression() || isConstantExpression())
     */
    public boolean isValueExpression() {
        return false;
    }

    /**
     * INTERNAL:
     * For iterating using an inner class
     */
    public void iterateOn(ExpressionIterator iterator) {
        iterator.iterate(this);
    }

    /**
     * PUBLIC:
     * Function, returns the date with the last date in the months of this source date.
     */
    public Expression lastDay() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LastDay);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function, returns the string padded with the substring to the size.
     */
    public Expression leftPad(int size, Object substring) {
        return leftPad(new Integer(size), substring);
    }

    /**
     * PUBLIC:
     * Function, returns the string padded with the substring to the size.
     */
    public Expression leftPad(Object size, Object substring) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LeftPad);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        args.addElement(size);
        args.addElement(substring);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Function, returns the string left trimmed for white space.
     */
    public Expression leftTrim() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LeftTrim);
        return anOperator.expressionFor(this);
    }
    
    /**
     * PUBLIC:
     * Function, returns the string with the substring trimed from the left.
     */
    public Expression leftTrim(Object substring) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LeftTrim2);
        return anOperator.expressionFor(this, substring);
    }

    /**
     * PUBLIC:
     * Function, returns the size of the string.
     */
    public Expression length() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Length);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(byte theValue) {
        return lessThan(new Byte(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(char theChar) {
        return lessThan(new Character(theChar));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(double theValue) {
        return lessThan(new Double(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(float theValue) {
        return lessThan(new Float(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(int theValue) {
        return lessThan(new Integer(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(long theValue) {
        return lessThan(new Long(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LessThan);
        return anOperator.expressionFor(this, theValue);
    }

    public Expression lessThan(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LessThan);
        return anOperator.expressionFor(this, theValue);

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(short theValue) {
        return lessThan(new Short(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than the other value.
     * This is equivalent to the SQL "<" operator.
     */
    public Expression lessThan(boolean theBoolean) {
        return lessThan(new Boolean(theBoolean));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(byte theValue) {
        return lessThanEqual(new Byte(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(char theChar) {
        return lessThanEqual(new Character(theChar));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(double theValue) {
        return lessThanEqual(new Double(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(float theValue) {
        return lessThanEqual(new Float(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(int theValue) {
        return lessThanEqual(new Integer(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(long theValue) {
        return lessThanEqual(new Long(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LessThanEqual);
        return anOperator.expressionFor(this, theValue);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LessThanEqual);
        return anOperator.expressionFor(this, theValue);

    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(short theValue) {
        return lessThanEqual(new Short(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is less than and equal to the other value.
     * This is equivalent to the SQL "<=" operator.
     */
    public Expression lessThanEqual(boolean theBoolean) {
        return lessThanEqual(new Boolean(theBoolean));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is like other value.
     * This is equivalent to the SQL "LIKE" operator that except wildcards.
     * The character "%" means any sequence of characters and the character "_" mean any character.
     * i.e. "B%" == "Bob", "B_B" == "BOB"
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").like("B%")
     * Java: NA
     * SQL: F_NAME LIKE 'B%'
     * </blockquote></pre>
     */
    public Expression like(String value) {
        return like(new ConstantExpression(value, this));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is like other value.
     * This is equivalent to the SQL "LIKE ESCAPE" operator that except wildcards.
     * The character "%" means any sequence of characters and the character "_" mean any character.
     * i.e. "B%" == "Bob", "B_B" == "BOB"
     * The escape sequence specifies a set of characters the may be used to indicate that
     * an one of the wildcard characters should be interpretted literally.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").like("B\_SMITH", "\")
     * Java: NA
     * SQL: F_NAME LIKE 'B\_SMITH ESCAPE '\''
     * </blockquote></pre>
     */
    public Expression like(String value, String escapeSequence) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LikeEscape);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        args.addElement(value);
        args.addElement(escapeSequence);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is like other value.
     * This is equivalent to the SQL "LIKE" operator that except wildcards.
     * The character "%" means any sequence of characters and the character "_" mean any character.
     * i.e. "B%" == "Bob", "B_B" == "BOB"
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").like("B%")
     * Java: NA
     * SQL: F_NAME LIKE 'B%'
     * </pre></blockquote>
     */
    public Expression like(Expression argument) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Like);
        return anOperator.expressionFor(this, argument);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is like other value.
     * This is equivalent to the SQL "LIKE ESCAPE" operator that except wildcards.
     * The character "%" means any sequence of characters and the character "_" mean any character.
     * i.e. "B%" == "Bob", "B_B" == "BOB"
     * The escape sequence specifies a set of characters the may be used to indicate that
     * an one of the wildcard characters should be interpretted literally.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").like("B\_SMITH", "\")
     * Java: NA
     * SQL: F_NAME LIKE 'B\_SMITH ESCAPE '\''
     * </blockquote></pre>
     */
    public Expression like(Expression value, Expression escapeSequence) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.LikeEscape);
        Vector args = new Vector();
        args.addElement(value);
        args.addElement(escapeSequence);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is like the other value, ignoring case.
     * This is a case in-sensitive like.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").likeIgnoreCase("%Bob%")
     * Java: none
     * SQL: UPPER(F_NAME) LIKE '%BOB%'
     * </pre></blockquote>
     */
    public Expression likeIgnoreCase(String theValue) {
        return toUpperCase().like(theValue.toUpperCase());
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is like the other value, ignoring case.
     * This is a case in-sensitive like.
     */
    public Expression likeIgnoreCase(Expression theValue) {
        return toUpperCase().like(theValue.toUpperCase());
    }

    /**
     * PUBLIC:
     * Function, returns the position of <code>str</code> in <code>this</code>
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").locate("ob")
     * Java: employee.getFirstName().indexOf("ob") + 1
     * SQL: LOCATE('ob', t0.F_NAME)
     * </pre></blockquote>
     * <p>
     * Note that while in String.locate(str) -1 is returned if not found, and the
     * index starting at 0 if found, in SQL it is 0 if not found, and the index
     * starting at 1 if found.
     */
    public Expression locate(Object str) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Locate);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        args.addElement(str);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Function, returns the position of <code>str</code> in <code>this</code>,
     * starting the search at <code>fromIndex</code>.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").locate("ob", 1)
     * Java: employee.getFirstName().indexOf("ob", 1) + 1
     * SQL: LOCATE('ob', t0.F_NAME, 1)
     * </pre></blockquote>
     * <p>
     * Note that while in String.locate(str) -1 is returned if not found, and the
     * index starting at 0 if found, in SQL it is 0 if not found, and the index
     * starting at 1 if found.
     */
    public Expression locate(String str, int fromIndex) {
        return locate(str, new Integer(fromIndex));
    }

    /**
     * PUBLIC:
     * Function, returns the position of <code>str</code> in <code>this</code>,
     * starting the search at <code>fromIndex</code>.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").locate("ob", 1)
     * Java: employee.getFirstName().indexOf("ob", 1) + 1
     * SQL: LOCATE('ob', t0.F_NAME, 1)
     * </pre></blockquote>
     * <p>
     * Note that while in String.locate(str) -1 is returned if not found, and the
     * index starting at 0 if found, in SQL it is 0 if not found, and the index
     * starting at 1 if found.
     */
    public Expression locate(Object str, Object fromIndex) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Locate2);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        args.addElement(str);
        args.addElement(fromIndex);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * This represents the aggregate function Maximum. Can be used only within Report Queries.
     */
    public Expression maximum() {
        return getFunction(ExpressionOperator.Maximum);
    }

    /**
     * PUBLIC:
     * This represents the aggregate function Minimum. Can be used only within Report Queries.
     */
    public Expression minimum() {
        return getFunction(ExpressionOperator.Minimum);
    }

    /**
     * PUBLIC:
     * Function, returns the decimal number of months between the two dates.
     */
    public Expression monthsBetween(Object otherDate) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.MonthsBetween);
        return anOperator.expressionFor(this, otherDate);
    }

    /**
     * PUBLIC:
     * funcation return a date converted to a new timezone. Equivalent of the Oracle NEW_TIME function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").newTime("EST", "PST")
     * Java: NA
     * SQL: NEW_TIME(date, 'EST', 'PST')
     * </pre></blockquote> *
     */
    public Expression newTime(String timeZoneFrom, String timeZoneTo) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NewTime);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        args.addElement(timeZoneFrom);
        args.addElement(timeZoneTo);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Function, returns the date with the next day from the source date as the day name given.
     */
    public Expression nextDay(Object dayName) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NextDay);
        return anOperator.expressionFor(this, dayName);
    }

    /**
     * PUBLIC: Returns an expression equivalent to none of <code>attributeName</code>
     * holding true for <code>criteria</code>.
     * <p>
     * For every expression with an anyOf, its negation has either an allOf or a
     * noneOf. The following two examples will illustrate as the second is the
     * negation of the first:
     * <p>
     * AnyOf Example: Employees with a '613' area code phone number.
     * <pre><blockquote>
     * ReadAllQuery query = new ReadAllQuery(Employee.class);
     * ExpressionBuilder employee = new ExpressionBuilder();
     * Expression exp = employee.anyOf("phoneNumbers").get("areaCode").equal("613");
     * </blockquote></pre>
     * <p>
     * NoneOf Example: Employees with no '613' area code phone numbers.
     * <pre><blockquote>
     * ExpressionBuilder employee = new ExpressionBuilder();
     * ExpressionBuilder phones = new ExpressionBuilder();
     * Expression exp = employee.noneOf("phoneNumbers", phones.get("areaCode").equal("613"));
     * SQL:
     * SELECT ... EMPLOYEE t0 WHERE NOT EXISTS (SELECT ... PHONE t1 WHERE
     * (t0.EMP_ID = t1.EMP_ID) AND (t1.AREACODE = '613'))
     * </blockquote></pre>
     * <p>
     * noneOf is the universal counterpart to the existential anyOf. To have the
     * condition evaluated for each instance it must be put inside of a subquery,
     * which can be expressed as not exists (any of attributeName some condition).
     * (All x such that !y = !Exist x such that y).
     * <p>Likewise the syntax employee.noneOf("phoneNumbers").get("areaCode").equal("613")
     * is not supported for the <code>equal</code> must go inside a subQuery.
     * <p>
     * This method saves you from writing the sub query yourself. The above is
     * equivalent to the following expression:
     * <pre><blockquote>
     * ExpressionBuilder employee = new ExpressionBuilder();
     * ExpressionBuilder phone = new ExpressionBuilder();
     * ReportQuery subQuery = new ReportQuery(Phone.class, phone);
     * subQuery.retreivePrimaryKeys();
     * subQuery.setSelectionCriteria(phone.equal(employee.anyOf("phoneNumbers").and(
     * phone.get("areaCode").equal("613")));
     * Expression exp = employee.notExists(subQuery);
     * </blockquote></pre>
     * @param criteria must have its own builder, as it will become the
     * seperate selection criteria of a subQuery.
     * @return a notExists subQuery expression
     */
    public Expression noneOf(String attributeName, Expression criteria) {
        ReportQuery subQuery = new ReportQuery();
        subQuery.setShouldRetrieveFirstPrimaryKey(true);
        Expression builder = criteria.getBuilder();
        criteria = builder.equal(anyOf(attributeName)).and(criteria);
        subQuery.setSelectionCriteria(criteria);
        return notExists(subQuery);
    }

    /**
     * INTERNAL:
     * Normalize into a structure that is printable.
     * Also compute printing information such as outer joins.
     */
    public Expression normalize(ExpressionNormalizer normalizer) {
        //This class has no validation but we should still make the method call for consistency
        //bug # 2956674
        //validation is moved into normalize to ensure that expressions are valid before we attempt to work with them
        validateNode();
        return this;
    }

    /**
     * PUBLIC:
     * Return an expression that is the boolean logical negation of the expression.
     * This is equivalent to the SQL "NOT" operator and the Java "!" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").equal(24).not()
     * Java: (! (employee.getAge() == 24))
     * SQL: NOT (AGE = 24)
     * </blockquote></pre>
     */
    public Expression not() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Not);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(byte leftValue, byte rightValue) {
        return notBetween(new Byte(leftValue), new Byte(rightValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(char leftChar, char rightChar) {
        return notBetween(new Character(leftChar), new Character(rightChar));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(double leftValue, double rightValue) {
        return notBetween(new Double(leftValue), new Double(rightValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(float leftValue, float rightValue) {
        return notBetween(new Float(leftValue), new Float(rightValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(int leftValue, int rightValue) {
        return notBetween(new Integer(leftValue), new Integer(rightValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(long leftValue, long rightValue) {
        return notBetween(new Long(leftValue), new Long(rightValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(Object leftValue, Object rightValue) {
        return between(leftValue, rightValue).not();
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(Expression leftExpression, Expression rightExpression) {
        return between(leftExpression, rightExpression).not();
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not between two other values.
     * Equivalent to between negated.
     * @see #between(Object, Object)
     */
    public Expression notBetween(short leftValue, short rightValue) {
        return notBetween(new Short(leftValue), new Short(rightValue));
    }

    /**
     * PUBLIC: A logical expression for the collection <code>attributeName</code>
     * not being empty.
     * Equivalent to <code>size(attributeName).greaterThan(0)</code>
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.notEmpty("phoneNumbers")
     * Java: employee.getPhoneNumbers().size() > 0
     * SQL: SELECT ... FROM EMP t0 WHERE (
     * (SELECT COUNT(*) FROM PHONE t1 WHERE (t0.EMP_ID = t1.EMP_ID)) > 0)
     * </blockquote></pre>
     * This is a case where a fast operation in java does not translate to an
     * equally fast operation in SQL, requiring a correlated subselect.
     * @see #size(java.lang.String)
     */
    public Expression notEmpty(String attributeName) {
        return size(attributeName).greaterThan(0);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(byte theValue) {
        return notEqual(new Byte(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(char theChar) {
        return notEqual(new Character(theChar));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(double theValue) {
        return notEqual(new Double(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(float theValue) {
        return notEqual(new Float(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(int theValue) {
        return notEqual(new Integer(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(long theValue) {
        return notEqual(new Long(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotEqual);
        return anOperator.expressionFor(this, theValue);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(Expression theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotEqual);
        return anOperator.expressionFor(this, theValue);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(short theValue) {
        return notEqual(new Short(theValue));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not equal to the other value.
     * This is equivalent to the SQL "<>" operator
     *
     * @see #equal(Object)
     */
    public Expression notEqual(boolean theBoolean) {
        return notEqual(new Boolean(theBoolean));
    }

    /**
     * PUBLIC:
     * Return a sub query expression.
     * A sub query using a report query to define a subselect within another queries expression or select's where clause.
     * The sub query (the report query) will use its own expression builder be can reference expressions from the base expression builder.
     * <p>Example:
     * <pre><blockquote>
     * ExpressionBuilder builder = new ExpressionBuilder();
     * ReportQuery subQuery = new ReportQuery(Employee.class, new ExpressionBuilder());
     * subQuery.setSelectionCriteria(subQuery.getExpressionBuilder().get("name").equal(builder.get("name")));
     * builder.notExists(subQuery);
     * </blockquote></pre>
     */
    public Expression notExists(ReportQuery subQuery) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotExists);
        return anOperator.expressionFor(subQuery(subQuery));
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(byte[] theBytes) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBytes.length; index++) {
            vector.addElement(new Byte(theBytes[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(char[] theChars) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theChars.length; index++) {
            vector.addElement(new Character(theChars[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(double[] theDoubles) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theDoubles.length; index++) {
            vector.addElement(new Double(theDoubles[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(float[] theFloats) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theFloats.length; index++) {
            vector.addElement(new Float(theFloats[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(int[] theInts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theInts.length; index++) {
            vector.addElement(new Integer(theInts[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(long[] theLongs) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theLongs.length; index++) {
            vector.addElement(new Long(theLongs[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(Object[] theObjects) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theObjects.length; index++) {
            vector.addElement(theObjects[index]);
        }

        return notIn(vector);
    }

    public Expression notIn(ReportQuery subQuery) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotInSubQuery);
        return anOperator.expressionFor(this, subQuery);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(short[] theShorts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theShorts.length; index++) {
            vector.addElement(new Short(theShorts[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression notIn(boolean[] theBooleans) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBooleans.length; index++) {
            vector.addElement(new Boolean(theBooleans[index]));
        }

        return notIn(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * The collection can be a collection of constants or expressions.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").in(agesVector)
     * Java: agesVector.contains(employee.getAge())
     * SQL: AGE IN (55, 18, 30)
     * </blockquote></pre>
     */
    public Expression notIn(Vector theObjects) {
        //If none of the elements in theObjects is expression, build a ConstantExpression with theObjects.
        if (!detectExpression(theObjects)) {
            return notIn(new ConstantExpression(theObjects, this));
        }
        //Otherwise build a collection of expressions.
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotIn);
        return anOperator.expressionForArguments(this, theObjects);
    }

    public Expression notIn(Expression arguments) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotIn);
        return anOperator.expressionFor(this, arguments);
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not like the other value.
     * Equivalent to like negated.
     * @see #like(String)
     */
    public Expression notLike(String aString) {
        return notLike(new ConstantExpression(aString, this));
    }

    /**
     * PUBLIC:
     * Return an expression that compares if the receivers value is not like the other value.
     * Equivalent to like negated.
     * @see #like(String)
     */
    public Expression notLike(Expression arguments) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotLike);
        return anOperator.expressionFor(this, arguments);
    }

    /**
     * PUBLIC:
     * Return an expression representing a comparison to null
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").notNull()
     * Java: employee.getAge() != null
     * SQL: AGE IS NOT NULL
     * </blockquote></pre>
     */
    public Expression notNull() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.NotNull);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Return an expression that is the boolean logical combination of both expressions.
     * This is equivalent to the SQL "OR" operator and the Java "||" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").equal("Bob").OR(employee.get("lastName").equal("Smith"))
     * Java: (employee.getFirstName().equals("Bob")) || (employee.getLastName().equals("Smith"))
     * SQL: F_NAME = 'Bob' OR L_NAME = 'Smith'
     * </blockquote></pre>
     */
    public Expression or(Expression theExpression) {
        // Allow ands with null.
        if (theExpression == null) {
            return this;
        }

        ExpressionBuilder base = getBuilder();
        Expression expressionToUse = theExpression;

        // Ensure the same builder, unless a parralel query is used.
        if ((theExpression.getBuilder() != base) && (theExpression.getBuilder().getQueryClass() == null)) {
            expressionToUse = theExpression.rebuildOn(base);
        }

        if (base == this) {// Allow and to be sent to the builder.
            return expressionToUse;
        }

        ExpressionOperator anOperator = getOperator(ExpressionOperator.Or);
        return anOperator.expressionFor(this, expressionToUse);
    }

    /**
     * INTERNAL:
     */
    public Expression performOperator(ExpressionOperator anOperator, Vector args) {
        return anOperator.expressionForArguments(this, args);
    }

    protected void postCopyIn(Dictionary alreadyDone) {
    }

    /**
     * ADVANCED:
     * Inserts the SQL as is directly into the expression.
     * The sql will be printed immediately after (postfixed to) the sql for
     * <b>this</b>.
     */
    public Expression postfixSQL(String sqlString) {
        ExpressionOperator anOperator = new ExpressionOperator();
        anOperator.setType(ExpressionOperator.FunctionOperator);
        Vector v = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        v.addElement(sqlString);
        anOperator.printsAs(v);
        anOperator.bePostfix();
        anOperator.setNodeClass(ClassConstants.FunctionExpression_Class);

        return anOperator.expressionFor(this);
    }

    /**
     * ADVANCED:
     * Insert the SQL as is directly into the expression.
     * The sql will be printed immediately before (prefixed to) the sql for
     * <b>this</b>.
     */
    public Expression prefixSQL(String sqlString) {
        ExpressionOperator anOperator = new ExpressionOperator();
        anOperator.setType(ExpressionOperator.FunctionOperator);
        Vector v = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        v.addElement(sqlString);
        anOperator.printsAs(v);
        anOperator.bePrefix();
        anOperator.setNodeClass(ClassConstants.FunctionExpression_Class);

        return anOperator.expressionFor(this);
    }

    /**
     * INTERNAL:
     * Print SQL
     */
    public abstract void printSQL(ExpressionSQLPrinter printer);

    /**
     * INTERNAL:
     * Print java for project class generation
     */
    public void printJava(ExpressionJavaPrinter printer) {
        //do nothing
    }

    /**
     * INTERNAL:
     * Print SQL, this is called from functions, so must not be converted through the mapping.
     */
    public void printSQLWithoutConversion(ExpressionSQLPrinter printer) {
        printSQL(printer);
    }

    /**
     * INTERNAL:
     * This expression is built on a different base than the one we want. Rebuild it and
     * return the root of the new tree
     * If receiver is a complex expression, use cloneUsing(newBase) instead.
     * @see #cloneUsing(Expression newBase)
     */
    public abstract Expression rebuildOn(Expression newBase);

    /**
     * ADVANCED:
     * For Object-relational support.
     */
    public Expression ref() {
        return getFunction(ExpressionOperator.Ref);
    }

    protected Expression registerIn(Dictionary alreadyDone) {
        Expression copy = (Expression)shallowClone();
        alreadyDone.put(this, copy);
        copy.postCopyIn(alreadyDone);
        return copy;

    }

    /**
     * PUBLIC:
     * Function, returns the string with occurances of the first substring replaced with the second substring.
     */
    public Expression replace(Object stringToReplace, Object stringToReplaceWith) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Replace);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        args.addElement(stringToReplace);
        args.addElement(stringToReplaceWith);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * return the result of this query repeated a given number of times.
     * Equivalent of the Sybase REPLICATE function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").replicate(2)
     * Java: NA
     * SQL: REPLICATE(name, 2)
     * </pre></blockquote>
     */
    public Expression replicate(int constant) {
        return replicate(new Integer(constant));
    }

    /**
     * PUBLIC:
     * return the result of this query repeated a given number of times.
     * Equivalent of the Sybase REPLICATE function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").replicate(2)
     * Java: NA
     * SQL: REPLICATE(name, 2)
     * </pre></blockquote>
     */
    public Expression replicate(Object theValue) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Replicate);
        return anOperator.expressionFor(this, theValue);
    }

    /**
     * Reset cached information here so that we can be sure we're accurate.
     */
    protected void resetCache() {
    }

    /**
     * PUBLIC:
     * Function return the reverse of the query result. Equivalent of the
     * Sybase REVERSE function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").reverse()
     * Java: NA
     * SQL: REVERSE(name)
     * </pre></blockquote>
     */
    public Expression reverse() {
        return getFunction(ExpressionOperator.Reverse);
    }

    /**
     * PUBLIC:
     * Function return a given number of characters starting at the
     * right of a string. Equivalent to the Sybase RIGHT function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").right(2)
     * Java: NA
     * SQL: RIGHT(name, 2)
     * </pre></blockquote>
     */
    public Expression right(int characters) {
        return right(new Integer(characters));
    }

    /**
     * PUBLIC:
     * Function return a given number of characters starting at the
     * right of a string. Equivalent to the Sybase RIGHT function
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("name").right(2)
     * Java: NA
     * SQL: RIGHT(name, 2)
     * </pre></blockquote>
     */
    public Expression right(Object characters) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Right);
        return anOperator.expressionFor(this, characters);
    }

    /**
     * PUBLIC:
     * Function, returns the string padded with the substring to the size.
     */
    public Expression rightPad(int size, Object substring) {
        return rightPad(new Integer(size), substring);
    }

    /**
     * PUBLIC:
     * Function, returns the string padded with the substring to the size.
     */
    public Expression rightPad(Object size, Object substring) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.RightPad);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        args.addElement(size);
        args.addElement(substring);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Function, returns the string right trimmed for white space.
     */
    public Expression rightTrim() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.RightTrim);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function, returns the string with the substring trimed from the right.
     */
    public Expression rightTrim(Object substring) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.RightTrim2);
        return anOperator.expressionFor(this, substring);
    }

    /**
     * PUBLIC:
     * Function, returns the date rounded to the year, month or day.
     */
    public Expression roundDate(Object yearOrMonthOrDayRoundToken) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.RoundDate);
        return anOperator.expressionFor(this, yearOrMonthOrDayRoundToken);
    }

    /**
     * PUBLIC:
     * Return whether this expression should be included in the SELECT clause if it is used
     * in an ORDER BY clause
     */
    public boolean selectIfOrderedBy() {
        return selectIfOrderedBy;
    }

    /**
     * INTERNAL:
     * Set the local base expression, ie the one on the other side of the operator
     * Most types will ignore this, since they don't need it.
     */
    public void setLocalBase(Expression exp) {
    }

    /**
     * PUBLIC:
     * Set whether this expression should be included in the SELECT clause of a query
     * that uses it in the ORDER BY clause.
     *
     * @param selectIfOrderedBy
     */
    public void setSelectIfOrderedBy(boolean selectIfOrderedBy) {
        this.selectIfOrderedBy = selectIfOrderedBy;
    }

    /**
     * INTERNAL:
     */
    public Expression shallowClone() {
        Expression result = null;
        try {
            result = (Expression)super.clone();
        } catch (CloneNotSupportedException e) {
        }
        ;
        return result;
    }

    /**
     * PUBLIC: A logical expression for the size of collection <code>attributeName</code>.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.size("phoneNumbers")
     * Java: employee.getPhoneNumbers().size()
     * SQL: SELECT ... FROM EMP t0 WHERE ...
     * (SELECT COUNT(*) FROM PHONE t1 WHERE (t0.EMP_ID = t1.EMP_ID))
     * </blockquote></pre>
     * This is a case where a fast operation in java does not translate to an
     * equally fast operation in SQL, requiring a correlated subselect.
     */
    public Expression size(String attributeName) {
        // Create an anoymous subquery that will get its reference class
        // set during SubSelectExpression.normalize.
        ReportQuery subQuery = new ReportQuery();
        subQuery.addCount();
        subQuery.setSelectionCriteria(subQuery.getExpressionBuilder().equal(this.anyOf(attributeName)));
        return subQuery(subQuery);
    }

    /**
     * PUBLIC:
     * This represents the aggregate function StandardDeviation. Can be used only within Report Queries.
     */
    public Expression standardDeviation() {
        return getFunction(ExpressionOperator.StandardDeviation);
    }

    /**
     * PUBLIC:
     * Return a sub query expression.
     * A sub query using a report query to define a subselect within another queries expression or select's where clause.
     * The sub query (the report query) will use its own expression builder be can reference expressions from the base expression builder.
     * <p>Example:
     * <pre><blockquote>
     * ExpressionBuilder builder = new ExpressionBuilder();
     * ReportQuery subQuery = new ReportQuery(Employee.class, new ExpressionBuilder());
     * subQuery.addMaximum("salary");
     * builder.get("salary").equal(builder.subQuery(subQuery));
     * </blockquote></pre>
     */
    public Expression subQuery(ReportQuery subQuery) {
        return new SubSelectExpression(subQuery, this);
    }

    /**
     * PUBLIC:
     * Function, returns the substring from the souce string.
     */
    public Expression substring(int startPosition, int size) {
        return substring(new Integer(startPosition), new Integer(size));
    }

    /**
     * PUBLIC:
     * Function, returns the substring from the souce string.
     */
    public Expression substring(Object startPosition, Object size) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Substring);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        args.addElement(startPosition);
        args.addElement(size);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * This represents the aggregate function Sum. Can be used only within Report Queries.
     */
    public Expression sum() {
        return getFunction(ExpressionOperator.Sum);
    }

    /**
     * PUBLIC:
     * Function, returns the single character string with the ascii or character set value.
     */
    public Expression toCharacter() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Chr);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function, returns date from the string using the default format.
     */
    public Expression toDate() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ToDate);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Return an expression that represents the receiver value converted to a character string.
     * This is equivalent to the SQL "TO_CHAR" operator and Java "toString" method.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("salary").toChar().equal("100000")
     * Java: employee.getSalary().toString().equals("100000")
     * SQL: TO_CHAR(SALARY) = '100000'
     * </blockquote></pre>
     */
    public Expression toChar() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ToChar);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Return an expression that represents the receiver value converted to a character string,
     * with the database formating options (i.e. 'year', 'yyyy', 'day', etc.).
     * This is equivalent to the SQL "TO_CHAR" operator and Java Date API.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("startDate").toChar("day").equal("monday")
     * Java: employee.getStartDate().getDay().equals("monday")
     * SQL: TO_CHAR(START_DATE, 'day') = 'monday'
     * </blockquote></pre>
     */
    public Expression toChar(String format) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ToCharWithFormat);
        return anOperator.expressionFor(this, format);
    }

    /**
     * PUBLIC:
     * Return an expression that represents the receiver value converted to lower case.
     * This is equivalent to the SQL "LOWER" operator and Java "toLowerCase" method.
     * This is only allowed for String attribute values.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").toLowerCase().equal("bob")
     * Java: employee.getFirstName().toLowerCase().equals("bob")
     * SQL: LOWER(F_NAME) = 'bob'
     * </blockquote></pre>
     */
    public Expression toLowerCase() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ToLowerCase);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function, returns the number converted from the string.
     */
    public Expression toNumber() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ToNumber);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Print a debug form of the expression tree.
     */
    public String toString() {
        try {
            StringWriter innerWriter = new StringWriter();
            BufferedWriter outerWriter = new BufferedWriter(innerWriter);
            toString(outerWriter, 0);
            outerWriter.flush();
            return innerWriter.toString();
        } catch (IOException e) {
            return ToStringLocalization.buildMessage("error_printing_expression", (Object[])null);
        }
    }

    /**
     * INTERNAL:
     * Print a debug form of the expression tree.
     */
    public void toString(BufferedWriter writer, int indent) throws IOException {
        writer.newLine();
        for (int i = 0; i < indent; i++) {
            writer.write(" ");
        }
        writer.write(descriptionOfNodeType());
        writer.write(" ");
        writeDescriptionOn(writer);
        writeSubexpressionsTo(writer, indent + 1);
    }

    /**
     * PUBLIC:
     * Return an expression that represents the receiver value converted to upper case.
     * This is equivalent to the SQL "UPPER" operator and Java "toUpperCase" method.
     * This is only allowed for String attribute values.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("firstName").toUpperCase().equal("BOB")
     * Java: employee.getFirstName().toUpperCase().equals("BOB")
     * SQL: UPPER(F_NAME) = 'BOB'
     * </blockquote></pre>
     */
    public Expression toUpperCase() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ToUpperCase);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function, returns the string with the first letter of each word capitalized.
     */
    public Expression toUppercaseCasedWords() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Initcap);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Function, returns the string with each char from the from string converted to the char in the to string.
     */
    public Expression translate(Object fromString, Object toString) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Translate);
        Vector args = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        args.addElement(fromString);
        args.addElement(toString);
        return anOperator.expressionForArguments(this, args);
    }

    /**
     * PUBLIC:
     * Function, returns the string trimmed for white space.
     */
    public Expression trim() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Trim);
        return anOperator.expressionFor(this);
    }
    
    /**
     * PUBLIC:
     * Function, returns the string right and left trimmed for the substring.
     */
    public Expression trim(Object substring) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Trim2);
        return anOperator.expressionForWithBaseLast(this, substring);
    }

    /**
     * PUBLIC:
     * XMLType Function, extracts a secton of XML from a larget XML document
     * @param String - xpath representing the node to be returned
     */
    public Expression extract(String path) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Extract);
        return anOperator.expressionFor(this, path);
    }

    /**
     * PUBLIC:
     * XMLType Function, extracts a value from an XMLType field
     * @param String - xpath expression
     */
    public Expression extractValue(String path) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ExtractValue);
        return anOperator.expressionFor(this, path);
    }

    /**
     * PUBLIC:
     * XMLType Function, gets the number of nodes returned by the given xpath expression
     * returns 0 if there are none
     * @param - Xpath expression
     */
    public Expression existsNode(String path) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.ExistsNode);
        return anOperator.expressionFor(this, path);
    }

    /**
     * PUBLIC:
     * XMLType Function - evaluates to 0 if the xml is a well formed document and 1 if the document
     * is a fragment
     */
    public Expression isFragment() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.IsFragment);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * XMLType Function - gets a string value from an XMLType
     */
    public Expression getStringVal() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.GetStringVal);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * XMLType Function - gets a number value from an XMLType
     */
    public Expression getNumberVal() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.GetNumberVal);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * return the date truncated to the indicated datePart. Equivalent
     * to the Sybase TRUNC function for dates
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("date").truncDate(year)
     * Java: NA
     * SQL: TRUNC(date, year)
     * </pre></blockquote> */
    public Expression truncateDate(String datePart) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.TruncateDate);
        return anOperator.expressionFor(this, datePart);

    }

    /**
     * INTERNAL:
     * We are given an expression that comes from a different context than the one in which this was built,
     * e.g. it is the selection criteria of a mapping, or the criteria on which multiple tables are joined in a descriptor.
     * We need to transform it so it refers to the objects we are dealing with, and AND it into the rest of our expression.
     *
     * We want to replace the original base expression with <newBase>, and any parameters will be given values based
     * on the context which <this> provides.
     *
     * For example, suppose that the main expression is
     * emp.address.streetName = 'something'
     * and we are trying to twist the selection criteria for the mapping 'address' in Employee. Because that mapping
     * selects addresses, we will use the 'address' node as the base. Values for any parameters will come from the 'emp' node,
     * which was the base of the original expression. Note that the values need not be constants, they can be fields.
     *
     * We do this by taking the tree we're trying to merge and traverse it more or less re-executing it
     * it with the appropriate initial receiver and context.
     * Return the root of the new expression tree. This will probably need to be AND'ed with the root of the old tree.
     */
    public Expression twist(Expression expression, Expression newBase) {
        if (expression == null) {
            return null;
        }
        return expression.twistedForBaseAndContext(newBase, this);

    }

    /**
     * INTERNAL:
     * Rebuild myself against the base, with the values of parameters supplied by the context
     * expression. This is used for transforming a standalone expression (e.g. the join criteria of a mapping)
     * into part of some larger expression. You normally would not call this directly, instead calling twist
     * See the comment there for more details"
     */
    public Expression twistedForBaseAndContext(Expression newBase, Expression context) {
        // Will be overridden by subclasses
        return this;
    }

    /**
     * INTERNAL:
     * Do any required validation for this node. Throw an exception for any incorrect constructs.
     */
    public void validateNode() {
    }

    /**
     * PUBLIC:
     * Function, this represents the value function, used in nestedtable
     */
    public Expression value() {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Value);
        return anOperator.expressionFor(this);
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(byte constant) {
        return value(new Byte(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(char constant) {
        return value(new Character(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(double constant) {
        return value(new Double(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(float constant) {
        return value(new Float(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(int constant) {
        return value(new Integer(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(long constant) {
        return value(new Long(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(Object constant) {
        return new ConstantExpression(constant, this);
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(short constant) {
        return value(new Short(constant));
    }

    /**
     * PUBLIC:
     * Return an expression on the constant.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("a constant", builder.value("a constant"));
     * </blockquote></pre>
     */
    public Expression value(boolean constant) {
        return value(new Boolean(constant));
    }

    /**
     * ADVANCED:
     * Return an expression on the literal.
     * A literal is a specific SQL syntax string that will be printed as is without quotes in the SQL.
     * It can be useful for printing database key words or global variables.
     * <p>Example:
     * <pre><blockquote>
     * reportQuery.addItem("currentTime", builder.literal("SYSDATE"));
     * </blockquote></pre>
     */
    public Expression literal(String literal) {
        return new LiteralExpression(literal, this);
    }

    /**
     * INTERNAL:
     * Return the value for in memory comparison.
     * This is only valid for valueable expressions.
     * New parameter added for feature 2612601
     * @param isObjectRegistered true if object possibly not a clone, but is being
     * conformed against the unit of work cache.
     */
    public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy, boolean isObjectUnregistered) {
        throw QueryException.cannotConformExpression();
    }

    /**
     * INTERNAL:
     * Return the value for in memory comparison.
     * This is only valid for valueable expressions.
     */
    public Object valueFromObject(Object object, AbstractSession session, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy) {
        return valueFromObject(object, session, translationRow, valueHolderPolicy, false);
    }

    /**
     * PUBLIC:
     * Function, this represents the aggregate function Variance. Can be used only within Report Queries.
     */
    public Expression variance() {
        return getFunction(ExpressionOperator.Variance);
    }

    /**
     * INTERNAL:
     * Used to print a debug form of the expression tree.
     */
    public void writeDescriptionOn(BufferedWriter writer) throws IOException {
        writer.write("some expression");
    }

    /**
     * INTERNAL:
     * Append the field name to the writer. Should be overriden for special operators such as functions.
     */
    protected void writeField(ExpressionSQLPrinter printer, DatabaseField field, SQLSelectStatement statement) {
        //print ", " before each selected field except the first one
        if (printer.isFirstElementPrinted()) {
            printer.printString(", ");
        } else {
            printer.setIsFirstElementPrinted(true);
        }

        if (statement.requiresAliases()) {
            if (field.getTable() != lastTable) {
                lastTable = field.getTable();
                currentAlias = aliasForTable(lastTable);
            }
            printer.printString(currentAlias.getQualifiedName());
            printer.printString(".");
            printer.printString(field.getName());
        } else {
            printer.printString(field.getName());
        }
    }

    /**
     * INTERNAL:
     * called from SQLSelectStatement.writeFieldsFromExpression(...)
     */
    public void writeFields(ExpressionSQLPrinter printer, Vector newFields, SQLSelectStatement statement) {
        for (Enumeration fieldsEnum = getFields().elements(); fieldsEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)fieldsEnum.nextElement();
            newFields.addElement(field);
            writeField(printer, field, statement);
        }
    }

    /**
     * INTERNAL:
     * Used in SQL printing.
     */
    public void writeSubexpressionsTo(BufferedWriter writer, int indent) throws IOException {
        // In general, there are no sub-expressions
    }

    /**
     *
     * PUBLIC:
     * Return an expression that is used with a comparison expression.
     * The ANY keyword denotes that the search condition is TRUE if the comparison is TRUE
     * for at least one of the values that is returned. If the subquery returns no value,
     * the search condition is FALSE
     */
    public Expression any(byte[] theBytes) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBytes.length; index++) {
            vector.addElement(new Byte(theBytes[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(char[] theChars) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theChars.length; index++) {
            vector.addElement(new Character(theChars[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(double[] theDoubles) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theDoubles.length; index++) {
            vector.addElement(new Double(theDoubles[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(float[] theFloats) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theFloats.length; index++) {
            vector.addElement(new Float(theFloats[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(int[] theInts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theInts.length; index++) {
            vector.addElement(new Integer(theInts[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(long[] theLongs) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theLongs.length; index++) {
            vector.addElement(new Long(theLongs[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(Object[] theObjects) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theObjects.length; index++) {
            vector.addElement(theObjects[index]);
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(short[] theShorts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theShorts.length; index++) {
            vector.addElement(new Short(theShorts[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression any(boolean[] theBooleans) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBooleans.length; index++) {
            vector.addElement(new Boolean(theBooleans[index]));
        }

        return any(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").in(agesVector)
     * Java: agesVector.contains(employee.getAge())
     * SQL: AGE IN (55, 18, 30)
     * </blockquote></pre>
     */
    public Expression any(Vector theObjects) {
        return any(new ConstantExpression(theObjects, this));
    }

    public Expression any(Expression arguments) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Any);
        return anOperator.expressionFor(this, arguments);
    }

    public Expression any(ReportQuery subQuery) {
        return any(subQuery(subQuery));
    }

    /**
     *
     * PUBLIC:
     * Return an expression that is used with a comparison expression.
     * The SOME keyword denotes that the search condition is TRUE if the comparison is TRUE
     * for at least one of the values that is returned. If the subquery returns no value,
     * the search condition is FALSE
     */
    public Expression some(byte[] theBytes) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBytes.length; index++) {
            vector.addElement(new Byte(theBytes[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(char[] theChars) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theChars.length; index++) {
            vector.addElement(new Character(theChars[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(double[] theDoubles) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theDoubles.length; index++) {
            vector.addElement(new Double(theDoubles[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(float[] theFloats) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theFloats.length; index++) {
            vector.addElement(new Float(theFloats[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(int[] theInts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theInts.length; index++) {
            vector.addElement(new Integer(theInts[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(long[] theLongs) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theLongs.length; index++) {
            vector.addElement(new Long(theLongs[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(Object[] theObjects) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theObjects.length; index++) {
            vector.addElement(theObjects[index]);
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(short[] theShorts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theShorts.length; index++) {
            vector.addElement(new Short(theShorts[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression some(boolean[] theBooleans) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBooleans.length; index++) {
            vector.addElement(new Boolean(theBooleans[index]));
        }

        return some(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").in(agesVector)
     * Java: agesVector.contains(employee.getAge())
     * SQL: AGE IN (55, 18, 30)
     * </blockquote></pre>
     */
    public Expression some(Vector theObjects) {
        return some(new ConstantExpression(theObjects, this));
    }

    public Expression some(Expression arguments) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.Some);
        return anOperator.expressionFor(this, arguments);
    }

    public Expression some(ReportQuery subQuery) {
        return some(subQuery(subQuery));
    }

    /**
     *
     * PUBLIC:
     * Return an expression that is used with a comparison expression.
     * The SOME keyword denotes that the search condition is TRUE if the comparison is TRUE
     * for at least one of the values that is returned. If the subquery returns no value,
     * the search condition is FALSE
     */
    public Expression all(byte[] theBytes) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBytes.length; index++) {
            vector.addElement(new Byte(theBytes[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(char[] theChars) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theChars.length; index++) {
            vector.addElement(new Character(theChars[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(double[] theDoubles) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theDoubles.length; index++) {
            vector.addElement(new Double(theDoubles[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(float[] theFloats) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theFloats.length; index++) {
            vector.addElement(new Float(theFloats[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(int[] theInts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theInts.length; index++) {
            vector.addElement(new Integer(theInts[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(long[] theLongs) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theLongs.length; index++) {
            vector.addElement(new Long(theLongs[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(Object[] theObjects) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theObjects.length; index++) {
            vector.addElement(theObjects[index]);
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(short[] theShorts) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theShorts.length; index++) {
            vector.addElement(new Short(theShorts[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     */
    public Expression all(boolean[] theBooleans) {
        Vector vector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = 0; index < theBooleans.length; index++) {
            vector.addElement(new Boolean(theBooleans[index]));
        }

        return all(vector);
    }

    /**
     * PUBLIC:
     * Return an expression that checks if the receivers value is contained in the collection.
     * This is equivalent to the SQL "IN" operator and Java "contains" operator.
     * <p>Example:
     * <pre><blockquote>
     * TopLink: employee.get("age").in(agesVector)
     * Java: agesVector.contains(employee.getAge())
     * SQL: AGE IN (55, 18, 30)
     * </blockquote></pre>
     */
    public Expression all(Vector theObjects) {
        return all(new ConstantExpression(theObjects, this));
    }

    public Expression all(Expression arguments) {
        ExpressionOperator anOperator = getOperator(ExpressionOperator.All);
        return anOperator.expressionFor(this, arguments);
    }

    public Expression all(ReportQuery subQuery) {
        return all(subQuery(subQuery));
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.queryframework;

import java.util.*;

import oracle.toplink.essentials.internal.descriptors.OptimisticLockingPolicy;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.logging.SessionLog;
import oracle.toplink.essentials.mappings.DatabaseMapping;
import oracle.toplink.essentials.mappings.DirectCollectionMapping;
import oracle.toplink.essentials.mappings.ManyToManyMapping;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.mappings.OneToOneMapping;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.descriptors.InheritancePolicy;
import oracle.toplink.essentials.descriptors.DescriptorQueryManager;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>:
 * Mechanism used for all expression read queries.
 * ExpressionQueryInterface understands how to deal with expressions.
 * <p>
 * <p><b>Responsibilities</b>:
 * Translates the expression and creates the appropriate SQL statements.
 * Retrieves the data from the database and return the results to the query.
 *
 * @author Yvon Lavoie
 * @since TOPLink/Java 1.0
 */
public class ExpressionQueryMechanism extends StatementQueryMechanism {
    protected Expression selectionCriteria;

    /**
     * Initialize the state of the query
     * @param query - owner of mechanism
     */
    public ExpressionQueryMechanism(DatabaseQuery query) {
        super(query);
    }

    /**
     * Initialize the state of the query
     * @param query - owner of mechanism
     * @param expression - selection criteria
     */
    public ExpressionQueryMechanism(DatabaseQuery query, Expression expression) {
        super(query);
        this.selectionCriteria = expression;
    }

    /**
     * Alias the supplied fields with respect to the expression node. Return copies of the fields
     */
    protected Vector aliasFields(ObjectExpression node, Vector fields) {
        Vector result = new Vector(fields.size());

        for (Enumeration e = fields.elements(); e.hasMoreElements();) {
            DatabaseField eachField = (DatabaseField)((DatabaseField)e.nextElement()).clone();
            eachField.setTable(node.aliasForTable(eachField.getTable()));
            result.addElement(eachField);
        }

        return result;
    }

    /**
     * If the fields in the statement have breen pre-set, e.g. for a subset of the fields
     * in a partial attribute read, report query, or just a query for the class indicator,
     * then try to alias those. Right now this just guesses that they're all from the base.
     */
    public Vector aliasPresetFields(SQLSelectStatement statement) {
        Vector fields = statement.getFields();
        Expression exp = statement.getWhereClause();

        if (exp == null) {
            return fields;
        } else {
            ExpressionBuilder base = exp.getBuilder();
            return aliasFields(base, fields);
        }
    }

    /**
     * Create the appropriate where clause.
     * Since this is where the selection criteria gets cloned for the first time
     * (long after the owning query has been) many interesting things happen here.
     */
    public Expression buildBaseSelectionCriteria(boolean isSubSelect, Dictionary clonedExpressions) {
        Expression expression = getSelectionCriteria();

        // For Flashback: builder.asOf(value) counts as a non-trivial selection criteria.
        // Also for bug 2612185 try to preserve the original builder as far as possible.
        if ((expression == null) && getQuery().isObjectLevelReadQuery()) {
            expression = ((ObjectLevelReadQuery)getQuery()).getExpressionBuilder();
        }

        // Subselects are not cloned, as they are cloned in the context of the parent expression.
        if ((!isSubSelect) && (expression != null)) {
            // For bug 2612185 specify the identity hashtable to be used in cloning so
            // it is not thrown away at the end of cloning.
            expression = expression.copiedVersionFrom(clonedExpressions);
        }

        DescriptorQueryManager queryManager = getDescriptor().getQueryManager();

        
        // Leaf inheritence and multiple table join.
        if (queryManager.getAdditionalJoinExpression() != null) {
            // CR#3701077, additional join not required for view, view does join.
            if (! (getDescriptor().hasInheritance() && (getDescriptor().getInheritancePolicy().hasView()))) {
                Expression additionalJoin = (Expression)queryManager.getAdditionalJoinExpression();
    
                // If there's an expression, then we know we'll have to rebuild anyway, so don't clone.
                if (expression == null) {
                    // Should never happen...
                    expression = (Expression)additionalJoin.clone();
                } else {
                    if (query.isObjectLevelReadQuery()){
                        ExpressionBuilder builder = ((ObjectLevelReadQuery)query).getExpressionBuilder();
                        if ((additionalJoin.getBuilder() != builder) && (additionalJoin.getBuilder().getQueryClass() == null)) {
                            if ((!isSubSelect) && (builder != null)) {
                                builder = (ExpressionBuilder)builder.copiedVersionFrom(clonedExpressions);
                            }
                            additionalJoin = additionalJoin.rebuildOn(builder);
                        }
                    }
                    expression = expression.and(additionalJoin);
                }
                // set wasAdditionalJoinCriteriaUsed on the addionalJoin because the expression may not have the correct builder as its left most builder
                additionalJoin.getBuilder().setWasAdditionJoinCriteriaUsed(true);
            }
        }

        return expression;
    }

    /**
     * Return the appropriate select statement containing the fields in the table.
     */
    public SQLSelectStatement buildBaseSelectStatement(boolean isSubSelect, Dictionary clonedExpressions) {
        SQLSelectStatement selectStatement = new SQLSelectStatement();
        selectStatement.setLockingClause(((ObjectLevelReadQuery)getQuery()).getLockingClause());
        selectStatement.setDistinctState(((ObjectLevelReadQuery)getQuery()).getDistinctState());
        selectStatement.setTables((Vector)getDescriptor().getTables().clone());
        selectStatement.setWhereClause(buildBaseSelectionCriteria(isSubSelect, clonedExpressions));
        if (getQuery().isReadAllQuery() && ((ReadAllQuery)getQuery()).hasOrderByExpressions()) {
            //For bug 2641, the clone of the OrderBy expressions needs to be used to ensure they are normalized
            //every time when select SQL statement gets re-prepared, which will further guarantee the calculation
            //of table alias always be correct
            selectStatement.setOrderByExpressions(cloneExpressions(((ReadAllQuery)getQuery()).getOrderByExpressions(),clonedExpressions));
        }
        selectStatement.setTranslationRow(getTranslationRow());
        return selectStatement;
    }

    /**
     * Return the appropriate select statement containing the fields in the table.
     * This is used as a second read to a concrete class with subclasses in an abstract-multiple table read.
     */
    protected SQLSelectStatement buildConcreteSelectStatement() {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        HardIdentityHashtable clonedExpressions = new HardIdentityHashtable();
        SQLSelectStatement selectStatement = buildBaseSelectStatement(false, clonedExpressions);

        // Case of class with subclasses that also has instances on abstract-multiple table read.
        if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().shouldReadSubclasses()) {
            Expression concrete = (Expression)getDescriptor().getInheritancePolicy().getOnlyInstancesExpression();
            if ((concrete != null) && (selectStatement.getWhereClause() != null)) {
                selectStatement.setWhereClause(selectStatement.getWhereClause().and(concrete));
            } else if (concrete != null) {
                selectStatement.setWhereClause((Expression)concrete.clone());
            }
        }

        selectStatement.setFields(getSelectionFields(selectStatement, false));
        selectStatement.normalize(getSession(), getDescriptor(), clonedExpressions);
        // Allow for joining indexes to be computed to ensure distinct rows
        ((ObjectLevelReadQuery)getQuery()).getJoinedAttributeManager().computeJoiningMappingIndexes(false, getSession(), 0);

        return selectStatement;
    }

    /**
     * Return the appropriate delete statement
     * Passing of a call/ statement pair is used because the same pair
     * may be used several times.
     * More elegant orangement of passing just a statement and creating the call
     * in the method was rejected because the same call would've been potentially
     * re-created several times.
     * Preconditions:
     * if selectCallForExist != null then selectStatementForExist != null;
     * if selectCallForNotExist != null then selectStatementForNotExist != null.
     * @return SQLDeleteStatement
     */
    protected SQLDeleteStatement buildDeleteAllStatement(DatabaseTable table, Expression inheritanceExpression,
                SQLCall selectCallForExist, SQLSelectStatement selectStatementForExist,
                SQLCall selectCallForNotExist, SQLSelectStatement selectStatementForNotExist,
                Collection primaryKeyFields) {
        if(selectCallForExist == null && selectCallForNotExist == null) {
            return buildDeleteStatementForDeleteAllQuery(table, inheritanceExpression);
        }
        
        SQLDeleteAllStatement deleteAllStatement = new SQLDeleteAllStatement();
        deleteAllStatement.setTable(table);
        deleteAllStatement.setTranslationRow(getTranslationRow());

        if(selectCallForExist != null) {
            deleteAllStatement.setSelectCallForExist(selectCallForExist);
            // if selectStatementForExist doesn't require aliasing and targets the same
            // table as the statement to be built,
            // then instead of creating sql with "WHERE EXISTS("
            // sql is created by extracting where clause from selectStatementForExist,
            // for instance:
            // DELETE FROM PROJECT WHERE (PROJ_NAME = ?)
            // instead of the wrong one:
            // DELETE FROM PROJECT WHERE EXISTS(SELECT PROJ_ID FROM PROJECT WHERE (PROJ_NAME = ?) AND PROJECT.PROJ_ID = PROJECT.PROJ_ID)
            deleteAllStatement.setShouldExtractWhereClauseFromSelectCallForExist(!selectStatementForExist.requiresAliases() && table.equals(selectStatementForExist.getTables().firstElement()));
            deleteAllStatement.setTableAliasInSelectCallForExist(getAliasTableName(selectStatementForExist, table));
        } else {
            // inheritanceExpression is irrelevant in case selectCallForExist != null
            if(inheritanceExpression != null) {
                deleteAllStatement.setInheritanceExpression((Expression)inheritanceExpression.clone());
            }
        }

        if(selectCallForNotExist != null) {
            deleteAllStatement.setSelectCallForNotExist(selectCallForNotExist);
            deleteAllStatement.setTableAliasInSelectCallForNotExist(getAliasTableName(selectStatementForNotExist, table));
        }

        deleteAllStatement.setPrimaryKeyFieldsForAutoJoin(primaryKeyFields);

        return deleteAllStatement;
    }
    
    protected SQLDeleteStatement buildDeleteAllStatementForMapping(SQLCall selectCallForExist, SQLSelectStatement selectStatementForExist, Vector sourceFields, Vector targetFields) {
        DatabaseTable targetTable = ((DatabaseField)targetFields.firstElement()).getTable();
        if(selectCallForExist == null) {
            return buildDeleteStatementForDeleteAllQuery(targetTable);
        }

        SQLDeleteAllStatement deleteAllStatement = new SQLDeleteAllStatement();
        
        deleteAllStatement.setTable(targetTable);
        deleteAllStatement.setTranslationRow(getTranslationRow());

        deleteAllStatement.setSelectCallForExist(selectCallForExist);
        DatabaseTable sourceTable = ((DatabaseField)sourceFields.firstElement()).getTable();
        if(selectStatementForExist != null) {
            deleteAllStatement.setTableAliasInSelectCallForExist(getAliasTableName(selectStatementForExist, sourceTable));
        }

        deleteAllStatement.setAliasedFieldsForJoin(sourceFields);
        deleteAllStatement.setOriginalFieldsForJoin(targetFields);

        return deleteAllStatement;
    }
    
    protected static String getAliasTableName(SQLSelectStatement selectStatement, DatabaseTable table) {
        if(!selectStatement.requiresAliases()) {
            return null;
        }
        HashSet aliasTables = new HashSet();
        Iterator itEntries = selectStatement.getTableAliases().entrySet().iterator();
        DatabaseTable aliasTable = null;
        while(itEntries.hasNext()) {
            Map.Entry entry = (Map.Entry)itEntries.next();
            if(table.equals(entry.getValue())) {
                aliasTable = (DatabaseTable)entry.getKey();
                aliasTables.add(aliasTable);
            }
        }
        if(aliasTables.isEmpty()) {
            return null;
        } else if(aliasTables.size() == 1) {
            return aliasTable.getQualifiedName();
        }
        // The table has several aliases,
        // remove the aliases that used by DataExpressions
        // with baseExpression NOT the expressionBuilder used by the statement
        ExpressionIterator expIterator = new ExpressionIterator() {
            public void iterate(Expression each) {
                if(each instanceof DataExpression) {
                    DataExpression dataExpression = (DataExpression)each;
                    DatabaseField field = dataExpression.getField();
                    if(field != null) {
                        if(dataExpression.getBaseExpression() != getStatement().getBuilder()) {
                            ((Collection)getResult()).remove(dataExpression.getAliasedField().getTable());
                        }
                    }
                }
            }
            public boolean shouldIterateOverSubSelects() {
                return true;
            }
        };

        expIterator.setStatement(selectStatement);
        expIterator.setResult(aliasTables);
        expIterator.iterateOn(selectStatement.getWhereClause());
        
        if(aliasTables.size() == 1) {
            aliasTable = (DatabaseTable)aliasTables.iterator().next();
            return aliasTable.getQualifiedName();
        } else if(aliasTables.isEmpty()) {
            // should never happen
            return aliasTable.getQualifiedName();
        } else {
            // should never happen
            aliasTable = (DatabaseTable)aliasTables.iterator().next();
            return aliasTable.getQualifiedName();
        }
    }
    
    /**
     * Used by DeleteAllQuery to create DeleteStatement in a simple case
     * when selectionCriteria==null.
     */
    protected SQLDeleteStatement buildDeleteStatementForDeleteAllQuery(DatabaseTable table) {
        return buildDeleteStatementForDeleteAllQuery(table, null);
    }

    /**
     * Used by DeleteAllQuery to create DeleteStatement in a simple case
     * when selectionCriteria==null.
     */
    protected SQLDeleteStatement buildDeleteStatementForDeleteAllQuery(DatabaseTable table, Expression inheritanceExpression) {
        SQLDeleteStatement deleteStatement = new SQLDeleteStatement();

        if(inheritanceExpression != null) {
            deleteStatement.setWhereClause((Expression)inheritanceExpression.clone());
        }
        deleteStatement.setTable(table);
        deleteStatement.setTranslationRow(getTranslationRow());
        return deleteStatement;
    }

    /**
     * Return the appropriate delete statement
     */
    protected SQLDeleteStatement buildDeleteStatement(DatabaseTable table) {
        SQLDeleteStatement deleteStatement = new SQLDeleteStatement();
        Expression whereClause;
        whereClause = getDescriptor().getObjectBuilder().buildDeleteExpression(table, getTranslationRow());

        deleteStatement.setWhereClause(whereClause);
        deleteStatement.setTable(table);
        deleteStatement.setTranslationRow(getTranslationRow());
        return deleteStatement;
    }

    /**
     * Return the appropriate insert statement
     */
    protected SQLInsertStatement buildInsertStatement(DatabaseTable table) {
        SQLInsertStatement insertStatement = new SQLInsertStatement();
        insertStatement.setTable(table);
        insertStatement.setModifyRow(getModifyRow());
        return insertStatement;
    }

    /**
     * Return the appropriate select statement containing the fields in the table.
     */
    protected SQLSelectStatement buildNormalSelectStatement() {
        // From bug 2612185 Remember the identity hashtable used in cloning the selection criteria even in the normal case
        // for performance, in case subqueries need it, or for order by expressions.
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        HardIdentityHashtable clonedExpressions = new HardIdentityHashtable();
        SQLSelectStatement selectStatement = buildBaseSelectStatement(false, clonedExpressions);

        // Case, normal read for branch inheritence class that reads subclasses all in its own table(s).
        if (getDescriptor().hasInheritance()) {
            getDescriptor().getInheritancePolicy().appendWithAllSubclassesExpression(selectStatement);
        }

        selectStatement.setFields(getSelectionFields(selectStatement, true));
        selectStatement.setNonSelectFields(getNonSelectionFields());
        selectStatement.normalize(getSession(), getDescriptor(), clonedExpressions);
        // Allow for joining indexes to be computed to ensure distinct rows
        ((ObjectLevelReadQuery)getQuery()).getJoinedAttributeManager().computeJoiningMappingIndexes(true, getSession(),0);

        return selectStatement;
    }

    /**
     * Return the appropriate select statement containing the fields in the table.
     * Similar to super except the buildBaseSelectStatement will look after setting
     * the fields to select.
     */
    protected SQLSelectStatement buildReportQuerySelectStatement(boolean isSubSelect) {
        return buildReportQuerySelectStatement(isSubSelect, false, null);
    }
    /**
     * Customary inheritance expression is required for DeleteAllQuery and UpdateAllQuery preparation.
     */
    protected SQLSelectStatement buildReportQuerySelectStatement(boolean isSubSelect, boolean useCustomaryInheritanceExpression, Expression inheritanceExpression) {
        // For bug 2612185: Need to know which original bases were mapped to which cloned bases.
        // Note: subSelects are already cloned so this table is not needed.
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        HardIdentityHashtable clonedExpressions = isSubSelect ? null : new HardIdentityHashtable();
        SQLSelectStatement selectStatement = buildBaseSelectStatement(isSubSelect, clonedExpressions);
        selectStatement.setGroupByExpressions(((ReportQuery)getQuery()).getGroupByExpressions());
        selectStatement.setHavingExpression(((ReportQuery)getQuery()).getHavingExpression());
        if (getDescriptor().hasInheritance()) {
            if(useCustomaryInheritanceExpression) {
                if(inheritanceExpression != null) {
                    if (selectStatement.getWhereClause() == null) {
                        selectStatement.setWhereClause((Expression)inheritanceExpression.clone());
                    } else {
                        selectStatement.setWhereClause(selectStatement.getWhereClause().and(inheritanceExpression));
                    }
                }
            } else {
                getDescriptor().getInheritancePolicy().appendWithAllSubclassesExpression(selectStatement);
            }
        }
        Vector fieldExpressions = ((ReportQuery)getQuery()).getQueryExpressions();
        int itemOffset = fieldExpressions.size();
        for (Iterator items = ((ReportQuery)getQuery()).getItems().iterator(); items.hasNext();){
            ReportItem item = (ReportItem) items.next();
            if(item.isContructorItem()){
                ConstructorReportItem citem= (ConstructorReportItem)item;
                List reportItems = citem.getReportItems();
                int size = reportItems.size();
                for(int i=0;i<size;i++){
                    item = (ReportItem)reportItems.get(i);
                    extractStatementFromItem( item, clonedExpressions, selectStatement, fieldExpressions );
                }
            }
            else{
                extractStatementFromItem( item, clonedExpressions, selectStatement, fieldExpressions );
            }
        }
        
        selectStatement.setFields(fieldExpressions);
        selectStatement.setNonSelectFields(((ReportQuery)getQuery()).getNonFetchJoinAttributeExpressions());
        
        // Subselects must be normalized in the context of the parent statement.
        if (!isSubSelect) {
            selectStatement.normalize(getSession(), getDescriptor(), clonedExpressions);
        }

        //calculate indexes after normalize to insure expressions are set up correctly
        for (Iterator items = ((ReportQuery)getQuery()).getItems().iterator(); items.hasNext();){
            ReportItem item = (ReportItem) items.next();
            
            if(item.isContructorItem()){
                ConstructorReportItem citem= (ConstructorReportItem)item;
                List reportItems = citem.getReportItems();
                int size = reportItems.size();
                for(int i=0;i<size;i++){
                    item = (ReportItem)reportItems.get(i);
                    itemOffset = computeAndSetItemOffset(item, itemOffset);
                }
            }
            else{
                itemOffset = computeAndSetItemOffset(item, itemOffset);
            }
        }

        return selectStatement;
    }
    
    
    /**
     * calculate indexes for an item, given the current Offset
     */
    protected int computeAndSetItemOffset(ReportItem item, int itemOffset){
        item.setResultIndex(itemOffset);
        if (item.getAttributeExpression() != null){
            JoinedAttributeManager joinManager = item.getJoinedAttributeManager();
            if (joinManager.hasJoinedExpressions()){
                itemOffset = joinManager.computeJoiningMappingIndexes(true, getSession(),itemOffset);
            }else{
                if (item.getDescriptor() != null){
                    itemOffset += item.getDescriptor().getAllFields().size();
                }else {
                    ++itemOffset; //only a single attribute can be selected
                }
            }
        }
        return itemOffset;
    }
    
    public void extractStatementFromItem(ReportItem item,HardIdentityHashtable clonedExpressions,SQLSelectStatement selectStatement,Vector fieldExpressions ){
        if (item.getAttributeExpression() != null){
                // this allows us to modify the item expression without modifying the original in case of re-prepare
                Expression attributeExpression = item.getAttributeExpression();
                ExpressionBuilder clonedBuilder = attributeExpression.getBuilder();
                if (clonedBuilder.wasQueryClassSetInternally() && ((ReportQuery)getQuery()).getExpressionBuilder() != clonedBuilder){
                    // no class specified so use statement builder as it is non-parallel
                    // must have same builder as it will be initialized
                    clonedBuilder = selectStatement.getBuilder();
                    attributeExpression = attributeExpression.rebuildOn(clonedBuilder);
                }else if (clonedExpressions != null && clonedExpressions.get(clonedBuilder) != null) {
                    Expression cloneExpression = (Expression)clonedExpressions.get(attributeExpression);
                    if ((cloneExpression != null) && !cloneExpression.isExpressionBuilder()) {
                        attributeExpression = cloneExpression;
                    } else {
                        //The builder has been cloned ensure that the cloned builder is used
                        //in the items.
                        clonedBuilder = (ExpressionBuilder)clonedBuilder.copiedVersionFrom(clonedExpressions);
                        attributeExpression = attributeExpression.rebuildOn(clonedBuilder);
                    }
                }
                if (attributeExpression.isExpressionBuilder()
                    && item.getDescriptor().getQueryManager().getAdditionalJoinExpression() != null
                    && !(((ExpressionBuilder)clonedBuilder).wasAdditionJoinCriteriaUsed()) ){
                    if (selectStatement.getWhereClause() == null ) {
                        selectStatement.setWhereClause(item.getDescriptor().getQueryManager().getAdditionalJoinExpression().rebuildOn(clonedBuilder));
                    } else {
                        selectStatement.setWhereClause(selectStatement.getWhereClause().and(item.getDescriptor().getQueryManager().getAdditionalJoinExpression().rebuildOn(clonedBuilder)));
                    }
                }
                fieldExpressions.add(attributeExpression);
                JoinedAttributeManager joinManager = item.getJoinedAttributeManager();
                if (joinManager.hasJoinedExpressions()){
                    fieldExpressions.addAll(joinManager.getJoinedAttributeExpressions());
                    fieldExpressions.addAll(joinManager.getJoinedMappingExpressions());
                }
        }
    }

    /**
     * Return the appropriate select statement to perform a does exist check
     * @param fields - fields for does exist check.
     */
    protected SQLSelectStatement buildSelectStatementForDoesExist(DatabaseField field) {
        // Build appropriate select statement
        SQLSelectStatement selectStatement;
        selectStatement = new SQLSelectStatement();
        selectStatement.addField(field);
        selectStatement.setWhereClause(((Expression)getDescriptor().getObjectBuilder().getPrimaryKeyExpression().clone()).and(getDescriptor().getQueryManager().getAdditionalJoinExpression()));
        selectStatement.setTranslationRow(getTranslationRow());

        selectStatement.normalize(getSession(), getQuery().getDescriptor());
        return selectStatement;
    }

    protected SQLUpdateAllStatement buildUpdateAllStatement(DatabaseTable table,
                HashMap databaseFieldsToValues,
                SQLCall selectCallForExist, SQLSelectStatement selectStatementForExist,
                Collection primaryKeyFields)
    {
        SQLUpdateAllStatement updateAllStatement = new SQLUpdateAllStatement();
        updateAllStatement.setTable(table);
        updateAllStatement.setTranslationRow(getTranslationRow());

        HashMap databaseFieldsToValuesCopy = new HashMap(databaseFieldsToValues.size());
        HashMap databaseFieldsToTableAliases = null;
        Iterator it = databaseFieldsToValues.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            // for each table to be updated
            DatabaseField field = (DatabaseField)entry.getKey();
            // here's a Map of left hand fields to right hand expressions
            Object value = entry.getValue();
            if(value instanceof SQLSelectStatement) {
                SQLSelectStatement selStatement = (SQLSelectStatement)value;
                SQLCall selCall = (SQLCall)selStatement.buildCall(getSession());
                databaseFieldsToValuesCopy.put(field, selCall);
                if(databaseFieldsToTableAliases == null) {
                    databaseFieldsToTableAliases = new HashMap();
                    updateAllStatement.setPrimaryKeyFieldsForAutoJoin(primaryKeyFields);
                }
                databaseFieldsToTableAliases.put(field, getAliasTableName(selStatement, table));
            } else {
                // should be Expression
                databaseFieldsToValuesCopy.put(field, value);
            }
        }
        updateAllStatement.setUpdateClauses(databaseFieldsToValuesCopy);
        updateAllStatement.setDatabaseFieldsToTableAliases(databaseFieldsToTableAliases);
        
        updateAllStatement.setSelectCallForExist(selectCallForExist);
        updateAllStatement.setShouldExtractWhereClauseFromSelectCallForExist(!selectStatementForExist.requiresAliases() && table.equals(selectStatementForExist.getTables().firstElement()));
        updateAllStatement.setTableAliasInSelectCallForExist(getAliasTableName(selectStatementForExist, table));
        updateAllStatement.setPrimaryKeyFieldsForAutoJoin(primaryKeyFields);

        return updateAllStatement;
    }
    
    /**
     * Return the appropriate update statement
     * @return SQLInsertStatement
     */
    protected SQLUpdateStatement buildUpdateStatement(DatabaseTable table) {
        SQLUpdateStatement updateStatement = new SQLUpdateStatement();

        updateStatement.setModifyRow(getModifyRow());
        updateStatement.setTranslationRow(getTranslationRow());
        updateStatement.setTable(table);
        updateStatement.setWhereClause(getDescriptor().getObjectBuilder().buildUpdateExpression(table, getTranslationRow(), getModifyRow()));
        return updateStatement;
    }

    /**
     * Perform a cache lookup for the query
     * This is only called from read object query.
     * The query has already checked that the cache should be checked.
     */
    public Object checkCacheForObject(AbstractRecord translationRow, AbstractSession session) {
        // For bug 2782991 a list of nearly 20 problems with this method have
        // been fixed.
        ReadObjectQuery query = getReadObjectQuery();
        boolean conforming = ((query.shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork()) && session.isUnitOfWork());

        // Set the in memory query policy automatically for conforming queries, unless the
        // user specifies the most cautious one.
        InMemoryQueryIndirectionPolicy policyToUse = query.getInMemoryQueryIndirectionPolicy();
        if (conforming && !policyToUse.shouldTriggerIndirection()) {
            policyToUse = new InMemoryQueryIndirectionPolicy();
            policyToUse.ignoreIndirectionExceptionReturnNotConformed();
        }
        Object cachedObject = null;
        Vector selectionKey = null;
        Expression selectionCriteria = getSelectionCriteria();

        // Perform a series of cache checks, in the following order...
        // 1: If selection key or selection object, lookup by primary key.
        // 2: If selection criteria null, take the first instance in cache.
        // 3: If exact primary key expression, lookup by primary key.
        // 4: If inexact primary key expression, lookup by primary key and see if it conforms.
        // 5: Perform a linear search on the cache, calling doesConform on each object.
        // 6: (Conforming) Search through new objects.
        // Each check is more optimal than the next.
        // Finally: (Conforming) check that any positive result was not deleted in the UnitOfWork.
        // 1: If selection key or selection object, do lookup by primary key.
        //
        if ((query.getSelectionKey() != null) || (query.getSelectionObject() != null)) {
            if ((query.getSelectionKey() == null) && (query.getSelectionObject() != null)) {
                // Must be checked seperately as the expression and row is not yet set.
                query.setSelectionKey(getDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(query.getSelectionObject(), session));
            }
            if (query.getSelectionKey() != null) {
                selectionKey = query.getSelectionKey();
                if (getDescriptor().shouldAcquireCascadedLocks()) {
                    cachedObject = session.getIdentityMapAccessorInstance().getFromIdentityMapWithDeferredLock(selectionKey, query.getReferenceClass(), false, getDescriptor());
                } else {
                    cachedObject = session.getIdentityMapAccessorInstance().getFromIdentityMap(selectionKey, query.getReferenceClass(), false, getDescriptor());
                }
            }
        } else {
            // 2: If selection criteria null, take any instance in cache.
            //
            if (selectionCriteria == null) {
                // In future would like to always return something from cache.
                if (query.shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork() || query.shouldCheckCacheOnly() || query.shouldCheckCacheThenDatabase()) {
                    cachedObject = session.getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(null, query.getReferenceClass(), translationRow, policyToUse, conforming, false, getDescriptor());
                }
            } else {
                // 3: If can extract exact primary key expression, do lookup by primary key.
                //
                selectionKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromExpression(true, selectionCriteria, translationRow, session);

                // If an exact primary key was extracted or should check cache by exact
                // primary key only this will become the final check.
                if ((selectionKey != null) || query.shouldCheckCacheByExactPrimaryKey()) {
                    if (selectionKey != null) {
                        if (getDescriptor().shouldAcquireCascadedLocks()) {
                            cachedObject = session.getIdentityMapAccessorInstance().getFromIdentityMapWithDeferredLock(selectionKey, query.getReferenceClass(), false, getDescriptor());
                        } else {
                            cachedObject = session.getIdentityMapAccessorInstance().getFromIdentityMap(selectionKey, query.getReferenceClass(), false, getDescriptor());
                        }
                    }
                    // Because it was exact primary key if the lookup failed then it is not there.
                } else {
                    // 4: If can extract inexact primary key, find one object by primary key and
                    // check if it conforms. Failure of this object to conform however does not
                    // rule out a cache hit.
                    //
                    Vector inexactSelectionKey = getDescriptor().getObjectBuilder().extractPrimaryKeyFromExpression(false, selectionCriteria, translationRow, session);// Check for any primary key in expression, may have other stuff.
                    if (inexactSelectionKey != null) {
                        // PERF: Only use deferred lock when required.
                        if (getDescriptor().shouldAcquireCascadedLocks()) {
                            cachedObject = session.getIdentityMapAccessorInstance().getFromIdentityMapWithDeferredLock(inexactSelectionKey, query.getReferenceClass(), false, getDescriptor());
                        } else {
                            cachedObject = session.getIdentityMapAccessorInstance().getFromIdentityMap(inexactSelectionKey, query.getReferenceClass(), false, getDescriptor());
                        }
                        if (cachedObject != null) {
                            // Must ensure that it matches the expression.
                            try {
                                // PERF: 3639015 - cloning the expression no longer required
                                // when using the root session
                                selectionCriteria.getBuilder().setSession(session.getRootSession(query));
                                selectionCriteria.getBuilder().setQueryClass(getDescriptor().getJavaClass());
                                if (!selectionCriteria.doesConform(cachedObject, session, translationRow, policyToUse)) {
                                    cachedObject = null;
                                }
                            } catch (QueryException exception) {// Ignore if expression too complex.
                                if (query.shouldCheckCacheOnly()) {// Throw on only cache.
                                    throw exception;
                                }
                                cachedObject = null;
                            }
                        }
                    }

                    // 5: Perform a linear search of the cache, calling expression.doesConform on each element.
                    // This is a last resort linear time search of the identity map.
                    // This can be avoided by setting check cache by (inexact/exact) primary key on the query.
                    // That flag becomes invalid in the conforming case (bug 2609611: SUPPORT CONFORM RESULT IN UOW IN CONJUNCTION WITH OTHER IN-MEMORY FEATURES)
                    // so if conforming must always do this linear search, but at least only on
                    // objects registered in the UnitOfWork.
                    //
                    boolean conformingButOutsideUnitOfWork = ((query.shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork()) && !session.isUnitOfWork());
                    if ((cachedObject == null) && (conforming || (!query.shouldCheckCacheByPrimaryKey() && !conformingButOutsideUnitOfWork))) {
                        // PERF: 3639015 - cloning the expression no longer required
                        // when using the root session
                        if (selectionCriteria != null) {
                            selectionCriteria.getBuilder().setSession(session.getRootSession(query));
                            selectionCriteria.getBuilder().setQueryClass(getDescriptor().getJavaClass());
                        }
                        try {
                            cachedObject = session.getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(selectionCriteria, query.getReferenceClass(), translationRow, policyToUse, conforming, false, getDescriptor());
                        } catch (QueryException exception) {// Ignore if expression too complex.
                            if (query.shouldCheckCacheOnly()) {// Throw on only cache.
                                throw exception;
                            }
                        }
                    }
                }
            }
        }
        if (conforming) {
            // 6: If unit of work search through new objects.
            //
            // PERF: 3639015 - cloning the expression no longer required
            // when using the root session
            if (selectionCriteria != null) {
                selectionCriteria.getBuilder().setSession(session.getRootSession(query));
                selectionCriteria.getBuilder().setQueryClass(getDescriptor().getJavaClass());
            }
            if (cachedObject == null) {
                try {
                    if (selectionKey != null) {
                        if (!((UnitOfWorkImpl)session).shouldNewObjectsBeCached()) {
                            cachedObject = ((UnitOfWorkImpl)session).getObjectFromNewObjects(query.getReferenceClass(), selectionKey);
                        }
                    } else {
                        cachedObject = ((UnitOfWorkImpl)session).getObjectFromNewObjects(selectionCriteria, query.getReferenceClass(), translationRow, policyToUse);
                    }
                } catch (QueryException exception) {
                }
                // Ignore if expression too complex.
            }

            // Finally, check that a positive result is not deleted in the Unit Of Work.
            //
            if (cachedObject != null) {
                if (((UnitOfWorkImpl)session).isObjectDeleted(cachedObject)) {
                    if (selectionKey != null) {
                        // In this case return a special value, to notify TopLink
                        // that the object was found but null must be returned.
                        return InvalidObject.instance;
                    } else {
                        cachedObject = null;
                    }
                }
            }
        }

        //fetch group check
        if (getDescriptor().hasFetchGroupManager()) {
            if (getDescriptor().getFetchGroupManager().isPartialObject(cachedObject)) {
                if (!getDescriptor().getFetchGroupManager().isObjectValidForFetchGroup(cachedObject, getReadObjectQuery().getFetchGroup())) {
                    //the cached object is patially fetched, and it's fetch group is not a superset of the one in the query, so the cached object is not valid for the query.
                    cachedObject = null;
                }
            }
        }

        return cachedObject;
    }

    /**
     * Clone the mechanism for the specified query clone.
     * Should not try to clone statements.
     */
    public DatabaseQueryMechanism clone(DatabaseQuery queryClone) {
        DatabaseQueryMechanism clone = (DatabaseQueryMechanism)clone();
        clone.setQuery(queryClone);
        return clone;
    }

    /**
     * Return an expression builder which is valid for us
     */
    public ExpressionBuilder getExpressionBuilder() {
        if (getSelectionCriteria() != null) {
            return getSelectionCriteria().getBuilder();
        }
        return null;
    }

    /**
     * Return the selection criteria of the query.
     */
    public Expression getSelectionCriteria() {
        return selectionCriteria;
    }

    /**
     * Return the fields required in the select clause.
     * This must now be called after normalization, so it will get the aliased fields
     */
    public Vector getSelectionFields(SQLSelectStatement statement, boolean includeAllSubclassFields) {
        ObjectLevelReadQuery owner = (ObjectLevelReadQuery)getQuery();
        //fetch group support
        if (owner.hasFetchGroupAttributeExpressions()) {
            Vector fields = new Vector(owner.getFetchGroup().getFetchGroupAttributeExpressions().size());
            for (Iterator attrExprIter = owner.getFetchGroup().getFetchGroupAttributeExpressions().iterator();
                     attrExprIter.hasNext();) {
                Expression fetchGroupAttrExpr = (Expression)attrExprIter.next();
                fields.add(fetchGroupAttrExpr);
            }

            // Add additional fields, use for fetch group version locking field retrieval.
            Helper.addAllToVector(fields, owner.getAdditionalFields());
            return fields;
        }

        ExpressionBuilder base = statement.getExpressionBuilder();

        // All fields are required if all subclass field are need, this is for view selects, or where parent and children share a single table.
        Vector fields;
        if (includeAllSubclassFields) {
            fields = (Vector)getDescriptor().getAllFields().clone();
        } else {
            fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            fields.addElement(base);
        }

        // Add joined fields.
        fields.addAll(owner.getJoinedAttributeManager().getJoinedAttributeExpressions());
        fields.addAll(owner.getJoinedAttributeManager().getJoinedMappingExpressions());

        // Add additional fields, use for batch reading m-m.
        fields.addAll(owner.getAdditionalFields());
        return fields;
    }
    
    /**
     * Return the fields required in the from and where clause (join). These
     * fields will not apprear in the select clause.
     */
    public Vector getNonSelectionFields() {
        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        // Add non fetch joined fields.
        fields.addAll(((ObjectLevelReadQuery) getQuery()).getNonFetchJoinAttributeExpressions());
        
        return fields;
    }

    /**
     * Return true if this is an expression query mechanism.
     */
    public boolean isExpressionQueryMechanism() {
        return true;
    }

    /**
     * Return true if this is a statement query mechanism
     */
    public boolean isStatementQueryMechanism() {
        return false;
    }

    /**
     * Override super to do nothing.
     */
    public void prepare() throws QueryException {
        // Do nothing.
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareCursorSelectAllRows() {
        if (getQuery().isReportQuery()) {
            SQLSelectStatement statement = buildReportQuerySelectStatement(false);
            setSQLStatement(statement);
            // For bug 2718118 inheritance with cursors is supported provided there is a read all subclasses view.
        } else if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().requiresMultipleTableSubclassRead() && getDescriptor().getInheritancePolicy().hasView()) {
            InheritancePolicy inheritancePolicy = getDescriptor().getInheritancePolicy();
            SQLSelectStatement statement = inheritancePolicy.buildViewSelectStatement((ObjectLevelReadQuery)getQuery());
            setSQLStatement(statement);
        } else {
            setSQLStatement(buildNormalSelectStatement());
        }

        super.prepareCursorSelectAllRows();
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareDeleteAll() {
        prepareDeleteAll(null);
    }
    
    /**
     * Pre-build the SQL statement from the expression.
     *
     * NOTE: A similar pattern also used in method buildDeleteAllStatementsForTempTable():
     * if you are updating this method consider applying a similar update to that method as well.
     */
    protected void prepareDeleteAll(Vector tablesToIgnore) {
        Vector tablesInInsertOrder;
        if(tablesToIgnore == null) {
            // It's original (not a nested) method call.
            tablesInInsertOrder = getDescriptor().getMultipleTableInsertOrder();
        } else {
            // It's a nested method call: tableInInsertOrder filled with descriptor's tables (in insert order),
            // the tables found in tablesToIgnore are thrown away -
            // they have already been taken care of by the caller.
            // In Employee example, query with reference class Project gets here
            // to handle LPROJECT table; tablesToIgnore contains PROJECT table.
            tablesInInsertOrder = new Vector(getDescriptor().getMultipleTableInsertOrder().size());
            for (Iterator tablesEnum = getDescriptor().getMultipleTableInsertOrder().iterator();
                     tablesEnum.hasNext();) {
                DatabaseTable table = (DatabaseTable)tablesEnum.next();
                if(!tablesToIgnore.contains(table)) {
                    tablesInInsertOrder.addElement(table);
                }
            }
        }
        
        // cache the flag - used many times
        boolean hasInheritance = getDescriptor().hasInheritance();
        
        if(!tablesInInsertOrder.isEmpty()) {
            Expression whereClause = getSelectionCriteria();
            
            SQLCall selectCallForExist = null;

            boolean isSelectCallForNotExistRequired = tablesToIgnore == null && tablesInInsertOrder.size() > 1;

            SQLSelectStatement selectStatementForNotExist = null;
            SQLCall selectCallForNotExist = null;
            
            // inheritanceExpression is always null in a nested method call.
            Expression inheritanceExpression = null;
            if(tablesToIgnore == null) {
                // It's original (not a nested) method call.
                if(hasInheritance) {
                    if(getDescriptor().getInheritancePolicy().shouldReadSubclasses()) {
                        inheritanceExpression = getDescriptor().getInheritancePolicy().getWithAllSubclassesExpression();
                    } else {
                        inheritanceExpression = getDescriptor().getInheritancePolicy().getOnlyInstancesExpression();
                    }
                }
            }
            
            SQLSelectStatement selectStatementForExist = createSQLSelectStatementForModifyAll(whereClause);
            
            // Main Case: Descriptor is mapped to more than one table and/or the query references other tables
            boolean isMainCase = selectStatementForExist.requiresAliases();
            if(isMainCase) {
                if(whereClause != null) {
                    if(getSession().getPlatform().shouldAlwaysUseTempStorageForModifyAll() && tablesToIgnore == null) {
                        // currently DeleteAll using Oracle anonymous block is not implemented
                        if(!getSession().getPlatform().isOracle()) {
                            prepareDeleteAllUsingTempStorage();
                            return;
                        }
                    }
                
                    if(isSelectCallForNotExistRequired) {
                        selectStatementForNotExist = createSQLSelectStatementForModifyAll(null, null);
                        selectCallForNotExist = (SQLCall)selectStatementForNotExist.buildCall(getSession());
                    }
                } else {
                    //whereClause = null
                    if(getSession().getPlatform().shouldAlwaysUseTempStorageForModifyAll() && tablesToIgnore == null) {
                        // currently DeleteAll using Oracle anonymous block is not implemented
                        if(!getSession().getPlatform().isOracle()) {
                            // the only case to handle without temp storage is inheritance root without inheritanceExpression:
                            // in this case all generated delete calls will have no where clauses.
                            if(hasInheritance && !(inheritanceExpression == null && getDescriptor().getInheritancePolicy().isRootParentDescriptor())) {
                                prepareDeleteAllUsingTempStorage();
                                return;
                            }
                        }
                    }
                }
            } else {
                // simple case: Descriptor is mapped to a single table and the query references no other tables.
                if(whereClause != null) {
                    if(getSession().getPlatform().shouldAlwaysUseTempStorageForModifyAll() && tablesToIgnore == null) {
                        // currently DeleteAll using Oracle anonymous block is not implemented
                        if(!getSession().getPlatform().isOracle()) {
                            // if there are derived classes with additional tables - use temporary storage
                            if(hasInheritance && getDescriptor().getInheritancePolicy().hasMultipleTableChild()) {
                                prepareDeleteAllUsingTempStorage();
                                return;
                            }
                        }
                    }
                }
            }

            // Don't use selectCallForExist in case there is no whereClause -
            // a simpler sql will be created if possible.
            if(whereClause != null) {
                selectCallForExist = (SQLCall)selectStatementForExist.buildCall(getSession());
            }
            
            if(isMainCase) {
                // Main case: Descriptor is mapped to more than one table and/or the query references other tables
                //
                // Add and prepare to a call a delete statement for each table.
                // In the case of multiple tables, build the sql statements Vector in insert order. When the
                // actual SQL calls are sent they are sent in the reverse of this order.
                for (Enumeration tablesEnum = tablesInInsertOrder.elements(); tablesEnum.hasMoreElements();) {
                    DatabaseTable table = (DatabaseTable)tablesEnum.nextElement();
                    
                    Collection primaryKeyFields = getPrimaryKeyFieldsForTable(table);
                    
                    SQLDeleteStatement deleteStatement;

                    // In Employee example, query with reference class:
                    // Employee will build "EXISTS" for SALARY and "NOT EXISTS" for EMPLOYEE;
                    // LargeProject will build "EXISTS" for LPROJECT and "NOT EXISTS" for Project.
                    // The situation is a bit more complex if more than two levels of inheritance is involved:
                    // both "EXISTS" and "NOT EXISTS" used for the "intermediate" (not first and not last) tables.
                    
                    if(!isSelectCallForNotExistRequired) {
                        // isSelectCallForNotExistRequired == false:
                        // either tablesToIgnore != null: it's a nested method call.
                        // Example:
                        // In Employee example, query with reference class
                        // Project will get here to handle LPROJECT table
                        // or tablesInInsertOrder.size() == 1: there is only one table,
                        // but there is joining to at least one other table (otherwise would've been isMainCase==false).
                        //
                        // Note that buildDeleteAllStatement ignores inheritanceExpression if selectCallForExist!=null.
                        deleteStatement = buildDeleteAllStatement(table, inheritanceExpression, selectCallForExist, selectStatementForExist, null, null, primaryKeyFields);
                    } else {
                        // isSelectCallForNotExistRequired==true: original call, multiple tables.
                        
                        // indicates whether the table is the last in insertion order
                        boolean isLastTable = table.equals((DatabaseTable)tablesInInsertOrder.lastElement());
                        
                        if(inheritanceExpression == null) {
                            if(isLastTable) {
                                // In Employee example, query with reference class Employee calls this for SALARY table;
                                deleteStatement = buildDeleteAllStatement(table, null, selectCallForExist, selectStatementForExist, null, null, primaryKeyFields);
                            } else {
                                // In Employee example, query with reference class Employee calls this for EMPLOYEE table
                                deleteStatement = buildDeleteAllStatement(table, null, null, null, selectCallForNotExist, selectStatementForNotExist, primaryKeyFields);
                            }
                        } else {
                            // there is inheritance
                            if(table.equals(getDescriptor().getMultipleTableInsertOrder().firstElement())) {
                                // This is the highest table in inheritance hierarchy - the one that contains conditions
                                // (usually class indicator fields) that defines the class identity.
                                // inheritanceExpression is for this table (it doesn't reference any other tables).
                                // In Employee example, query with reference class LargeProject calls this for PROJECT table
                                deleteStatement = buildDeleteAllStatement(table, inheritanceExpression, null, null, selectCallForNotExist, selectStatementForNotExist, primaryKeyFields);
                            } else {
                                ClassDescriptor desc = getHighestDescriptorMappingTable(table);
                                if(desc == getDescriptor()) {
                                    if(isLastTable) {
                                        // In Employee example, query with reference class LargeProject calls this for LPROJECT table;
                                        deleteStatement = buildDeleteAllStatement(table, null, selectCallForExist, selectStatementForExist, null, null, primaryKeyFields);
                                    } else {
                                        // Class has multiple tables that are not inherited.
                                        // In extended Employee example:
                                        // Employee2 class inherits from Employee and
                                        // mapped to two additional tables: EMPLOYEE2 and SALARY2.
                                        // Query with reference class Employee2 calls this for EMPLOYEE2 table.
                                        deleteStatement = buildDeleteAllStatement(table, null, null, null, selectCallForNotExist, selectStatementForNotExist, primaryKeyFields);
                                    }
                                } else {
                                    // This table is mapped through descriptor that stands higher in inheritance hierarchy
                                    // (but not the highest one - this is taken care in another case).
                                    //
                                    // inheritanceSelectStatementForExist is created for the higher descriptor,
                                    // but the inheritance expression from the current descriptor is used.
                                    // Note that this trick doesn't work in case the higher descriptor was defined with
                                    // inheritance policy set not to read subclasses
                                    // (descriptor.getInheritancePolicy().dontReadSubclassesOnQueries()).
                                    // In that case inheritance expression for the higher descriptor can't
                                    // be removed - it still appears in the sql and collides with the inheritance
                                    // expression from the current descriptor - the selection expression is never true.
                                    //
                                    // In extended Employee example:
                                    // VeryLargeProject inherits from LargeProject,
                                    // mapped to an additional table VLPROJECT;
                                    // VeryVeryLargeProject inherits from VeryLargeProject,
                                    // mapped to the same tables as it's parent.
                                    //
                                    // Note that this doesn't work in case LargeProject descriptor was set not to read subclasses:
                                    // in that case the selection expression will have (PROJ_TYPE = 'L') AND (PROJ_TYPE = 'V')
                                    //
                                    SQLSelectStatement inheritanceSelectStatementForExist = createSQLSelectStatementForModifyAll(null, inheritanceExpression, desc);
                                    SQLCall inheritanceSelectCallForExist = (SQLCall)inheritanceSelectStatementForExist.buildCall(getSession());

                                    if(isLastTable) {
                                        // In extended Employee example:
                                        // Query with reference class VeryVeryLargeProject calls this for VLPROJECT table.
                                        deleteStatement = buildDeleteAllStatement(table, null, inheritanceSelectCallForExist, inheritanceSelectStatementForExist, null, null, primaryKeyFields);
                                    } else {
                                        // In extended Employee example:
                                        // Query with reference class VeryLargeProject calls this for LPROJECT table.
                                        // Note that both EXISTS and NOT EXISTS clauses created.
                                        deleteStatement = buildDeleteAllStatement(table, null, inheritanceSelectCallForExist, inheritanceSelectStatementForExist, selectCallForNotExist, selectStatementForNotExist, primaryKeyFields);
                                    }
                                }
                            }
                        }
                    }
        
                    if (getDescriptor().getTables().size() > 1) {
                        getSQLStatements().addElement(deleteStatement);
                    } else {
                        setSQLStatement(deleteStatement);
                    }
                }
            } else {
                // A simple case:
                // there is only one table mapped to the descriptor, and
                // selection criteria doesn't reference any other tables
                // A simple sql call with no subselect should be built.
                // In Employee example, query with reference class:
                // Project will build a simple sql call for PROJECT(and will make nested method calls for LargeProject and SmallProject);
                // SmallProject will build a simple sql call for PROJECT
                setSQLStatement(buildDeleteAllStatement(getDescriptor().getDefaultTable(), inheritanceExpression, selectCallForExist, selectStatementForExist, null, null, null));
            }

            if(selectCallForExist == null) {
                // Getting there means there is no whereClause.
                // To handle the mappings selectCallForExist may be required in this case, too.
                if(hasInheritance && (tablesToIgnore != null || inheritanceExpression != null)) {
                    // The only case NOT to create the call for no whereClause is either no inheritance,
                    // or it's an original (not a nested) method call and there is no inheritance expression.
                    // In Employee example:
                    // query with reference class Project and no where clause for m-to-m mapping generates:
                    // DELETE FROM EMP_PROJ;
                    // as opposed to query with reference class SmallProject:
                    // DELETE FROM EMP_PROJ WHERE EXISTS(SELECT PROJ_ID FROM PROJECT WHERE (PROJ_TYPE = ?) AND PROJ_ID = EMP_PROJ.PROJ_ID).
                    //
                    selectCallForExist = (SQLCall)selectStatementForExist.buildCall(getSession());
                }
            }

            // Add statements for ManyToMany and DirectCollection mappings
            Vector deleteStatementsForMappings = buildDeleteAllStatementsForMappings(selectCallForExist, selectStatementForExist, tablesToIgnore == null);
            if(!deleteStatementsForMappings.isEmpty()) {
                if(getSQLStatement() != null) {
                    getSQLStatements().add(getSQLStatement());
                    setSQLStatement(null);
                }
                getSQLStatements().addAll(deleteStatementsForMappings);
            }
        }

        // Indicates whether the descriptor has children using extra tables.
        boolean hasChildrenWithExtraTables = hasInheritance && getDescriptor().getInheritancePolicy().hasChildren() && getDescriptor().getInheritancePolicy().hasMultipleTableChild();

        // TBD: should we ignore subclasses in case descriptor doesn't want us to read them in?
        //** Currently in this code we do ignore.
        //** If it will be decided that we need to handle children in all cases
        //** the following statement should be changed to: boolean shouldHandleChildren = hasChildrenWithExtraTables;
        boolean shouldHandleChildren = hasChildrenWithExtraTables && getDescriptor().getInheritancePolicy().shouldReadSubclasses();

        // Perform a nested method call for each child
        if(shouldHandleChildren) {
            // In Employee example: query for Project will make nested calls to
            // LargeProject and SmallProject and ask them to ignore PROJECT table
            Vector tablesToIgnoreForChildren = new Vector();
            // The tables this descriptor has ignored, its children also should ignore.
            if(tablesToIgnore != null) {
                tablesToIgnoreForChildren.addAll(tablesToIgnore);
            }

            // If the desctiptor reads subclasses there is no need for
            // subclasses to process its tables for the second time.
            if (getDescriptor().getInheritancePolicy().shouldReadSubclasses()) {
                tablesToIgnoreForChildren.addAll(tablesInInsertOrder);
            }
            
            Iterator it = getDescriptor().getInheritancePolicy().getChildDescriptors().iterator();
            while(it.hasNext()) {
                // Define the same query for the child
                ClassDescriptor childDescriptor = (ClassDescriptor)it.next();
                
                // Need to process only "multiple tables" child descriptors
                if ((childDescriptor.getTables().size() > getDescriptor().getTables().size()) ||
                    (childDescriptor.getInheritancePolicy().hasMultipleTableChild()))
                {
                    DeleteAllQuery childQuery = new DeleteAllQuery();
                    childQuery.setReferenceClass(childDescriptor.getJavaClass());
                    childQuery.setSelectionCriteria(getSelectionCriteria());
                    childQuery.setDescriptor(childDescriptor);
                    childQuery.setSession(getSession());
                    
                    ExpressionQueryMechanism childMechanism = (ExpressionQueryMechanism)childQuery.getQueryMechanism();
                    // nested call
                    childMechanism.prepareDeleteAll(tablesToIgnoreForChildren);
                    
                    // Copy the statements from child query mechanism.
                    // In Employee example query for Project will pick up a statement for
                    // LPROJECT table from LargeProject and nothing from SmallProject.
                    Vector childStatements = new Vector();
                    if(childMechanism.getCall() != null) {
                        childStatements.add(childMechanism.getSQLStatement());
                    } else if(childMechanism.getSQLStatements() != null) {
                        childStatements.addAll(childMechanism.getSQLStatements());
                    }
                    if(!childStatements.isEmpty()) {
                        if(getSQLStatement() != null) {
                            getSQLStatements().add(getSQLStatement());
                            setSQLStatement(null);
                        }
                        getSQLStatements().addAll(childStatements);
                    }
                }
            }
        }
        
        // Nested method call doesn't need to call this.
        if(tablesToIgnore == null) {
            ((DeleteAllQuery)getQuery()).setIsPreparedUsingTempStorage(false);
            super.prepareDeleteAll();
        }
    }

    protected void prepareDeleteAllUsingTempStorage() {
        if(getSession().getPlatform().supportsTempTables()) {
            prepareDeleteAllUsingTempTables();
        } else {
            throw QueryException.tempTablesNotSupported(getQuery(), Helper.getShortClassName(getSession().getPlatform()));
        }
    }
    
    protected void prepareDeleteAllUsingTempTables() {
        getSQLStatements().addAll(buildStatementsForDeleteAllForTempTables());
        ((DeleteAllQuery)getQuery()).setIsPreparedUsingTempStorage(true);
        super.prepareDeleteAll();
    }
    
    /**
     * Create SQLDeleteAllStatements for mappings that may be responsible for references
     * to the objects to be deleted
     * in the tables NOT mapped to any class: ManyToManyMapping and DirectCollectionMapping
     *
     * NOTE: A similar pattern also used in method buildDeleteAllStatementsForMappingsWithTempTable():
     * if you are updating this method consider applying a similar update to that method as well.
     *
     * @return Vector<SQLDeleteAllStatement>
     */
    protected Vector buildDeleteAllStatementsForMappings(SQLCall selectCallForExist, SQLSelectStatement selectStatementForExist, boolean dontCheckDescriptor) {
        Vector deleteStatements = new Vector();
        Iterator itMappings = getDescriptor().getMappings().iterator();
        while(itMappings.hasNext()) {
            DatabaseMapping mapping = (DatabaseMapping)itMappings.next();
            if(mapping.isManyToManyMapping() || mapping.isDirectCollectionMapping()) {
                if(dontCheckDescriptor || mapping.getDescriptor().equals(getDescriptor())) {
                    Vector sourceFields = null;
                    Vector targetFields = null;
                    if(mapping.isManyToManyMapping()) {
                        sourceFields = ((ManyToManyMapping)mapping).getSourceKeyFields();
                        targetFields = ((ManyToManyMapping)mapping).getSourceRelationKeyFields();
                    } else if(mapping.isDirectCollectionMapping()) {
                        sourceFields = ((DirectCollectionMapping)mapping).getSourceKeyFields();
                        targetFields = ((DirectCollectionMapping)mapping).getReferenceKeyFields();
                    }
                    deleteStatements.addElement(buildDeleteAllStatementForMapping(selectCallForExist, selectStatementForExist, sourceFields, targetFields));
                }
            }
        }
        return deleteStatements;
    }
    
    /**
     * Build delete statements with temporary table for ManyToMany and DirectCollection mappings.
     *
     * NOTE: A similar pattern also used in method buildDeleteAllStatementsForMappings():
     * if you are updating this method consider applying a similar update to that method as well.
     *
     * @return Vector<SQLDeleteAllStatementForTempTable>
     */
    protected Vector buildDeleteAllStatementsForMappingsWithTempTable(ClassDescriptor descriptor, DatabaseTable rootTable, Collection rootTablePrimaryKeyFields, boolean dontCheckDescriptor) {
        Vector deleteStatements = new Vector();
        Iterator itMappings = descriptor.getMappings().iterator();
        while(itMappings.hasNext()) {
            DatabaseMapping mapping = (DatabaseMapping)itMappings.next();
            if(mapping.isManyToManyMapping() || mapping.isDirectCollectionMapping()) {
                if(dontCheckDescriptor || mapping.getDescriptor().equals(descriptor)) {
                    Vector sourceFields = null;
                    Vector targetFields = null;
                    if(mapping.isManyToManyMapping()) {
                        sourceFields = ((ManyToManyMapping)mapping).getSourceKeyFields();
                        targetFields = ((ManyToManyMapping)mapping).getSourceRelationKeyFields();
                    } else if(mapping.isDirectCollectionMapping()) {
                        sourceFields = ((DirectCollectionMapping)mapping).getSourceKeyFields();
                        targetFields = ((DirectCollectionMapping)mapping).getReferenceKeyFields();
                    }

                    DatabaseTable targetTable = ((DatabaseField)targetFields.firstElement()).getTable();
                    SQLDeleteAllStatementForTempTable deleteStatement
                        = buildDeleteAllStatementForTempTable(rootTable, rootTablePrimaryKeyFields, targetTable, targetFields);
                    deleteStatements.addElement(deleteStatement);
                }
            }
        }
        return deleteStatements;
    }

    protected SQLSelectStatement createSQLSelectStatementForModifyAll(Expression whereClause) {
        return createSQLSelectStatementForModifyAll(whereClause, null, getDescriptor(), false);
    }
    
    protected SQLSelectStatement createSQLSelectStatementForModifyAll(Expression whereClause, Expression inheritanceExpression) {
        return createSQLSelectStatementForModifyAll(whereClause, inheritanceExpression, getDescriptor(), true);
    }
    
    protected SQLSelectStatement createSQLSelectStatementForModifyAll(Expression whereClause, Expression inheritanceExpression,
                                 ClassDescriptor desc)
    {
        return createSQLSelectStatementForModifyAll(whereClause, inheritanceExpression, desc, true);
    }
    
    protected SQLSelectStatement createSQLSelectStatementForModifyAll(Expression whereClause, Expression inheritanceExpression,
                                 ClassDescriptor desc, boolean useCustomaryInheritanceExpression)
    {
        ExpressionBuilder builder;
        if(whereClause != null) {
            whereClause = (Expression)whereClause.clone();
            builder = whereClause.getBuilder();
        } else {
            builder = new ExpressionBuilder();
        }
        
        ReportQuery reportQuery = new ReportQuery(desc.getJavaClass(), builder);
        reportQuery.setDescriptor(desc);
        reportQuery.setShouldRetrieveFirstPrimaryKey(true);
        reportQuery.setSelectionCriteria(whereClause);
        reportQuery.setSession(getSession());
        
        return ((ExpressionQueryMechanism)reportQuery.getQueryMechanism()).buildReportQuerySelectStatement(false, useCustomaryInheritanceExpression, inheritanceExpression);
    }
        
    protected SQLSelectStatement createSQLSelectStatementForAssignedExpressionForUpdateAll(Expression value)
    {
        ReportQuery reportQuery = new ReportQuery(getQuery().getReferenceClass(), value.getBuilder());
        reportQuery.setDescriptor(getQuery().getDescriptor());
        reportQuery.setSession(getSession());
        reportQuery.addAttribute("", value);
        
        return ((ExpressionQueryMechanism)reportQuery.getQueryMechanism()).buildReportQuerySelectStatement(false);
    }
    
    /**
     * This method return the clones of the given expressions.
     *
     * @param originalExpressions
     * @param clonedExpressions
     * @return Vector
     */
    private Vector cloneExpressions(Vector originalExpressions,Dictionary clonedExpressions){
        if(originalExpressions==null || originalExpressions.size()==0){
            return originalExpressions;
        }
        Vector newExpressions = new Vector(originalExpressions.size());
        Iterator i = originalExpressions.iterator();
        while (i.hasNext()){
            Expression e = (Expression)i.next();
            newExpressions.add(e.copiedVersionFrom(clonedExpressions));
        }
        return newExpressions;
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareDeleteObject() {
        // Add and prepare to a call a delete statement for each table.
        // In the case of multiple tables, build the sql statements Vector in insert order. When the
        // actual SQL calls are sent they are sent in the reverse of this order.
        for (Enumeration tablesEnum = getDescriptor().getMultipleTableInsertOrder().elements();
                 tablesEnum.hasMoreElements();) {
            DatabaseTable table = (DatabaseTable)tablesEnum.nextElement();
            SQLDeleteStatement deleteStatement = buildDeleteStatement(table);
            if (getDescriptor().getTables().size() > 1) {
                getSQLStatements().addElement(deleteStatement);
            } else {
                setSQLStatement(deleteStatement);
            }
        }

        super.prepareDeleteObject();
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareDoesExist(DatabaseField field) {
        setSQLStatement(buildSelectStatementForDoesExist(field));

        super.prepareDoesExist(field);
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareInsertObject() {
        // Require modify row to prepare.
        if (getModifyRow() == null) {
            return;
        }

        // Add and prepare to a call a update statement for each table.
        // In the case of multiple tables, build the sql statements Vector in insert order.
        for (Enumeration tablesEnum = getDescriptor().getMultipleTableInsertOrder().elements();
                 tablesEnum.hasMoreElements();) {
            DatabaseTable table = (DatabaseTable)tablesEnum.nextElement();
            SQLInsertStatement insertStatement = buildInsertStatement(table);
            if (getDescriptor().getTables().size() > 1) {
                getSQLStatements().addElement(insertStatement);
            } else {
                setSQLStatement(insertStatement);
            }
        }

        super.prepareInsertObject();
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareReportQuerySelectAllRows() {
        SQLSelectStatement statement = buildReportQuerySelectStatement(false);
        setSQLStatement(statement);
        setCallFromStatement();
        // The statement is no longer require so can be released.
        setSQLStatement(null);

        getCall().returnManyRows();
        prepareCall();
    }

    /**
     * Pre-build the SQL statement from the expression.
     * This is used for subselects, so does not normalize or generate the SQL as it needs the outer expression for this.
     */
    public void prepareReportQuerySubSelect() {
        setSQLStatement(buildReportQuerySelectStatement(true));
        // The expression is no longer require so can be released.
        setSelectionCriteria(null);
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareSelectAllRows() {
        if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().requiresMultipleTableSubclassRead()) {
            if (getDescriptor().getInheritancePolicy().hasView()) {
                // CR#3158703 if the descriptor has a view, then it requires a single select,
                // so can be prepared.
                setSQLStatement(getDescriptor().getInheritancePolicy().buildViewSelectStatement((ObjectLevelReadQuery)getQuery()));
                super.prepareSelectAllRows();
            } else if (!getDescriptor().getInheritancePolicy().hasClassExtractor()) {
                // CR#3158703 otherwise if using a type indicator at least the type select can be prepared.
                setSQLStatement(getDescriptor().getInheritancePolicy().buildClassIndicatorSelectStatement((ObjectLevelReadQuery)getQuery()));
                super.prepareSelectAllRows();
            }

            // else - otherwise cannot prepare the select.
        } else {
            setSQLStatement(buildNormalSelectStatement());
            super.prepareSelectAllRows();
        }
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareSelectOneRow() {
        if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().requiresMultipleTableSubclassRead()) {
            if (getDescriptor().getInheritancePolicy().hasView()) {
                // CR#3158703 if the descriptor has a view, then it requires a single select,
                // so can be prepared.
                setSQLStatement(getDescriptor().getInheritancePolicy().buildViewSelectStatement((ObjectLevelReadQuery)getQuery()));
                super.prepareSelectOneRow();
            } else if (!getDescriptor().getInheritancePolicy().hasClassExtractor()) {
                // CR#3158703 otherwise if using a type indicator at least the type select can be prepared.
                setSQLStatement(getDescriptor().getInheritancePolicy().buildClassIndicatorSelectStatement((ObjectLevelReadQuery)getQuery()));
                super.prepareSelectOneRow();
            }

            // else - otherwise cannot prepare the select.
        } else {
            setSQLStatement(buildNormalSelectStatement());
            super.prepareSelectOneRow();
        }
    }

    /**
     * Pre-build the SQL statement from the expression.
     */
    public void prepareUpdateObject() {
        // Require modify row to prepare.
        if (getModifyRow() == null) {
            return;
        }

        // Add and prepare to a call a update statement for each table.
        int tablesSize = getDescriptor().getTables().size();
        for (int index = 0; index < tablesSize; index++) {
            DatabaseTable table = (DatabaseTable)getDescriptor().getTables().get(index);
            SQLUpdateStatement updateStatement = buildUpdateStatement(table);
            if (tablesSize > 1) {
                getSQLStatements().addElement(updateStatement);
            } else {
                setSQLStatement(updateStatement);
            }
        }

        super.prepareUpdateObject();
    }

    /**
     * Pre-build the SQL statement from the expressions.
     */
    public void prepareUpdateAll() {
        ExpressionBuilder builder = ((UpdateAllQuery)getQuery()).getExpressionBuilder();
        HashMap updateClauses = ((UpdateAllQuery)getQuery()).getUpdateClauses();
        
        // Add a statement to update the optimistic locking field if their is one.
        OptimisticLockingPolicy policy = getDescriptor().getOptimisticLockingPolicy();
        if (policy != null) {
            if(policy.getWriteLockField() != null) {
                Expression writeLock = builder.getField(policy.getWriteLockField());
                Expression writeLockUpdateExpression = policy.getWriteLockUpdateExpression(builder);
                if (writeLockUpdateExpression != null) {
                    // clone it to keep user's original data intact
                    updateClauses = (HashMap)updateClauses.clone();
                    updateClauses.put(writeLock, writeLockUpdateExpression);
                }
            }
        }
        
        HashMap tables_databaseFieldsToValues = new HashMap();
        HashMap tablesToPrimaryKeyFields = new HashMap();
        Iterator it = updateClauses.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            
            Object fieldObject = entry.getKey();
            DataExpression fieldExpression = null;
            Expression baseExpression = null; // QueryKeyExpression or FieldExpression of the field
            String attributeName = null;
            if(fieldObject instanceof String) {
                attributeName = (String)fieldObject;
            } else {
                // it should be either QueryKeyExpression or FieldExpression
                fieldExpression = (DataExpression)fieldObject;
            }

            DatabaseField field = null;
            DatabaseMapping mapping = null;
            if(attributeName != null) {
                mapping = getDescriptor().getObjectBuilder().getMappingForAttributeName(attributeName);
                if (mapping != null && !mapping.getFields().isEmpty()) {
                    field = (DatabaseField)mapping.getFields().firstElement();
                }
                if(field == null) {
                    throw QueryException.updateAllQueryAddUpdateDoesNotDefineField(getDescriptor(), getQuery(), attributeName);
                }
                baseExpression = ((UpdateAllQuery)getQuery()).getExpressionBuilder().get(attributeName);
            } else if (fieldExpression != null) {
                // it should be either QueryKeyExpression or ExpressionBuilder
                if (fieldExpression.getBaseExpression() instanceof ExpressionBuilder) {
                    field = getDescriptor().getObjectBuilder().getFieldForQueryKeyName(fieldExpression.getName());
                }
                if(field == null) {
                    DataExpression fieldExpressionClone = (DataExpression)fieldExpression.clone();
                    fieldExpressionClone.getBuilder().setQueryClass(getQuery().getReferenceClass());
                    fieldExpressionClone.getBuilder().setSession(getSession().getRootSession(null));
                    field = fieldExpressionClone.getField();
                    if(field == null) {
                        throw QueryException.updateAllQueryAddUpdateDoesNotDefineField(getDescriptor(), getQuery(), fieldExpression.toString());
                    }
                }
                mapping = getDescriptor().getObjectBuilder().getMappingForField(field);
                baseExpression = fieldExpression;
            }

            Object valueObject = entry.getValue();
            Vector fields;
            Vector values;
            Vector baseExpressions;
            if(mapping != null && mapping.isOneToOneMapping()) {
                fields = mapping.getFields();
                int fieldsSize = fields.size();
                values = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldsSize);
                baseExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldsSize);
                for(int i=0; i<fieldsSize; i++) {
                    if(valueObject instanceof ConstantExpression) {
                        valueObject = ((ConstantExpression)valueObject).getValue();
                    }
                    if(valueObject == null) {
                        values.add(null);
                    } else {
                        DatabaseField targetField = (DatabaseField)((OneToOneMapping)mapping).getSourceToTargetKeyFields().get(fields.elementAt(i));
                        if(valueObject instanceof Expression) {
                            values.add(((Expression)((Expression)valueObject).clone()).getField(targetField));
                        } else {
                            values.add(((OneToOneMapping)mapping).getReferenceDescriptor().getObjectBuilder().extractValueFromObjectForField(valueObject, targetField, getSession()));
                        }
                    }
                    baseExpressions.add(new FieldExpression((DatabaseField)fields.elementAt(i), ((QueryKeyExpression)baseExpression).getBaseExpression()));
                }
            } else {
                fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
                fields.add(field);
                values = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
                values.add(valueObject);
                baseExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
                baseExpressions.add(baseExpression);
            }
            int fieldsSize = fields.size();
            for(int i=0; i<fieldsSize; i++) {
                field = (DatabaseField)fields.elementAt(i);
                DatabaseTable table = field.getTable();
                if(!getDescriptor().getTables().contains(table)) {
                    if(attributeName != null) {
                        throw QueryException.updateAllQueryAddUpdateDefinesWrongField(getDescriptor(), getQuery(), attributeName, field.getQualifiedName());
                    } else {
                        throw QueryException.updateAllQueryAddUpdateDefinesWrongField(getDescriptor(), getQuery(), fieldExpression.toString(), field.getQualifiedName());
                    }
                }
                
                HashMap databaseFieldsToValues = (HashMap)tables_databaseFieldsToValues.get(table);
                if(databaseFieldsToValues == null) {
                    databaseFieldsToValues = new HashMap();
                    tables_databaseFieldsToValues.put(table, databaseFieldsToValues);
    
                    tablesToPrimaryKeyFields.put(table, getPrimaryKeyFieldsForTable(table));
                }
    
                Object value = values.elementAt(i);
                Expression valueExpression;
                if(valueObject instanceof Expression) {
                    valueExpression = (Expression)value;
                } else {
                    valueExpression = builder.value(value);
                }
                // GF#1123 - UPDATE with JPQL does not handle enums correctly
                // Set localBase so that the value can be converted properly later.
                // NOTE: If baseExpression is FieldExpression, conversion is not required.
                if(valueExpression.isValueExpression()) {
                    valueExpression.setLocalBase((Expression)baseExpressions.elementAt(i));
                }
                
                databaseFieldsToValues.put(field, valueExpression);
            }
        }
        
        SQLCall selectCallForExist = null;
        SQLSelectStatement selectStatementForExist = createSQLSelectStatementForModifyAll(getSelectionCriteria());
        
        // Main Case: Descriptor is mapped to more than one table and/or the query references other tables
        boolean isMainCase = selectStatementForExist.requiresAliases();
        if(isMainCase) {
            if(getSession().getPlatform().shouldAlwaysUseTempStorageForModifyAll()) {
                prepareUpdateAllUsingTempStorage(tables_databaseFieldsToValues, tablesToPrimaryKeyFields);
                return;
            }
        }
        selectCallForExist = (SQLCall)selectStatementForExist.buildCall(getSession());
        
        // ExpressionIterator to search for valueExpressions that require select statements.
        // Those are expressions that
        // either reference other tables:
        // Employee-based example: valueExp = builder.get("address").get("city");
        // or use DataExpressions with base not ExpressionBuilder:
        // Employee-base example: valueExp = builder.get("manager").get("firstName");
        // Before iterating the table is set into result,
        // if expression requiring select is found, then resul set to null.
        ExpressionIterator expRequiresSelectIterator = new ExpressionIterator() {
            public void iterate(Expression each) {
                if(getResult() == null) {
                    return;
                }
                if(each instanceof DataExpression) {
                    DataExpression dataExpression = (DataExpression)each;
                    Expression baseExpression = dataExpression.getBaseExpression();
                    if(baseExpression != null && !(baseExpression instanceof ExpressionBuilder)) {
                        boolean stop = true;
                        if(baseExpression instanceof DataExpression) {
                            DataExpression baseDataExpression = (DataExpression)baseExpression;
                            if(baseDataExpression.getMapping() != null && baseDataExpression.getMapping().isAggregateObjectMapping()) {
                                stop = false;
                            }
                        }
                        if(stop) {
                            setResult(null);
                            return;
                        }
                    }
                    DatabaseField field = dataExpression.getField();
                    if(field != null) {
                        if(!field.getTable().equals((DatabaseTable)getResult())) {
                            setResult(null);
                            return;
                        }
                    }
                }
            }
            public boolean shouldIterateOverSubSelects() {
                return true;
            }
        };

        HashMap tables_databaseFieldsToValuesCopy = new HashMap();
        it = tables_databaseFieldsToValues.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            DatabaseTable table = (DatabaseTable)entry.getKey();
            HashMap databaseFieldsToValues = (HashMap)entry.getValue();
            HashMap databaseFieldsToValuesCopy = new HashMap();
            tables_databaseFieldsToValuesCopy.put(table, databaseFieldsToValuesCopy);
            Iterator itFieldsToValues = databaseFieldsToValues.entrySet().iterator();
            while(itFieldsToValues.hasNext()) {
                Map.Entry entry2 = (Map.Entry)itFieldsToValues.next();
                DatabaseField field = (DatabaseField)entry2.getKey();
                Expression value = (Expression)entry2.getValue();

                // initialize result with the table
                expRequiresSelectIterator.setResult(table);
                // To find fields have to have session and ref class
                Expression valueClone = (Expression)value.clone();
                valueClone.getBuilder().setSession(getSession());
                valueClone.getBuilder().setQueryClass(getQuery().getReferenceClass());
                expRequiresSelectIterator.iterateOn(valueClone);
                if(expRequiresSelectIterator.getResult() == null) {
                    // this one should use SELECT as an assigned expression.
                    // The corresponding SelectionStatement should be assigned to value
                    if(getSession().getPlatform().shouldAlwaysUseTempStorageForModifyAll()) {
                        prepareUpdateAllUsingTempStorage(tables_databaseFieldsToValues, tablesToPrimaryKeyFields);
                        return;
                    }
                    
                    SQLSelectStatement selStatement = createSQLSelectStatementForAssignedExpressionForUpdateAll(value);
                    databaseFieldsToValuesCopy.put(field, selStatement);
                } else {
                    databaseFieldsToValuesCopy.put(field, valueClone);
                }
            }
        }
        HashMap tables_databaseFieldsToValuesOriginal = tables_databaseFieldsToValues;
        tables_databaseFieldsToValues = tables_databaseFieldsToValuesCopy;
        
        if(tables_databaseFieldsToValues.size() == 1) {
            Map.Entry entry = (Map.Entry)tables_databaseFieldsToValues.entrySet().iterator().next();
            DatabaseTable table = (DatabaseTable)entry.getKey();
            HashMap databaseFieldsToValues = (HashMap)entry.getValue();
            Collection primaryKeyFields = (Collection)tablesToPrimaryKeyFields.values().iterator().next();
            setSQLStatement(buildUpdateAllStatement(table, databaseFieldsToValues, selectCallForExist, selectStatementForExist, primaryKeyFields));
        } else {
            // To figure out the order of statements we need to find dependencies
            // between updating of tables.
            // Here's an example:
            // All objects with nameA = "Clob" should be changed so that nameA = "Alex" and nameB = "Bob";
            // nameA is mapped to A.name and nameB mapped to B.name:
            // UPDATE B SET B.name = "Bob" WHERE A.name = "Clob" and A.id = B.id;
            // UPDATE A SET A.name = "Alex" WHERE A.name = "Clob" and A.id = B.id;
            // The order can't be altered - or there will be no updating of B.
            // To formalize that: for each table we'll gather two Collections:
            // leftFields - all the table's fields to receive a new value;
            // rightFields - all the fields either in assigned or selecton expression.
            // A_leftFields = {A.name}; A_rightFields = {A.name}.
            // B_leftFields = {B.name}; B_rightFields = {A.name}.
            // There are several comparison outcomes:
            // 1. A_leftFields doesn't intersect B_rightFields and
            // B_leftFields doesn't intersect A_rightFields
            // There is no dependency - doesn't matter which update goes first;
            // 2. A_leftFields intersects B_rightFields and
            // B_leftFields doesn't intersect A_rightFields
            // B should be updated before A (the case in the example).
            // 3. A_leftFields intersects B_rightFields and
            // B_leftFields intersects A_rightFields
            // Ordering conflict that can't be resolved without using transitionary storage.
            //
            // This ExpressionIterator will be used for collecting fields from
            // selection criteria and assigned expressions.
            ExpressionIterator expIterator = new ExpressionIterator() {
                public void iterate(Expression each) {
                    if(each instanceof DataExpression) {
                        DataExpression dataExpression = (DataExpression)each;
                        DatabaseField field = dataExpression.getField();
                        if(field != null) {
                            ((Collection)getResult()).add(field);
                        }
                    }
                }
                public boolean shouldIterateOverSubSelects() {
                    return true;
                }
            };
            
            // This will hold collection of fields from selection criteria expression.
            HashSet selectCallForExistFields = new HashSet();
            if(selectCallForExist != null) {
                expIterator.setResult(selectCallForExistFields);
                expIterator.iterateOn(selectStatementForExist.getWhereClause());
            }
            
            // Left of the assignment operator that is - the fields acquiring new values
            HashMap tablesToLeftFields = new HashMap();
            // The fields right of the assignment operator AND the fields from whereClause
            HashMap tablesToRightFields = new HashMap();
            
            // before and after vectors work together: n-th member of beforeTable should
            // be updated before than n-th member of afterTable
            Vector beforeTables = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            Vector afterTables = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            
            // Both keys and values are tables.
            // An entry indicates a timing conflict between the key and the value:
            // both key should be updated before value and value before key.
            HashMap simpleConflicts = new HashMap();
            
            it = tables_databaseFieldsToValues.entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                // for each table to be updated
                DatabaseTable table = (DatabaseTable)entry.getKey();
                // here's a Map of left hand fields to right hand expressions
                HashMap databaseFieldsToValues = (HashMap)entry.getValue();
                
                // This will contain all the left hand fields
                HashSet leftFields = new HashSet(databaseFieldsToValues.size());
                // This will contain all the left hand fields plus fields form selection criteria
                HashSet rightFields = (HashSet)selectCallForExistFields.clone();
                expIterator.setResult(rightFields);
                Iterator itDatabaseFieldsToValues = databaseFieldsToValues.entrySet().iterator();
                while(itDatabaseFieldsToValues.hasNext()) {
                    // for each left hand - right hand expression pair
                    Map.Entry databaseFieldValueEntry = (Map.Entry)itDatabaseFieldsToValues.next();
                    // here's the left hand database field
                    DatabaseField field = (DatabaseField)databaseFieldValueEntry.getKey();
                    leftFields.add(field);
                    // here's the right hand expression
                    Object value = databaseFieldValueEntry.getValue();
                    if(value instanceof Expression) {
                        Expression valueExpression = (Expression)value;
                        // use iterator to extract all the fields
                        expIterator.iterateOn(valueExpression);
                    } else {
                        // It should be SQLSelectStatement with a single field
                        SQLSelectStatement selStatement = (SQLSelectStatement)value;
                        // first one is the normalized value to be assigned
                        expIterator.iterateOn((Expression)selStatement.getFields().elementAt(0));
                        // whereClause - generated during normalization
                        expIterator.iterateOn(selStatement.getWhereClause());
                    }
                }
                
                // now let's compare the table with the already processed tables
                Iterator itProcessedTables = tablesToLeftFields.keySet().iterator();
                while(itProcessedTables.hasNext()) {
                    DatabaseTable processedTable = (DatabaseTable)itProcessedTables.next();
                    HashSet processedTableLeftFields = (HashSet)tablesToLeftFields.get(processedTable);
                    HashSet processedTableRightFields = (HashSet)tablesToRightFields.get(processedTable);
                    boolean tableBeforeProcessedTable = false;
                    Iterator itProcessedTableLeftField = processedTableLeftFields.iterator();
                    while(itProcessedTableLeftField.hasNext()) {
                        if(rightFields.contains(itProcessedTableLeftField.next())) {
                            tableBeforeProcessedTable = true;
                            break;
                        }
                    }
                    boolean processedTableBeforeTable = false;
                    Iterator itLeftField = leftFields.iterator();
                    while(itLeftField.hasNext()) {
                        if(processedTableRightFields.contains(itLeftField.next())) {
                            processedTableBeforeTable = true;
                            break;
                        }
                    }
                    if(tableBeforeProcessedTable && !processedTableBeforeTable) {
                        // table should be updated before processedTable
                        beforeTables.add(table);
                        afterTables.add(processedTable);
                    } else if (!tableBeforeProcessedTable && processedTableBeforeTable) {
                        // processedTable should be updated before table
                        beforeTables.add(processedTable);
                        afterTables.add(table);
                    } else if (tableBeforeProcessedTable && processedTableBeforeTable) {
                        // there is an order conflict between table and processTable
                        simpleConflicts.put(processedTable, table);
                    }
                }
                
                tablesToLeftFields.put(table, leftFields);
                tablesToRightFields.put(table, rightFields);
            }
            
            if(!simpleConflicts.isEmpty()) {
                prepareUpdateAllUsingTempStorage(tables_databaseFieldsToValuesOriginal, tablesToPrimaryKeyFields);
                return;
            }
            
            // This will contain tables in update order
            Vector orderedTables = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(tables_databaseFieldsToValues.size());
            // first process the tables found in beforeTables / afterTables
            while(!beforeTables.isEmpty()) {
                // Find firstTable - the one that appears in beforeTables, but not afterTables.
                // That means there is no requirement to update it after any other table and we
                // can put it first in update order. There could be several such tables -
                // it doesn't matter which one will be picked.
                DatabaseTable firstTable = null;
                for(int i=0; i < beforeTables.size(); i++) {
                    DatabaseTable beforeTable = (DatabaseTable)beforeTables.elementAt(i);
                    if(!afterTables.contains(beforeTable)) {
                        firstTable = beforeTable;
                        break;
                    }
                }
                if(firstTable == null) {
                    // There is no firstTable - it's an order conflict between three or more tables
                    prepareUpdateAllUsingTempStorage(tables_databaseFieldsToValuesOriginal, tablesToPrimaryKeyFields);
                    return;
                } else {
                    // Remove first table from beforeTables - there could be several entries.
                    // Also remove the corresponding entries from afterTable.
                    for(int i=beforeTables.size()-1; i>=0; i--) {
                        if(beforeTables.elementAt(i).equals(firstTable)) {
                            beforeTables.remove(i);
                            afterTables.remove(i);
                        }
                    }
                    // Add firstTable to orderedTables
                    orderedTables.addElement(firstTable);
                }
            }

            // now all the remaining ones - there are no dependencies between them
            // so the order is arbitrary.
            Iterator itTables = tables_databaseFieldsToValues.keySet().iterator();
            while(itTables.hasNext()) {
                DatabaseTable table = (DatabaseTable)itTables.next();
                if(!orderedTables.contains(table)) {
                    orderedTables.add(table);
                }
            }
            
            // finally create statements
            for(int i=0; i < orderedTables.size(); i++) {
                DatabaseTable table = (DatabaseTable)orderedTables.elementAt(i);
                HashMap databaseFieldsToValues = (HashMap)tables_databaseFieldsToValues.get(table);
                Collection primaryKeyFields = (Collection)tablesToPrimaryKeyFields.get(table);
                getSQLStatements().addElement(buildUpdateAllStatement(table, databaseFieldsToValues, selectCallForExist, selectStatementForExist, primaryKeyFields));
            }
        }

        ((UpdateAllQuery)getQuery()).setIsPreparedUsingTempStorage(false);
        super.prepareUpdateAll();
    }

    protected SQLSelectStatement createSQLSelectStatementForUpdateAllForOracleAnonymousBlock(HashMap tables_databaseFieldsToValues)
    {
        ExpressionBuilder builder = ((UpdateAllQuery)getQuery()).getExpressionBuilder();
        Expression whereClause = getSelectionCriteria();
        
        ReportQuery reportQuery = new ReportQuery(getDescriptor().getJavaClass(), builder);
        reportQuery.setDescriptor(getDescriptor());
        reportQuery.setSelectionCriteria(whereClause);
        reportQuery.setSession(getSession());
        
        reportQuery.setShouldRetrievePrimaryKeys(true);
        Iterator itDatabaseFieldsToValues = tables_databaseFieldsToValues.values().iterator();
        while(itDatabaseFieldsToValues.hasNext()) {
            HashMap databaseFieldsToValues = (HashMap)itDatabaseFieldsToValues.next();
            Iterator itValues = databaseFieldsToValues.values().iterator();
            while(itValues.hasNext()) {
                reportQuery.addAttribute("", (Expression)itValues.next());
            }
        }

        return ((ExpressionQueryMechanism)reportQuery.getQueryMechanism()).buildReportQuerySelectStatement(false);
    }
        
    protected SQLSelectStatement createSQLSelectStatementForModifyAllForTempTable(HashMap databaseFieldsToValues)
    {
        ExpressionBuilder builder = ((ModifyAllQuery)getQuery()).getExpressionBuilder();
        Expression whereClause = getSelectionCriteria();
                
        ReportQuery reportQuery = new ReportQuery(getDescriptor().getJavaClass(), builder);
        reportQuery.setDescriptor(getDescriptor());
        reportQuery.setSelectionCriteria(whereClause);
        reportQuery.setSession(getSession());
        
        reportQuery.setShouldRetrievePrimaryKeys(true);
        if(databaseFieldsToValues != null) {
            Iterator itValues = databaseFieldsToValues.values().iterator();
            while(itValues.hasNext()) {
                reportQuery.addAttribute("", (Expression)itValues.next());
            }
        }

        return ((ExpressionQueryMechanism)reportQuery.getQueryMechanism()).buildReportQuerySelectStatement(false);
    }
        
    protected SQLModifyStatement buildUpdateAllStatementForOracleAnonymousBlock(HashMap tables_databaseFieldsToValues, HashMap tablesToPrimaryKeyFields) {
        SQLSelectStatement selectStatement = createSQLSelectStatementForUpdateAllForOracleAnonymousBlock(tables_databaseFieldsToValues);
        SQLCall selectCall = (SQLCall)selectStatement.buildCall(getSession());
        
        SQLUpdateAllStatementForOracleAnonymousBlock updateAllStatement = new SQLUpdateAllStatementForOracleAnonymousBlock();
        updateAllStatement.setTranslationRow(getTranslationRow());

        updateAllStatement.setSelectCall(selectCall);
        updateAllStatement.setTables_databaseFieldsToValues(tables_databaseFieldsToValues);
        updateAllStatement.setTablesToPrimaryKeyFields(tablesToPrimaryKeyFields);
        
        updateAllStatement.setTable((DatabaseTable)getDescriptor().getTables().firstElement());

        return updateAllStatement;
    }
    
    protected void prepareUpdateAllUsingTempStorage(HashMap tables_databaseFieldsToValues, HashMap tablesToPrimaryKeyFields) {
        if(getSession().getPlatform().supportsTempTables()) {
            prepareUpdateAllUsingTempTables(tables_databaseFieldsToValues, tablesToPrimaryKeyFields);
        } else if(getSession().getPlatform().isOracle()) {
            prepareUpdateAllUsingOracleAnonymousBlock(tables_databaseFieldsToValues, tablesToPrimaryKeyFields);
        } else {
            throw QueryException.tempTablesNotSupported(getQuery(), Helper.getShortClassName(getSession().getPlatform()));
        }
    }
    
    /**
     * Pre-build the SQL statement from the expressions.
     */
    protected void prepareUpdateAllUsingOracleAnonymousBlock(HashMap tables_databaseFieldsToValues, HashMap tablesToPrimaryKeyFields) {
        
        setSQLStatement(buildUpdateAllStatementForOracleAnonymousBlock(tables_databaseFieldsToValues, tablesToPrimaryKeyFields));
        ((UpdateAllQuery)getQuery()).setIsPreparedUsingTempStorage(true);
        super.prepareUpdateAll();
    }

    /**
     * Pre-build the SQL statement from the expressions.
     */
    protected void prepareUpdateAllUsingTempTables(HashMap tables_databaseFieldsToValues, HashMap tablesToPrimaryKeyFields) {
        int nTables = tables_databaseFieldsToValues.size();
        Vector createTableStatements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(nTables);
        Vector selectStatements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(nTables);
        Vector updateStatements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(nTables);
        Vector cleanupStatements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(nTables);
        
        Iterator itEntrySets = tables_databaseFieldsToValues.entrySet().iterator();
        while(itEntrySets.hasNext()) {
            Map.Entry entry = (Map.Entry)itEntrySets.next();
            DatabaseTable table = (DatabaseTable)entry.getKey();
            HashMap databaseFieldsToValues = (HashMap)entry.getValue();
            Collection primaryKeyFields = (Collection)tablesToPrimaryKeyFields.get(table);

            Vector statementsForTable = buildStatementsForUpdateAllForTempTables(table, databaseFieldsToValues, primaryKeyFields);
            
            createTableStatements.add(statementsForTable.elementAt(0));
            selectStatements.add(statementsForTable.elementAt(1));
            updateStatements.add(statementsForTable.elementAt(2));
            cleanupStatements.add(statementsForTable.elementAt(3));
        }
        
        getSQLStatements().addAll(createTableStatements);
        getSQLStatements().addAll(selectStatements);
        getSQLStatements().addAll(updateStatements);
        getSQLStatements().addAll(cleanupStatements);

        if(getSession().getPlatform().dontBindUpdateAllQueryUsingTempTables()) {
            if(getQuery().shouldBindAllParameters() || (getQuery().shouldIgnoreBindAllParameters() && getSession().getPlatform().shouldBindAllParameters())) {
                getQuery().setShouldBindAllParameters(false);
                getSession().warning("update_all_query_cannot_use_binding_on_this_platform", SessionLog.QUERY);
            }
        }
        ((UpdateAllQuery)getQuery()).setIsPreparedUsingTempStorage(true);
        super.prepareUpdateAll();
    }

    /**
     * Build SQLStatements for delete all using temporary table.
     * @return Vector<SQLStatement>
     */
    protected Vector buildStatementsForDeleteAllForTempTables() {
        Vector statements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        // retrieve rootTable and its primary key fields for composing temporary table
        DatabaseTable rootTable = (DatabaseTable)getDescriptor().getMultipleTableInsertOrder().firstElement();
        Collection rootTablePrimaryKeyFields = getPrimaryKeyFieldsForTable(rootTable);
        ClassDescriptor rootDescriptor = getDescriptor();
        if(getDescriptor().hasInheritance()) {
            rootDescriptor = rootDescriptor.getInheritancePolicy().getRootParentDescriptor();
        }
        Vector allFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        Iterator it = rootDescriptor.getFields().iterator();
        while(it.hasNext()) {
            DatabaseField field = (DatabaseField)it.next();
            if(rootTable.equals(field.getTable())) {
                allFields.add(field);
            }
        }
        
        // statements will be executed in reverse order
        
        // statement for temporary table cleanup (Drop table or Delete from temp_table)
        SQLDeleteAllStatementForTempTable cleanupStatement = new SQLDeleteAllStatementForTempTable();
        cleanupStatement.setMode(SQLModifyAllStatementForTempTable.CLEANUP_TEMP_TABLE);
        cleanupStatement.setTable(rootTable);
        statements.addElement(cleanupStatement);
        
        // delete statements using temporary table
        Vector deleteStatements = buildDeleteAllStatementsForTempTable(getDescriptor(), rootTable, rootTablePrimaryKeyFields, null);
        statements.addAll(deleteStatements);
        
        // Insert statement populating temporary table with criteria
        SQLSelectStatement selectStatement = createSQLSelectStatementForModifyAllForTempTable(null);
        SQLCall selectCall = (SQLCall)selectStatement.buildCall(getSession());
        SQLDeleteAllStatementForTempTable insertStatement = new SQLDeleteAllStatementForTempTable();
        insertStatement.setMode(SQLModifyAllStatementForTempTable.INSERT_INTO_TEMP_TABLE);
        insertStatement.setTable(rootTable);
        insertStatement.setTranslationRow(getTranslationRow());
        insertStatement.setSelectCall(selectCall);
        insertStatement.setPrimaryKeyFields(rootTablePrimaryKeyFields);
        statements.addElement(insertStatement);

        // Create temporary table statement
        SQLDeleteAllStatementForTempTable createTempTableStatement = new SQLDeleteAllStatementForTempTable();
        createTempTableStatement.setMode(SQLModifyAllStatementForTempTable.CREATE_TEMP_TABLE);
        createTempTableStatement.setTable(rootTable);
        createTempTableStatement.setAllFields(allFields);
        createTempTableStatement.setPrimaryKeyFields(rootTablePrimaryKeyFields);
        statements.addElement(createTempTableStatement);
                
        return statements;
    }
    
    /**
     * Build delete all SQLStatements using temporary table.
     * This is recursively called for multiple table child descriptors.
     *
     * NOTE: A similar pattern also used in method prepareDeleteAll():
     * if you are updating this method consider applying a similar update to that method as well.
     *
     * @return Vector<SQLDeleteAllStatementForTempTable>
     */
    private Vector buildDeleteAllStatementsForTempTable(ClassDescriptor descriptor, DatabaseTable rootTable, Collection rootTablePrimaryKeyFields, Vector tablesToIgnore) {
        Vector statements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        
        Vector tablesInInsertOrder;
        if(tablesToIgnore == null) {
            // It's original (not a nested) method call.
            tablesInInsertOrder = descriptor.getMultipleTableInsertOrder();
        } else {
            // It's a nested method call: tableInInsertOrder filled with descriptor's tables (in insert order),
            // the tables found in tablesToIgnore are thrown away -
            // they have already been taken care of by the caller.
            // In Employee example, query with reference class Project gets here
            // to handle LPROJECT table; tablesToIgnore contains PROJECT table.
            tablesInInsertOrder = new Vector(descriptor.getMultipleTableInsertOrder().size());
            for (Iterator tablesEnum = descriptor.getMultipleTableInsertOrder().iterator();
                     tablesEnum.hasNext();) {
                DatabaseTable table = (DatabaseTable)tablesEnum.next();
                if(!tablesToIgnore.contains(table)) {
                    tablesInInsertOrder.addElement(table);
                }
            }
        }

        if(!tablesInInsertOrder.isEmpty()) {
            Iterator itTables = tablesInInsertOrder.iterator();
            while(itTables.hasNext()) {
                DatabaseTable table = (DatabaseTable)itTables.next();
                SQLDeleteAllStatementForTempTable deleteStatement
                    = buildDeleteAllStatementForTempTable(rootTable, rootTablePrimaryKeyFields, table, getPrimaryKeyFieldsForTable(descriptor, table));
                statements.addElement(deleteStatement);
            }
    
            // Add statements for ManyToMany and DirectCollection mappings
            Vector deleteStatementsForMappings
                = buildDeleteAllStatementsForMappingsWithTempTable(descriptor, rootTable, rootTablePrimaryKeyFields, tablesToIgnore == null);
            statements.addAll(deleteStatementsForMappings);
        }
        
        // Indicates whether the descriptor has children using extra tables.
        boolean hasChildrenWithExtraTables = descriptor.hasInheritance() && descriptor.getInheritancePolicy().hasChildren() && descriptor.getInheritancePolicy().hasMultipleTableChild();

        // TBD: should we ignore subclasses in case descriptor doesn't want us to read them in?
        //** Currently in this code we do ignore.
        //** If it will be decided that we need to handle children in all cases
        //** the following statement should be changed to: boolean shouldHandleChildren = hasChildrenWithExtraTables;
        boolean shouldHandleChildren = hasChildrenWithExtraTables && descriptor.getInheritancePolicy().shouldReadSubclasses();

        // Perform a nested method call for each child
        if(shouldHandleChildren) {
            // In Employee example: query for Project will make nested calls to
            // LargeProject and SmallProject and ask them to ignore PROJECT table
            Vector tablesToIgnoreForChildren = new Vector();
            // The tables this descriptor has ignored, its children also should ignore.
            if(tablesToIgnore != null) {
                tablesToIgnoreForChildren.addAll(tablesToIgnore);
            }

            // If the desctiptor reads subclasses there is no need for
            // subclasses to process its tables for the second time.
            if (descriptor.getInheritancePolicy().shouldReadSubclasses()) {
                tablesToIgnoreForChildren.addAll(tablesInInsertOrder);
            }
            
            Iterator it = descriptor.getInheritancePolicy().getChildDescriptors().iterator();
            while(it.hasNext()) {
                ClassDescriptor childDescriptor = (ClassDescriptor)it.next();
                
                // Need to process only "multiple tables" child descriptors
                if ((childDescriptor.getTables().size() > descriptor.getTables().size()) ||
                    (childDescriptor.getInheritancePolicy().hasMultipleTableChild()))
                {
                    //recursively build for child desciptors
                    Vector childStatements = buildDeleteAllStatementsForTempTable(childDescriptor, rootTable, rootTablePrimaryKeyFields, tablesToIgnoreForChildren);
                    statements.addAll(childStatements);
                }
            }
        }
        
        return statements;
    }

    /**
     * Build SQL delete statement which delete from target table using temporary table.
     * @return SQLDeleteAllStatementForTempTable
     */
    private SQLDeleteAllStatementForTempTable buildDeleteAllStatementForTempTable(DatabaseTable rootTable, Collection rootTablePrimaryKeyFields, DatabaseTable targetTable, Collection targetTablePrimaryKeyFields) {
        SQLDeleteAllStatementForTempTable deleteStatement = new SQLDeleteAllStatementForTempTable();
        deleteStatement.setMode(SQLModifyAllStatementForTempTable.UPDATE_ORIGINAL_TABLE);
        deleteStatement.setTable(rootTable);
        deleteStatement.setPrimaryKeyFields(rootTablePrimaryKeyFields);
        deleteStatement.setTargetTable(targetTable);
        deleteStatement.setTargetPrimaryKeyFields(targetTablePrimaryKeyFields);
        return deleteStatement;
    }

    protected Vector buildStatementsForUpdateAllForTempTables(DatabaseTable table, HashMap databaseFieldsToValues, Collection primaryKeyFields) {
        Vector statements = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(4);
        
        Vector allFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        Iterator it = getDescriptor().getFields().iterator();
        while(it.hasNext()) {
            DatabaseField field = (DatabaseField)it.next();
            if(table.equals(field.getTable())) {
                allFields.add(field);
            }
        }
        
        Collection assignedFields = databaseFieldsToValues.keySet();
        HashMap databaseFieldsToValuesForInsert = databaseFieldsToValues;
        Collection assignedFieldsForInsert = assignedFields;

        // The platform doesn't allow nulls in select clause.
        // Remove all the constant expressions with value null:
        // can do that because all fields initialized to null when temp. table created.
        if(!getSession().getPlatform().isNullAllowedInSelectClause()) {
            databaseFieldsToValuesForInsert = new HashMap(databaseFieldsToValues.size());
            Iterator itEntries = databaseFieldsToValues.entrySet().iterator();
            while(itEntries.hasNext()) {
                Map.Entry entry = (Map.Entry)itEntries.next();
                if(entry.getValue() instanceof ConstantExpression) {
                    ConstantExpression constExp = (ConstantExpression)entry.getValue();
                    if(constExp.getValue() == null) {
                        continue;
                    }
                }
                databaseFieldsToValuesForInsert.put(entry.getKey(), entry.getValue());
            }
            assignedFieldsForInsert = databaseFieldsToValuesForInsert.keySet();
        }

        SQLUpdateAllStatementForTempTable createTempTableStatement = new SQLUpdateAllStatementForTempTable();
        createTempTableStatement.setMode(SQLModifyAllStatementForTempTable.CREATE_TEMP_TABLE);
        createTempTableStatement.setTable(table);
        createTempTableStatement.setAllFields(allFields);
        createTempTableStatement.setAssignedFields(assignedFields);
        createTempTableStatement.setPrimaryKeyFields(primaryKeyFields);
        statements.addElement(createTempTableStatement);
                
        SQLSelectStatement selectStatement = createSQLSelectStatementForModifyAllForTempTable(databaseFieldsToValuesForInsert);
        SQLCall selectCall = (SQLCall)selectStatement.buildCall(getSession());
        SQLUpdateAllStatementForTempTable insertStatement = new SQLUpdateAllStatementForTempTable();
        insertStatement.setMode(SQLModifyAllStatementForTempTable.INSERT_INTO_TEMP_TABLE);
        insertStatement.setTable(table);
        insertStatement.setTranslationRow(getTranslationRow());
        insertStatement.setSelectCall(selectCall);
        insertStatement.setAssignedFields(assignedFieldsForInsert);
        insertStatement.setPrimaryKeyFields(primaryKeyFields);
        statements.addElement(insertStatement);
        
        SQLUpdateAllStatementForTempTable updateStatement = new SQLUpdateAllStatementForTempTable();
        updateStatement.setMode(SQLModifyAllStatementForTempTable.UPDATE_ORIGINAL_TABLE);
        updateStatement.setTable(table);
        updateStatement.setTranslationRow(getTranslationRow());
        updateStatement.setAssignedFields(assignedFields);
        updateStatement.setPrimaryKeyFields(primaryKeyFields);
        statements.addElement(updateStatement);
        
        SQLUpdateAllStatementForTempTable cleanupStatement = new SQLUpdateAllStatementForTempTable();
        cleanupStatement.setMode(SQLModifyAllStatementForTempTable.CLEANUP_TEMP_TABLE);
        cleanupStatement.setTable(table);
        statements.addElement(cleanupStatement);
                
        return statements;
    }

    protected Collection getPrimaryKeyFieldsForTable(DatabaseTable table) {
        return getPrimaryKeyFieldsForTable(getDescriptor(), table);
    }

    protected Collection getPrimaryKeyFieldsForTable(ClassDescriptor descriptor, DatabaseTable table) {
        Collection primaryKeyFields;
        if(table.equals(descriptor.getTables().firstElement())) {
            primaryKeyFields = descriptor.getPrimaryKeyFields();
        } else {
            primaryKeyFields = ((Map)descriptor.getAdditionalTablePrimaryKeyFields().get(table)).values();
        }
        return primaryKeyFields;
    }

    /**
     * INTERNAL
     * Read all rows from the database. The code to retrieve the full inheritance hierarchy was removed.
     *
     * @return Vector containing the database rows.
     * @exception DatabaseException - an error has occurred on the database.
     */
    public Vector selectAllReportQueryRows() throws DatabaseException {
        return selectAllRowsFromTable();
    }

    /**
     * Read all rows from the database.
     * @return Vector containing the database rows.
     * @exception DatabaseException - an error has occurred on the database.
     */
    public Vector selectAllRows() throws DatabaseException {
        if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().requiresMultipleTableSubclassRead() && (!getDescriptor().getInheritancePolicy().hasView())) {
            return getDescriptor().getInheritancePolicy().selectAllRowUsingMultipleTableSubclassRead((ReadAllQuery)getQuery());
        } else {
            return selectAllRowsFromTable();
        }
    }

    /**
     * Read all rows from the database.
     * This is used only from query mechanism on a abstract-multiple table read.
     */
    public Vector selectAllRowsFromConcreteTable() throws DatabaseException {
        setSQLStatement(buildConcreteSelectStatement());
        // Must also build the call as mt reads are not pre-built.
        super.prepareSelectAllRows();

        return super.selectAllRows();
    }

    /**
     * Read all rows from the database.
     * @return Vector containing the database rows.
     * @exception DatabaseException - an error has occurred on the database.
     */
    public Vector selectAllRowsFromTable() throws DatabaseException {
        return super.selectAllRows();
    }

    /**
     * Read a single row from the database. Create an SQL statement object,
     * use it to create an SQL command string, and delegate row building
     * responsibility to the accessor.
     */
    public AbstractRecord selectOneRow() throws DatabaseException {
        if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().requiresMultipleTableSubclassRead() && (!getDescriptor().getInheritancePolicy().hasView())) {
            return getDescriptor().getInheritancePolicy().selectOneRowUsingMultipleTableSubclassRead((ReadObjectQuery)getQuery());
        } else {
            return selectOneRowFromTable();
        }
    }

    /**
     * Read a single row from the database.
     * This is used from query mechanism during an abstract-multiple table read.
     */
    public AbstractRecord selectOneRowFromConcreteTable() throws DatabaseException {
        setSQLStatement(buildConcreteSelectStatement());
        // Must also build the call as mt reads are not pre-built.
        super.prepareSelectOneRow();

        return super.selectOneRow();
    }

    /**
     * Read a single row from the database. Create an SQL statement object,
     * use it to create an SQL command string, and delegate row building
     * responsibility to the accessor.
     * @param fields - fields used to build database row
     * @return row containing data
     * @exception DatabaseException - an error has occurred on the database
     */
    public AbstractRecord selectOneRowFromTable() throws DatabaseException {
        return super.selectOneRow();
    }

    /**
     * Set the selection criteria of the query.
     */
    public void setSelectionCriteria(Expression expression) {
        this.selectionCriteria = expression;
    }

    /**
     * Pass to this method a table mapped by query's descriptor.
     * Returns the highest descriptor in inheritance hierarchy that mapps this table.
     */
    protected ClassDescriptor getHighestDescriptorMappingTable(DatabaseTable table) {
        // find the highest descriptor in inheritance hierarchy mapped to the table
        ClassDescriptor desc = getDescriptor();
        ClassDescriptor parentDescriptor = getDescriptor().getInheritancePolicy().getParentDescriptor();
        while(parentDescriptor != null && parentDescriptor.getTables().contains(table)) {
            desc = parentDescriptor;
            parentDescriptor = parentDescriptor.getInheritancePolicy().getParentDescriptor();
        }
        return desc;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.helper;


/**
 * INTERNAL:
 * <p>
 * <b>Purpose</b>: Define a {_at_link Hashtable} that manages key equality by
 * reference, not equals(). This is required to track objects throughout the
 * lifecycle of a {_at_link oracle.toplink.essentials.sessions.UnitOfWork}, regardless if the
 * domain object redefines its equals() method. Additionally, this implementation
 * does <b>not</b> permit nulls.
 */

// J2SE imports
import java.io.*;
import java.util.*;

public class HardIdentityHashtable extends IdentityHashtable {
    static final long serialVersionUID = 1421746759512286392L;
    
    // the default initial capacity
    static final int DEFAULT_INITIAL_CAPACITY = 32;

    // the maximum capacity.
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // the loadFactor used when none specified in constructor.
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /** An "enum" of Enumeration types. */
    static final int KEYS = 0;

    /** An "enum" of Enumeration types. */
    static final int ELEMENTS = 1;
    private static EmptyEnumerator emptyEnumerator = new EmptyEnumerator();
    protected transient Entry[] entries;// internal array of Entry's
    protected transient int count = 0;
    protected int threshold = 0;
    protected float loadFactor = 0;

    /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with the given
     * initial capacity and the given loadFactor.
     *
     * @param initialCapacity the initial capacity of this
     * <tt>HardIdentityHashtable</tt>.
     * @param loadFactor the loadFactor of the <tt>HardIdentityHashtable</tt>.
     * @throws IllegalArgumentException if the initial capacity is less
     * than zero, or if the loadFactor is nonpositive.
     */
    public HardIdentityHashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal initialCapacity: " + initialCapacity);
        }
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;
        }
        if ((loadFactor <= 0) || Float.isNaN(loadFactor)) {
            throw new IllegalArgumentException("Illegal loadFactor: " + loadFactor);
        }

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity) {
            capacity <<= 1;
        }
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        entries = new Entry[capacity];
    }

   /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with the given
     * initial capacity and a default loadFactor of <tt>0.75</tt>.
     *
     * @param initialCapacity the initial capacity of the
     * <tt>HardIdentityHashtable</tt>.
     * @throws <tt>IllegalArgumentException</tt> if the initial capacity is less
     * than zero.
     */
    public HardIdentityHashtable(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with a default initial
     * capacity of <tt>32</tt> and a loadfactor of <tt>0.75</tt>.
     */
    public HardIdentityHashtable() {
        loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        entries = new Entry[DEFAULT_INITIAL_CAPACITY];
    }
    
    /**
     * Removes all of the mappings from this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized void clear() {
        if (count > 0) {
            Entry[] copyOfEntries = entries;
            for (int i = copyOfEntries.length; --i >= 0;) {
                copyOfEntries[i] = null;
            }
            count = 0;
        }
    }

    /**
     * Returns a shallow copy of this <tt>HardIdentityHashtable</tt> (the
     * elements are not cloned).
     *
     * @return a shallow copy of this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized Object clone() {
        Entry[] copyOfEntries = entries;
        HardIdentityHashtable clone = (HardIdentityHashtable)super.clone();
        clone.entries = new Entry[copyOfEntries.length];
        for (int i = copyOfEntries.length; i-- > 0;) {
            clone.entries[i] = (copyOfEntries[i] != null) ? (Entry)copyOfEntries[i].clone() : null;
        }
        return clone;
    }

    /**
     * Returns <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains
     * the given object. Equality is tested by the equals() method.
     *
     * @param obj the object to find.
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains
     * obj.
     * @throws <tt>NullPointerException</tt> if obj is null</tt>.
     */
    public synchronized boolean contains(Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }

        Entry[] copyOfEntries = entries;
        for (int i = copyOfEntries.length; i-- > 0;) {
            for (Entry e = copyOfEntries[i]; e != null; e = e.next) {
                if (e.getValue().equals(obj)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains a
     * mapping for the given key. Equality is tested by reference.
     *
     * @param key object to be used as a key into this
     * <tt>HardIdentityHashtable</tt>.
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains a
     * mapping for key.
     */
    public synchronized boolean containsKey(Object key) {
        Entry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (Entry e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.key == key) {
                return true;
            }
        }
        return false;
    }

    public synchronized Enumeration elements() {
        if (count == 0) {
            return emptyEnumerator;
        } else {
            return new Enumerator(ELEMENTS);
        }
    }

    /**
     * Returns the value to which the given key is mapped in this
     * <tt>HardIdentityHashtable</tt>. Returns <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> contains no mapping for this key.
     *
     * @return the value to which this <tt>HardIdentityHashtable</tt> maps the
     * given key.
     * @param key key whose associated value is to be returned.
     */
    public synchronized Object get(Object key) {
        Entry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (Entry e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.key == key) {
                return e.getValue();
            }
        }
        return null;
    }

    /**
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> is empty.
     */
    public boolean isEmpty() {
        return (count == 0);
    }

    public synchronized Enumeration keys() {
        if (count == 0) {
            return emptyEnumerator;
        } else {
            return new Enumerator(KEYS);
        }
    }

    /**
     * Associate the given object with the given key in this
     * <tt>HardIdentityHashtable</tt>, replacing any existing mapping.
     *
     * @param key key to map to given object.
     * @param obj object to be associated with key.
     * @return the previous object for key or <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> did not have one.
     * @throws <tt>NullPointerException</tt> if obj is null</tt>.
     */
    public synchronized Object put(Object key, Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }

        Entry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (Entry e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.key == key) {
                Object old = e.getValue();
                e.setValue(obj);
                return old;
            }
        }

        if (count >= threshold) {
            rehash();
            copyOfEntries = entries;
            index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        }
        Entry e = new Entry(hash, key, obj, copyOfEntries[index]);
        copyOfEntries[index] = e;
        count++;
        return null;
    }

    /**
     * INTERNAL:
     * Re-builds the internal array of Entry's with a larger capacity.
     * This method is called automatically when the number of objects in this
     * HardIdentityHashtable exceeds its current threshold.
     */
    private void rehash() {
        int oldCapacity = entries.length;
        Entry[] oldEntries = entries;
        int newCapacity = (oldCapacity * 2) + 1;
        Entry[] newEntries = new Entry[newCapacity];
        threshold = (int)(newCapacity * loadFactor);
        entries = newEntries;
        for (int i = oldCapacity; i-- > 0;) {
            for (Entry old = oldEntries[i]; old != null;) {
                Entry e = old;
                old = old.next;
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newEntries[index];
                newEntries[index] = e;
            }
        }
    }

    /**
     * Removes the mapping (key and its corresponding value) from this
     * <tt>HardIdentityHashtable</tt>, if present.
     *
     * @param key key whose mapping is to be removed from the map.
     * @return the previous object for key or <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> did not have one.
     */
    public synchronized Object remove(Object key) {
        Entry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (Entry e = copyOfEntries[index], prev = null; e != null; prev = e, e = e.next) {
            if (e.key == key) {
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    copyOfEntries[index] = e.next;
                }
                count--;
                return e.getValue();
            }
        }
        return null;
    }

    /**
     * @return the size of this <tt>HardIdentityHashtable</tt>.
     */
    public int size() {
        return count;
    }

    /**
     * Return the string representation of this <tt>HardIdentityHashtable</tt>.
     *
     * @return the string representation of this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized String toString() {
        int max = size() - 1;
        StringBuffer buf = new StringBuffer();
        Enumeration k = keys();
        Enumeration e = elements();
        buf.append("{");
        for (int i = 0; i <= max; i++) {
            String s1 = k.nextElement().toString();
            String s2 = e.nextElement().toString();
            buf.append(s1 + "=" + s2);
            if (i < max) {
                buf.append(", ");
            }
        }
        buf.append("}");
        return buf.toString();
    }

    /**
     * Serialize the state of this <tt>HardIdentityHashtable</tt> to a stream.
     *
     * @serialData The <i>capacity</i> of the <tt>HardIdentityHashtable</tt>
     * (the length of the bucket array) is emitted (int), followed by the
     * <i>size</i> of the <tt>HardIdentityHashtable</tt>, followed by the
     * key-value mappings (in no particular order).
     */
    private void writeObject(ObjectOutputStream s) throws IOException {
        // Write out the threshold, loadfactor (and any hidden 'magic' stuff).
        s.defaultWriteObject();

        // Write out number of buckets
        s.writeInt(entries.length);
        // Write out count
        s.writeInt(count);
        // Write out contents
        for (int i = entries.length - 1; i >= 0; i--) {
            Entry entry = entries[i];
            while (entry != null) {
                s.writeObject(entry.key);
                s.writeObject(entry.getValue());
                entry = entry.next;
            }
        }
    }

    /**
     * Deserialize the <tt>HardIdentityHashtable</tt> from a stream.
     */
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        // Read in the threshold, loadfactor (and any hidden 'magic' stuff).
        s.defaultReadObject();

        // Read in number of buckets and allocate the bucket array;
        int numBuckets = s.readInt();
        entries = new Entry[numBuckets];
        // Read in size (count)
        int size = s.readInt();

        // Read the mappings and add to the TopLinkIdentityHashMap
        for (int i = 0; i < size; i++) {
            Object key = s.readObject();
            Object value = s.readObject();
            put(key, value);
        }
    }

    private static class EmptyEnumerator implements Enumeration {
        EmptyEnumerator() {
        }

        public boolean hasMoreElements() {
            return false;
        }

        public Object nextElement() {
            throw new NoSuchElementException();
        }
    }

    class Enumerator implements Enumeration {
        int enumeratorType;
        int index;
        Entry entry;

        Enumerator(int enumeratorType) {
            this.enumeratorType = enumeratorType;
            index = HardIdentityHashtable.this.entries.length;
        }

        public boolean hasMoreElements() {
            if (entry != null) {
                return true;
            }
            while (index-- > 0) {
                if ((entry = HardIdentityHashtable.this.entries[index]) != null) {
                    return true;
                }
            }
            return false;
        }

        public Object nextElement() {
            if (entry == null) {
                while ((index-- > 0) && ((entry = HardIdentityHashtable.this.entries[index]) == null)) {
                    ;
                }
            }
            if (entry != null) {
                Entry e = entry;
                entry = e.next;
                if (enumeratorType == KEYS) {
                    return e.key;
                } else {
                    return e.getValue();
                }
            }
            throw new NoSuchElementException("IdentityHashtable.Enumerator");
        }
    }

    /**
     * HardIdentityHashtable entry.
     */
    private static class Entry {
        int hash;
        Object key;
        Object value;
        Entry next;

        Entry(int hash, Object key, Object value, Entry next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        protected Object clone() {
            return new Entry(hash, key, value, ((next == null) ? null : (Entry)next.clone()));
        }

        public Object getKey() {
            return key;
        }

        public Object getValue() {
            return value;
        }

        public Object setValue(Object value) {
            Object oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof Entry)) {
                return false;
            }

            Entry e = (Entry)o;
            return (key == e.getKey()) && ((value == null) ? (e.getValue() == null) : value.equals(e.getValue()));
        }

        public int hashCode() {
            return hash ^ ((value == null) ? 0 : value.hashCode());
        }

        public String toString() {
            return key + "=" + value;
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.helper;

import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.sql.Timestamp;

import oracle.toplink.essentials.internal.security.PrivilegedAccessHelper;
import oracle.toplink.essentials.internal.security.PrivilegedNewInstanceFromClass;
import oracle.toplink.essentials.internal.security.PrivilegedGetField;
import oracle.toplink.essentials.internal.security.PrivilegedGetMethod;
import oracle.toplink.essentials.exceptions.*;

/**
 * INTERNAL:
 * <p>
 * <b>Purpose</b>: Define any usefull methods that are missing from the base Java.
 */
public class Helper implements Serializable {

    /** Used to configure JDBC level date optimization. */
    protected static boolean shouldOptimizeDates = false;

    /** Used to store null values in hashtables, is helper because need to be serializable. */
    protected static final Object nullWrapper = new Helper();

    /** PERF: Used to cache a set of calendars for conversion/printing purposes. */
    protected static Vector calendarCache = new Vector(10);

    /** PERF: Cache default timezone for calendar conversion. */
    protected static TimeZone defaultTimeZone = TimeZone.getDefault();

    // Changed static initialization to lazy initialization for bug 2756643

    /** Store CR string, for some reason \n is not platform independent. */
    protected static String CR = null;

    /** Prime the platform-dependent path separator */
    protected static String PATH_SEPARATOR = null;

    /** Prime the platform-dependent file separator */
    protected static String FILE_SEPARATOR = null;

    /** Prime the platform-dependent current working directory */
    protected static String CURRENT_WORKING_DIRECTORY = null;

    /** Prime the platform-dependent temporary directory */
    protected static String TEMP_DIRECTORY = null;

    /**
     * Return if JDBC date access should be optimized.
     */
    public static boolean shouldOptimizeDates() {
        return shouldOptimizeDates;
    }

    /**
     * Return if JDBC date access should be optimized.
     */
    public static void setShouldOptimizeDates(boolean value) {
        shouldOptimizeDates = value;
    }

    /**
     * PERF: This is used to optimize Calendar conversion/printing.
     * This should only be used when a calendar is temporarily required,
     * when finished it must be released back.
     */
    public static Calendar allocateCalendar() {
        Calendar calendar = null;
        synchronized (calendarCache) {
            if (calendarCache.size() > 0) {
                calendar = (Calendar)calendarCache.remove(calendarCache.size() - 1);
            }
        }
        if (calendar == null) {
            calendar = Calendar.getInstance();
        }
        return calendar;
    }

    /**
     * PERF: Return the cached default platform.
     * Used for ensuring Calendar are in the local timezone.
     * The JDK method clones the timezone, so cache it locally.
     */
    public static TimeZone getDefaultTimeZone() {
        return defaultTimeZone;
    }

    /**
     * PERF: This is used to optimize Calendar conversion/printing.
     * This should only be used when a calendar is temporarily required,
     * when finished it must be released back.
     */
    public static void releaseCalendar(Calendar calendar) {
        if (calendarCache.size() < 10) {
            calendarCache.add(calendar);
        }
    }

    public static void addAllToVector(Vector theVector, Vector elementsToAdd) {
        for (Enumeration stream = elementsToAdd.elements(); stream.hasMoreElements();) {
            theVector.addElement(stream.nextElement());
        }
    }

    public static void addAllToVector(Vector theVector, List elementsToAdd) {
        theVector.addAll(elementsToAdd);
    }

    public static Vector addAllUniqueToVector(Vector theVector, Vector elementsToAdd) {
        for (Enumeration stream = elementsToAdd.elements(); stream.hasMoreElements();) {
            Object element = stream.nextElement();
            if (!theVector.contains(element)) {
                theVector.addElement(element);
            }
        }

        return theVector;
    }

    /**
    * Convert the specified vector into an array.
    */
    public static Object[] arrayFromVector(Vector vector) {
        Object[] result = new Object[vector.size()];
        for (int i = 0; i < vector.size(); i++) {
            result[i] = vector.elementAt(i);
        }
        return result;
    }

    /**
     * Convert the HEX string to a byte array.
     * HEX allows for binary data to be printed.
     */
    public static byte[] buildBytesFromHexString(String hex) {
        String tmpString = (String)hex;
        if ((tmpString.length() % 2) != 0) {
            throw ConversionException.couldNotConvertToByteArray(hex);
        }
        byte[] bytes = new byte[tmpString.length() / 2];
        int byteIndex;
        int strIndex;
        byte digit1;
        byte digit2;
        for (byteIndex = bytes.length - 1, strIndex = tmpString.length() - 2; byteIndex >= 0;
                 byteIndex--, strIndex -= 2) {
            digit1 = (byte)Character.digit(tmpString.charAt(strIndex), 16);
            digit2 = (byte)Character.digit(tmpString.charAt(strIndex + 1), 16);
            if ((digit1 == -1) || (digit2 == -1)) {
                throw ConversionException.couldNotBeConverted(hex, ClassConstants.APBYTE);
            }
            bytes[byteIndex] = (byte)((digit1 * 16) + digit2);
        }
        return bytes;
    }

    /**
     * Convert the passed Vector to a Hashtable
     * Return the Hashtable
     */
    public static Hashtable buildHashtableFromVector(Vector theVector) {
        Hashtable toReturn = new Hashtable(theVector.size());

        Iterator iter = theVector.iterator();
        while (iter.hasNext()) {
            Object next = iter.next();
            toReturn.put(next, next);
        }
        return toReturn;
    }

    /**
     * Convert the byte array to a HEX string.
     * HEX allows for binary data to be printed.
     */
    public static String buildHexStringFromBytes(byte[] bytes) {
        char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        StringBuffer stringBuffer = new StringBuffer();
        int tempByte;
        for (int byteIndex = 0; byteIndex < ((byte[])bytes).length; byteIndex++) {
            tempByte = ((byte[])bytes)[byteIndex];
            if (tempByte < 0) {
                tempByte = tempByte + 256;//compensate for the fact that byte is signed in Java
            }
            tempByte = (byte)(tempByte / 16);//get the first digit
            if (tempByte > 16) {
                throw ConversionException.couldNotBeConverted(bytes, ClassConstants.STRING);
            }
            stringBuffer.append(hexArray[tempByte]);

            tempByte = ((byte[])bytes)[byteIndex];
            if (tempByte < 0) {
                tempByte = tempByte + 256;
            }
            tempByte = (byte)(tempByte % 16);//get the second digit
            if (tempByte > 16) {
                throw ConversionException.couldNotBeConverted(bytes, ClassConstants.STRING);
            }
            stringBuffer.append(hexArray[tempByte]);
        }
        return stringBuffer.toString();
    }

    /**
      * Create a new Vector containing all of the hashtable elements
      *
      */
    public static Vector buildVectorFromHashtableElements(Hashtable hashtable) {
        Vector vector = new Vector(hashtable.size());
        Enumeration enumeration = hashtable.elements();

        while (enumeration.hasMoreElements()) {
            vector.addElement(enumeration.nextElement());
        }

        return vector;
    }

    /**
      * Create a new Vector containing all of the map elements.
      */
    public static Vector buildVectorFromMapElements(Map map) {
        Vector vector = new Vector(map.size());
        Iterator iterator = map.values().iterator();

        while (iterator.hasNext()) {
            vector.addElement(iterator.next());
        }

        return vector;
    }

    /**
      * Create a new Vector containing all of the hashtable elements
      *
      */
    public static Vector buildVectorFromHashtableElements(HardIdentityHashtable hashtable) {
        Vector vector = new Vector(hashtable.size());
        Enumeration enumeration = hashtable.elements();

        while (enumeration.hasMoreElements()) {
            vector.addElement(enumeration.nextElement());
        }

        return vector;
    }

    /**
     * Answer a Calendar from a date.
     */
    public static Calendar calendarFromUtilDate(java.util.Date date) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        //In jdk1.3, millisecond is missing
        if (date instanceof Timestamp) {
            calendar.set(Calendar.MILLISECOND, ((Timestamp)date).getNanos() / 1000000);
        }
        return calendar;
    }

    /**
     * INTERNAL:
     * Return whether a Class implements a specific interface, either directly or indirectly
     * (through interface or implementation inheritance).
     * @return boolean
     */
    public static boolean classImplementsInterface(Class aClass, Class anInterface) {
        // quick check
        if (aClass == anInterface) {
            return true;
        }

        Class[] interfaces = aClass.getInterfaces();

        // loop through the "directly declared" interfaces
        for (int i = 0; i < interfaces.length; i++) {
            if (interfaces[i] == anInterface) {
                return true;
            }
        }

        // recurse through the interfaces
        for (int i = 0; i < interfaces.length; i++) {
            if (classImplementsInterface(interfaces[i], anInterface)) {
                return true;
            }
        }

        // finally, recurse up through the superclasses to Object
        Class superClass = aClass.getSuperclass();
        if (superClass == null) {
            return false;
        }
        return classImplementsInterface(superClass, anInterface);
    }

    /**
     * INTERNAL:
     * Return whether a Class is a subclass of, or the same as, another Class.
     * @return boolean
     */
    public static boolean classIsSubclass(Class subClass, Class superClass) {
        Class temp = subClass;

        if (superClass == null) {
            return false;
        }

        while (temp != null) {
            if (temp == superClass) {
                return true;
            }
            temp = temp.getSuperclass();
        }
        return false;
    }

    public static boolean compareArrays(Object[] array1, Object[] array2) {
        if (array1.length != array2.length) {
            return false;
        }
        for (int index = 0; index < array1.length; index++) {
            //Related to Bug#3128838 fix. ! is added to correct the logic.
            if (!array1[index].equals(array2[index])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Compare two BigDecimals.
     * This is required because the .equals method of java.math.BigDecimal ensures that
     * the scale of the two numbers are equal. Therefore 0.0 != 0.00.
     * @see java.math.BigDecimal#equals(Object)
     */
    public static boolean compareBigDecimals(java.math.BigDecimal one, java.math.BigDecimal two) {
        if (one.scale() != two.scale()) {
            double doubleOne = ((java.math.BigDecimal)one).doubleValue();
            double doubleTwo = ((java.math.BigDecimal)two).doubleValue();
            if ((doubleOne != Double.POSITIVE_INFINITY) && (doubleOne != Double.NEGATIVE_INFINITY) && (doubleTwo != Double.POSITIVE_INFINITY) && (doubleTwo != Double.NEGATIVE_INFINITY)) {
                return doubleOne == doubleTwo;
            }
        }
        return one.equals(two);
    }

    public static boolean compareByteArrays(byte[] array1, byte[] array2) {
        if (array1.length != array2.length) {
            return false;
        }
        for (int index = 0; index < array1.length; index++) {
            if (array1[index] != array2[index]) {
                return false;
            }
        }
        return true;
    }

    public static boolean compareCharArrays(char[] array1, char[] array2) {
        if (array1.length != array2.length) {
            return false;
        }
        for (int index = 0; index < array1.length; index++) {
            if (array1[index] != array2[index]) {
                return false;
            }
        }
        return true;
    }

    /**
    * PUBLIC:
    *
    * Compare two vectors of types. Return true if the size of the vectors is the
    * same and each of the types in the first Vector are assignable from the types
    * in the corresponding objects in the second Vector.
    */
    public static boolean areTypesAssignable(Vector types1, Vector types2) {
        if ((types1 == null) || (types2 == null)) {
            return false;
        }

        if (types1.size() == types2.size()) {
            for (int i = 0; i < types1.size(); i++) {
                Class type1 = (Class)types1.elementAt(i);
                Class type2 = (Class)types2.elementAt(i);

                // if either are null then we assume assignability.
                if ((type1 != null) && (type2 != null)) {
                    if (!type1.isAssignableFrom(type2)) {
                        return false;
                    }
                }
            }
            return true;
        }

        return false;
    }

    /**
      * PUBLIC:
      * Compare the elements in 2 hashtables to see if they are equal
      *
      * Added Nov 9, 2000 JED Patch 2.5.1.8
      */
    public static boolean compareHashtables(Hashtable hashtable1, Hashtable hashtable2) {
        Enumeration enumtr;
        Object element;
        Hashtable clonedHashtable;

        if (hashtable1.size() != hashtable2.size()) {
            return false;
        }

        clonedHashtable = (Hashtable)hashtable2.clone();

        enumtr = hashtable1.elements();
        while (enumtr.hasMoreElements()) {
            element = enumtr.nextElement();
            if (clonedHashtable.remove(element) == null) {
                return false;
            }
        }

        return clonedHashtable.isEmpty();
    }

    /**
     * Compare the elements in two <code>Vector</code>s to see if they are equal.
     * The order of the elements is significant.
     * @return whether the two vectors are equal
     */
    public static boolean compareOrderedVectors(Vector vector1, Vector vector2) {
        if (vector1 == vector2) {
            return true;
        }
        if (vector1.size() != vector2.size()) {
            return false;
        }
        for (int index = 0; index < vector1.size(); index++) {
            Object element1 = vector1.elementAt(index);
            Object element2 = vector2.elementAt(index);
            if (element1 == null) {// avoid null pointer exception
                if (element2 != null) {
                    return false;
                }
            } else {
                if (!element1.equals(element2)) {
                    return false;
                }
            }
        }
        return true;
    }

    /**
     * Compare the elements in two <code>Vector</code>s to see if they are equal.
     * The order of the elements is ignored.
     * @param v1 a vector
     * @param v2 a vector
     * @return whether the two vectors contain the same elements
     */
    public static boolean compareUnorderedVectors(Vector v1, Vector v2) {
        if (v1 == v2) {
            return true;
        }
        if (v1.size() != v2.size()) {
            return false;
        }

        // One of the Vectors must be cloned so we don't miscompare
        // vectors with the same elements but in different quantities.
        // e.g. [fred, sam, sam] != [fred, sam, fred]
        Vector v3 = (Vector)v2.clone();
        for (int i = 0; i < v1.size(); i++) {
            Object e1 = v1.elementAt(i);
            if (e1 == null) {// avoid null pointer exception
                // Helper.removeNullElement() will return false if the element was not present to begin with
                if (!removeNullElement(v3)) {
                    return false;
                }
            } else {
                // Vector.removeElement() will return false if the element was not present to begin with
                if (!v3.removeElement(e1)) {
                    return false;
                }
            }
        }
        return true;
    }

    public static Hashtable concatenateHashtables(Hashtable first, Hashtable second) {
        Hashtable concatenation;
        Object key;
        Object value;

        concatenation = new Hashtable(first.size() + second.size() + 4);

        for (Enumeration keys = first.keys(); keys.hasMoreElements();) {
            key = keys.nextElement();
            value = first.get(key);
            concatenation.put(key, value);
        }

        for (Enumeration keys = second.keys(); keys.hasMoreElements();) {
            key = keys.nextElement();
            value = second.get(key);
            concatenation.put(key, value);
        }

        return concatenation;
    }
    
    /**
     * Merge the two Maps into a new HashMap.
     */
    public static Map concatenateMaps(Map first, Map second) {
        Map concatenation = new HashMap(first.size() + second.size() + 4);

        for (Iterator keys = first.keySet().iterator(); keys.hasNext();) {
            Object key = keys.next();
            Object value = first.get(key);
            concatenation.put(key, value);
        }

        for (Iterator keys = second.keySet().iterator(); keys.hasNext();) {
            Object key = keys.next();
            Object value = second.get(key);
            concatenation.put(key, value);
        }

        return concatenation;
    }

    /**
      * Return a new vector with no duplicated values
      *
      */
    public static Vector concatenateUniqueVectors(Vector first, Vector second) {
        Vector concatenation;
        Object element;

        concatenation = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (Enumeration stream = first.elements(); stream.hasMoreElements();) {
            concatenation.addElement(stream.nextElement());
        }

        for (Enumeration stream = second.elements(); stream.hasMoreElements();) {
            element = stream.nextElement();
            if (!concatenation.contains(element)) {
                concatenation.addElement(element);
            }
        }

        return concatenation;

    }

    public static Vector concatenateVectors(Vector first, Vector second) {
        Vector concatenation;

        concatenation = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (Enumeration stream = first.elements(); stream.hasMoreElements();) {
            concatenation.addElement(stream.nextElement());
        }

        for (Enumeration stream = second.elements(); stream.hasMoreElements();) {
            concatenation.addElement(stream.nextElement());
        }

        return concatenation;

    }

    /**
     * Returns whether the given <code>Vector</code> contains a <code>null</code> element
     * Return <code>true</code> if the Vector contains a null element
     * Return <code>false</code> otherwise.
     * This is needed in jdk1.1, where <code>Vector.contains(Object)</code>
     * for a <code>null</code> element will result in a <code>NullPointerException</code>....
     */
    public static boolean containsNull(Vector v, int index) {
        return indexOfNullElement(v, 0) != -1;
    }

    /** Return a copy of the vector containing a subset starting at startIndex
     * and ending at stopIndex.
     * @param vector - original vector
     * @param startIndex - starting position in vector
     * @param stopIndex - ending position in vector
     * @exception TopLinkException
     */
    public static Vector copyVector(Vector originalVector, int startIndex, int stopIndex) throws ValidationException {
        Vector newVector;

        if (stopIndex < startIndex) {
            return oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        }

        if ((startIndex < 0) || (startIndex > originalVector.size())) {
            throw ValidationException.startIndexOutOfRange();
        }

        if ((stopIndex < 0) || (stopIndex > originalVector.size())) {
            throw ValidationException.stopIndexOutOfRange();
        }

        newVector = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (int index = startIndex; index < stopIndex; index++) {
            newVector.addElement(originalVector.elementAt(index));
        }

        return newVector;
    }

    /**
     * Return a string containing the platform-appropriate
     * characters for carriage return.
     */
    public static String cr() {
        // bug 2756643
        if (CR == null) {
            CR = System.getProperty("line.separator");
        }
        return CR;
    }

    /**
     * Return the name of the "current working directory".
     */
    public static String currentWorkingDirectory() {
        // bug 2756643
        if (CURRENT_WORKING_DIRECTORY == null) {
            CURRENT_WORKING_DIRECTORY = System.getProperty("user.dir");
        }
        return CURRENT_WORKING_DIRECTORY;
    }

    /**
     * Return the name of the "temporary directory".
     */
    public static String tempDirectory() {
        // Bug 2756643
        if (TEMP_DIRECTORY == null) {
            TEMP_DIRECTORY = System.getProperty("java.io.tmpdir");
        }
        return TEMP_DIRECTORY;
    }

    /**
     * Answer a Date from a long
     *
     * This implementation is based on the java.sql.Date class, not java.util.Date.
     * @param longObject - milliseconds from the epoch (00:00:00 GMT
     * Jan 1, 1970). Negative values represent dates prior to the epoch.
     */
    public static java.sql.Date dateFromLong(Long longObject) {
        return new java.sql.Date(longObject.longValue());
    }
    
    /**
     * Answer a Date with the year, month, date.
     * This builds a date avoiding the deprecated, inefficient and concurrency bottleneck date constructors.
     * This implementation is based on the java.sql.Date class, not java.util.Date.
     * The year, month, day are the values calendar uses,
     * i.e. year is from 0, month is 0-11, date is 1-31.
     */
    public static java.sql.Date dateFromYearMonthDate(int year, int month, int day) {
        // Use a calendar to compute the correct millis for the date.
        Calendar localCalendar = allocateCalendar();
        localCalendar.clear();
        localCalendar.set(year, month, day, 0, 0, 0);
        long millis = JavaPlatform.getTimeInMillis(localCalendar);
        java.sql.Date date = new java.sql.Date(millis);
        releaseCalendar(localCalendar);
        return date;
    }

    /**
     * Answer a Date from a string representation.
     * The string MUST be a valid date and in one of the following
     * formats: YYYY/MM/DD, YYYY-MM-DD, YY/MM/DD, YY-MM-DD.
     *
     * This implementation is based on the java.sql.Date class, not java.util.Date.
     *
     * The Date class contains some minor gotchas that you have to watch out for.
     * @param dateString - string representation of date
     * @return - date representation of string
     */
    public static java.sql.Date dateFromString(String dateString) throws ConversionException {
        int year;
        int month;
        int day;
        StringTokenizer dateStringTokenizer;

        if (dateString.indexOf('/') != -1) {
            dateStringTokenizer = new StringTokenizer(dateString, "/");
        } else if (dateString.indexOf('-') != -1) {
            dateStringTokenizer = new StringTokenizer(dateString, "- ");
        } else {
            throw ConversionException.incorrectDateFormat(dateString);
        }

        try {
            year = Integer.parseInt(dateStringTokenizer.nextToken());
            month = Integer.parseInt(dateStringTokenizer.nextToken());
            day = Integer.parseInt(dateStringTokenizer.nextToken());
        } catch (NumberFormatException exception) {
            throw ConversionException.incorrectDateFormat(dateString);
        }

        // Java returns the month in terms of 0 - 11 instead of 1 - 12.
        month = month - 1;

        return dateFromYearMonthDate(year, month, day);
    }

    /**
     * Answer a Date from a timestamp
     *
     * This implementation is based on the java.sql.Date class, not java.util.Date.
     * @param timestampObject - timestamp representation of date
     * @return - date representation of timestampObject
     */
    public static java.sql.Date dateFromTimestamp(java.sql.Timestamp timestamp) {
        return sqlDateFromUtilDate(timestamp);
    }

    /**
     * Returns true if the file of this name does indeed exist
     */
    public static boolean doesFileExist(String fileName) {
        try {
            new FileReader(fileName);
        } catch (FileNotFoundException fnfException) {
            return false;
        }

        return true;

    }

    /**
     * Double up \ to allow printing of directories for source code generation.
     */
    public static String doubleSlashes(String path) {
        StringBuffer buffer = new StringBuffer(path.length() + 5);
        for (int index = 0; index < path.length(); index++) {
            char charater = path.charAt(index);
            buffer.append(charater);
            if (charater == '\\') {
                buffer.append('\\');
            }
        }

        return buffer.toString();
    }

    /**
     * Extracts the actual path to the jar file.
     */
    public static String extractJarNameFromURL(java.net.URL url) {
        String tempName = url.getFile();
        int start = tempName.indexOf("file:") + 5;
        int end = tempName.indexOf("!/");
        return tempName.substring(start, end);
    }

    /**
     * Return a string containing the platform-appropriate
     * characters for separating directory and file names.
     */
    public static String fileSeparator() {
        //Bug 2756643
        if (FILE_SEPARATOR == null) {
            FILE_SEPARATOR = System.getProperty("file.separator");
        }
        return FILE_SEPARATOR;
    }

    /**
     * INTERNAL:
     * Returns a Field for the specified Class and field name.
     * Uses Class.getDeclaredField(String) to find the field.
     * If the field is not found on the specified class
     * the superclass is checked, and so on, recursively.
     * Set accessible to true, so we can access private/package/protected fields.
     */
    public static Field getField(Class javaClass, String fieldName) throws NoSuchFieldException {
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
            try {
                return (Field)AccessController.doPrivileged(new PrivilegedGetField(javaClass, fieldName, true));
            } catch (PrivilegedActionException exception) {
                if (exception.getCause() instanceof NoSuchMethodException) {
                   throw (NoSuchFieldException)exception.getCause();
               }
               else {
                   // really shouldn't happen
                   throw (RuntimeException)exception.getCause();
               }
            }
        } else {
            return PrivilegedAccessHelper.getField(javaClass, fieldName, true);
        }
    }

    /**
     * INTERNAL:
     * Returns a Method for the specified Class, method name,
     * and formal parameter types.
     * Uses Class.getDeclaredMethod(String Class[]) to find the method.
     * If the method is not found on the specified class
     * the superclass is checked, and so on, recursively.
     * Set accessible to true, so we can access private/package/protected methods.
     */
    public static Method getDeclaredMethod(Class javaClass, String methodName, Class[] methodParameterTypes) throws NoSuchMethodException {
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
            try {
                return (Method)AccessController.doPrivileged(new PrivilegedGetMethod(javaClass, methodName, methodParameterTypes, true));
            } catch (PrivilegedActionException exception) {
                if (exception.getCause() instanceof NoSuchMethodException) {
                   throw (NoSuchMethodException)exception.getCause();
               }
               else {
                   // really shouldn't happen
                   throw (RuntimeException)exception.getCause();
               }
            }
        } else {
            return PrivilegedAccessHelper.getMethod(javaClass, methodName, methodParameterTypes, true);
        }
    }

    /**
     * Return the class instance from the class
     */
    public static Object getInstanceFromClass(Class classFullName) {
        if (classFullName == null) {
            return null;
        }

        try {
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    return AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(classFullName));
                } catch (PrivilegedActionException exception) {
                    Exception throwableException = exception.getException();
                    if (throwableException instanceof InstantiationException) {
                        ValidationException exc = new ValidationException();
                        exc.setInternalException(throwableException);
                        throw exc;
                    } else {
                        ValidationException exc = new ValidationException();
                        exc.setInternalException(throwableException);
                        throw exc;
                    }
                }
            } else {
                return PrivilegedAccessHelper.newInstanceFromClass(classFullName);
            }
        } catch (InstantiationException notInstantiatedException) {
            ValidationException exception = new ValidationException();
            exception.setInternalException(notInstantiatedException);
            throw exception;
        } catch (IllegalAccessException notAccessedException) {
            ValidationException exception = new ValidationException();
            exception.setInternalException(notAccessedException);
            throw exception;
        }
    }

    /**
     * Used to store null values in hashtables, is helper because need to be serializable.
     */
    public static Object getNullWrapper() {
        return nullWrapper;
    }

    /**
     * Returns the object class. If a class is primitive return its non primitive class
     */
    public static Class getObjectClass(Class javaClass) {
        return ConversionManager.getObjectClass(javaClass);
    }

    /**
     * Answers the unqualified class name for the provided class.
     */
    public static String getShortClassName(Class javaClass) {
        return getShortClassName(javaClass.getName());
    }

    /**
     * Answers the unqualified class name from the specified String.
     */
    public static String getShortClassName(String javaClassName) {
        return javaClassName.substring(javaClassName.lastIndexOf('.') + 1);
    }

    /**
     * Answers the unqualified class name for the specified object.
     */
    public static String getShortClassName(Object object) {
        return getShortClassName(object.getClass());
    }

    /**
     * return a package name for the specified class.
     */
    public static String getPackageName(Class javaClass) {
        String className = Helper.getShortClassName(javaClass);
        return javaClass.getName().substring(0, (javaClass.getName().length() - (className.length() + 1)));
    }

    /**
     * Return a string containing the specified number of tabs.
     */
    public static String getTabs(int noOfTabs) {
        StringWriter writer = new StringWriter();
        for (int index = 0; index < noOfTabs; index++) {
            writer.write("\t");
        }
        return writer.toString();
    }

    /**
     * Returns the index of the the first <code>null</code> element found in the specified
     * <code>Vector</code> starting the search at the starting index specified.
     * Return an int >= 0 and less than size if a <code>null</code> element was found.
     * Return -1 if a <code>null</code> element was not found.
     * This is needed in jdk1.1, where <code>Vector.contains(Object)</code>
     * for a <code>null</code> element will result in a <code>NullPointerException</code>....
     */
    public static int indexOfNullElement(Vector v, int index) {
        for (int i = index; i < v.size(); i++) {
            if (v.elementAt(i) == null) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Return true if the object implements the Collection interface
     * Creation date: (9/7/00 1:59:51 PM)
     * @return boolean
     * @param testObject java.lang.Object
     */
    public static boolean isCollection(Object testObject) {
        // Does it implement the Collection interface
        if (testObject instanceof Collection) {
            return true;
        }

        // It's not a collection
        return false;
    }

    /**
     * ADVANCED
     * returns true if the class in question is a primitive wrapper
     */
    public static boolean isPrimitiveWrapper(Class classInQuestion) {
        return classInQuestion.equals(Character.class) || classInQuestion.equals(Boolean.class) || classInQuestion.equals(Byte.class) || classInQuestion.equals(Short.class) || classInQuestion.equals(Integer.class) || classInQuestion.equals(Long.class) || classInQuestion.equals(Float.class) || classInQuestion.equals(Double.class);
    }

    /**
     * Returns true if the string given is an all upper case string
     */
    public static boolean isUpperCaseString(String s) {
        char[] c = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (Character.isLowerCase(c[i])) {
                return false;
            }
        }
        return true;
    }

    /**
     * Returns true if the character given is a vowel. I.e. one of a,e,i,o,u,A,E,I,O,U.
     */
    public static boolean isVowel(char c) {
        return (c == 'A') || (c == 'a') || (c == 'e') || (c == 'E') || (c == 'i') || (c == 'I') || (c == 'o') || (c == 'O') || (c == 'u') || (c == 'U');
    }

    /**
     * Return an array of the files in the specified directory.
     * This allows us to simplify jdk1.1 code a bit.
     */
    public static File[] listFilesIn(File directory) {
        if (directory.isDirectory()) {
            return directory.listFiles();
        } else {
            return new File[0];
        }
    }

    /**
     * Make a Vector from the passed object.
     * If it's a Collection, iterate over the collection and add each item to the Vector.
     * If it's not a collection create a Vector and add the object to it.
     */
    public static Vector makeVectorFromObject(Object theObject) {
        if (theObject instanceof Vector) {
            return ((Vector)theObject);
        }
        if (theObject instanceof Collection) {
            Vector returnVector = new Vector(((Collection)theObject).size());
            Iterator iterator = ((Collection)theObject).iterator();
            while (iterator.hasNext()) {
                returnVector.add(iterator.next());
            }
            return returnVector;
        }

        Vector returnVector = new Vector();
        returnVector.addElement(theObject);
        return returnVector;
    }

    /**
     * Return a string containing the platform-appropriate
     * characters for separating entries in a path (e.g. the classpath)
     */
    public static String pathSeparator() {
        // Bug 2756643
        if (PATH_SEPARATOR == null) {
            PATH_SEPARATOR = System.getProperty("path.separator");
        }
        return PATH_SEPARATOR;
    }

    /**
     * Return a String containing the printed stacktrace of an exception.
     */
    public static String printStackTraceToString(Throwable aThrowable) {
        StringWriter swriter = new StringWriter();
        PrintWriter writer = new PrintWriter(swriter, true);
        aThrowable.printStackTrace(writer);
        writer.close();
        return swriter.toString();
    }

    /* Return a string representation of a number of milliseconds in terms of seconds, minutes, or
     * milliseconds, whichever is most appropriate.
     */
    public static String printTimeFromMilliseconds(long milliseconds) {
        if ((milliseconds > 1000) && (milliseconds < 60000)) {
            return (milliseconds / 1000) + "s";
        }
        if (milliseconds > 60000) {
            return (milliseconds / 60000) + "min " + printTimeFromMilliseconds(milliseconds % 60000);
        }
        return milliseconds + "ms";
    }

    /**
     * Given a Vector, print it, even if there is a null in it
     */
    public static String printVector(Vector vector) {
        StringWriter stringWriter = new StringWriter();
        stringWriter.write("[");
        Enumeration enumtr = vector.elements();
        stringWriter.write(String.valueOf(enumtr.nextElement()));
        while (enumtr.hasMoreElements()) {
            stringWriter.write(" ");
            stringWriter.write(String.valueOf(enumtr.nextElement()));
        }
        stringWriter.write("]");
        return stringWriter.toString();

    }

    public static Hashtable rehashHashtable(Hashtable table) {
        Hashtable rehashedTable = new Hashtable(table.size() + 2);

        Enumeration values = table.elements();
        for (Enumeration keys = table.keys(); keys.hasMoreElements();) {
            Object key = keys.nextElement();
            Object value = values.nextElement();
            rehashedTable.put(key, value);
        }

        return rehashedTable;
    }
    
    public static Map rehashMap(Map table) {
        HashMap rehashedTable = new HashMap(table.size() + 2);

        Iterator values = table.values().iterator();
        for (Iterator keys = table.keySet().iterator(); keys.hasNext();) {
            Object key = keys.next();
            Object value = values.next();
            rehashedTable.put(key, value);
        }

        return rehashedTable;
    }

    /**
     * Returns a String which has had enough non-alphanumeric characters removed to be equal to
     * the maximumStringLength.
     */
    public static String removeAllButAlphaNumericToFit(String s1, int maximumStringLength) {
        int s1Size = s1.length();
        if (s1Size <= maximumStringLength) {
            return s1;
        }

        // Remove the necessary number of characters
        StringBuffer buf = new StringBuffer();
        int numberOfCharsToBeRemoved = s1.length() - maximumStringLength;
        int s1Index = 0;
        while ((numberOfCharsToBeRemoved > 0) && (s1Index < s1Size)) {
            char currentChar = s1.charAt(s1Index);
            if (Character.isLetterOrDigit(currentChar)) {
                buf.append(currentChar);
            } else {
                numberOfCharsToBeRemoved--;
            }
            s1Index++;
        }

        // Append the rest of the character that were not parsed through.
        // Is it quicker to build a substring and append that?
        while (s1Index < s1Size) {
            buf.append(s1.charAt(s1Index));
            s1Index++;
        }

        //
        return buf.toString();
    }

    /**
     * Returns a String which has had enough of the specified character removed to be equal to
     * the maximumStringLength.
     */
    public static String removeCharacterToFit(String s1, char aChar, int maximumStringLength) {
        int s1Size = s1.length();
        if (s1Size <= maximumStringLength) {
            return s1;
        }

        // Remove the necessary number of characters
        StringBuffer buf = new StringBuffer();
        int numberOfCharsToBeRemoved = s1.length() - maximumStringLength;
        int s1Index = 0;
        while ((numberOfCharsToBeRemoved > 0) && (s1Index < s1Size)) {
            char currentChar = s1.charAt(s1Index);
            if (currentChar == aChar) {
                numberOfCharsToBeRemoved--;
            } else {
                buf.append(currentChar);
            }
            s1Index++;
        }

        // Append the rest of the character that were not parsed through.
        // Is it quicker to build a substring and append that?
        while (s1Index < s1Size) {
            buf.append(s1.charAt(s1Index));
            s1Index++;
        }

        //
        return buf.toString();
    }

    /**
     * Remove the first <code>null</code> element found in the specified <code>Vector</code>.
     * Return <code>true</code> if a <code>null</code> element was found and removed.
     * Return <code>false</code> if a <code>null</code> element was not found.
     * This is needed in jdk1.1, where <code>Vector.removeElement(Object)</code>
     * for a <code>null</code> element will result in a <code>NullPointerException</code>....
     */
    public static boolean removeNullElement(Vector v) {
        int indexOfNull = indexOfNullElement(v, 0);
        if (indexOfNull != -1) {
            v.removeElementAt(indexOfNull);
            return true;
        }
        return false;
    }

    /**
     * Returns a String which has had enough of the specified character removed to be equal to
     * the maximumStringLength.
     */
    public static String removeVowels(String s1) {
        // Remove the vowels
        StringBuffer buf = new StringBuffer();
        int s1Size = s1.length();
        int s1Index = 0;
        while (s1Index < s1Size) {
            char currentChar = s1.charAt(s1Index);
            if (!isVowel(currentChar)) {
                buf.append(currentChar);
            }
            s1Index++;
        }

        //
        return buf.toString();
    }

    /**
     * Replaces the first subString of the source with the replacement.
     */
    public static String replaceFirstSubString(String source, String subString, String replacement) {
        int index = source.indexOf(subString);

        if (index >= 0) {
            return source.substring(0, index) + replacement + source.substring(index + subString.length());
        }
        return null;
    }

    public static Vector reverseVector(Vector theVector) {
        Vector tempVector = new Vector(theVector.size());
        Object currentElement;

        for (int i = theVector.size() - 1; i > -1; i--) {
            currentElement = theVector.elementAt(i);
            tempVector.addElement(currentElement);
        }

        return tempVector;
    }

    /**
     * Returns a new string with all space characters removed from the right
     *
     * @param originalString - timestamp representation of date
     * @return - String
     */
    public static String rightTrimString(String originalString) {
        int len = originalString.length();
        while ((len > 0) && (originalString.charAt(len - 1) <= ' ')) {
            len--;
        }
        return originalString.substring(0, len);
    }

    /**
     * Returns a String which is a concatenation of two string which have had enough
     * vowels removed from them so that the sum of the sized of the two strings is less than
     * or equal to the specified size.
     */
    public static String shortenStringsByRemovingVowelsToFit(String s1, String s2, int maximumStringLength) {
        int size = s1.length() + s2.length();
        if (size <= maximumStringLength) {
            return s1 + s2;
        }

        // Remove the necessary number of characters
        int s1Size = s1.length();
        int s2Size = s2.length();
        StringBuffer buf1 = new StringBuffer();
        StringBuffer buf2 = new StringBuffer();
        int numberOfCharsToBeRemoved = size - maximumStringLength;
        int s1Index = 0;
        int s2Index = 0;
        int modulo2 = 0;

        // While we still want to remove characters, and not both string are done.
        while ((numberOfCharsToBeRemoved > 0) && !((s1Index >= s1Size) && (s2Index >= s2Size))) {
            if ((modulo2 % 2) == 0) {
                // Remove from s1
                if (s1Index < s1Size) {
                    if (isVowel(s1.charAt(s1Index))) {
                        numberOfCharsToBeRemoved--;
                    } else {
                        buf1.append(s1.charAt(s1Index));
                    }
                    s1Index++;
                }
            } else {
                // Remove from s2
                if (s2Index < s2Size) {
                    if (isVowel(s2.charAt(s2Index))) {
                        numberOfCharsToBeRemoved--;
                    } else {
                        buf2.append(s2.charAt(s2Index));
                    }
                    s2Index++;
                }
            }
            modulo2++;
        }

        // Append the rest of the character that were not parsed through.
        // Is it quicker to build a substring and append that?
        while (s1Index < s1Size) {
            buf1.append(s1.charAt(s1Index));
            s1Index++;
        }
        while (s2Index < s2Size) {
            buf2.append(s2.charAt(s2Index));
            s2Index++;
        }

        //
        return buf1.toString() + buf2.toString();
    }

    /**
     * Answer a sql.Date from a timestamp.
     */
    public static java.sql.Date sqlDateFromUtilDate(java.util.Date utilDate) {
        // PERF: Avoid deprecated get methods, that are now very inefficient.
        Calendar calendar = allocateCalendar();
        calendar.setTime(utilDate);
        java.sql.Date date = dateFromCalendar(calendar);
        releaseCalendar(calendar);
        return date;
    }

    /**
     * Print the sql.Date.
     */
    public static String printDate(java.sql.Date date) {
        // PERF: Avoid deprecated get methods, that are now very inefficient and used from toString.
        Calendar calendar = allocateCalendar();
        calendar.setTime(date);
        String string = printDate(calendar);
        releaseCalendar(calendar);
        return string;
    }

    /**
     * Print the date part of the calendar.
     */
    public static String printDate(Calendar calendar) {
        return printDate(calendar, true);
    }

    /**
     * Print the date part of the calendar.
     * Normally the calendar must be printed in the local time, but if the timezone is printed,
     * it must be printing in its timezone.
     */
    public static String printDate(Calendar calendar, boolean useLocalTime) {
        int year;
        int month;
        int day;
        if (useLocalTime && (!defaultTimeZone.equals(calendar.getTimeZone()))) {
            // Must convert the calendar to the local timezone if different, as dates have no timezone (always local).
            Calendar localCalendar = allocateCalendar();
            JavaPlatform.setTimeInMillis(localCalendar, JavaPlatform.getTimeInMillis(calendar));
            year = localCalendar.get(Calendar.YEAR);
            month = localCalendar.get(Calendar.MONTH) + 1;
            day = localCalendar.get(Calendar.DATE);
            releaseCalendar(localCalendar);
        } else {
            year = calendar.get(Calendar.YEAR);
            month = calendar.get(Calendar.MONTH) + 1;
            day = calendar.get(Calendar.DATE);
        }

        char[] buf = "2000-00-00".toCharArray();
        buf[0] = Character.forDigit(year / 1000, 10);
        buf[1] = Character.forDigit((year / 100) % 10, 10);
        buf[2] = Character.forDigit((year / 10) % 10, 10);
        buf[3] = Character.forDigit(year % 10, 10);
        buf[5] = Character.forDigit(month / 10, 10);
        buf[6] = Character.forDigit(month % 10, 10);
        buf[8] = Character.forDigit(day / 10, 10);
        buf[9] = Character.forDigit(day % 10, 10);

        return new String(buf);
    }

    /**
     * Print the sql.Time.
     */
    public static String printTime(java.sql.Time time) {
        // PERF: Avoid deprecated get methods, that are now very inefficient and used from toString.
        Calendar calendar = allocateCalendar();
        calendar.setTime(time);
        String string = printTime(calendar);
        releaseCalendar(calendar);
        return string;
    }

    /**
     * Print the time part of the calendar.
     */
    public static String printTime(Calendar calendar) {
        return printTime(calendar, true);
    }

    /**
     * Print the time part of the calendar.
     * Normally the calendar must be printed in the local time, but if the timezone is printed,
     * it must be printing in its timezone.
     */
    public static String printTime(Calendar calendar, boolean useLocalTime) {
        int hour;
        int minute;
        int second;
        if (useLocalTime && (!defaultTimeZone.equals(calendar.getTimeZone()))) {
            // Must convert the calendar to the local timezone if different, as dates have no timezone (always local).
            Calendar localCalendar = allocateCalendar();
            JavaPlatform.setTimeInMillis(localCalendar, JavaPlatform.getTimeInMillis(calendar));
            hour = localCalendar.get(Calendar.HOUR_OF_DAY);
            minute = localCalendar.get(Calendar.MINUTE);
            second = localCalendar.get(Calendar.SECOND);
            releaseCalendar(localCalendar);
        } else {
            hour = calendar.get(Calendar.HOUR_OF_DAY);
            minute = calendar.get(Calendar.MINUTE);
            second = calendar.get(Calendar.SECOND);
        }
        String hourString;
        String minuteString;
        String secondString;
        if (hour < 10) {
            hourString = "0" + hour;
        } else {
            hourString = Integer.toString(hour);
        }
        if (minute < 10) {
            minuteString = "0" + minute;
        } else {
            minuteString = Integer.toString(minute);
        }
        if (second < 10) {
            secondString = "0" + second;
        } else {
            secondString = Integer.toString(second);
        }
        return (hourString + ":" + minuteString + ":" + secondString);
    }

    /**
     * Print the Calendar.
     */
    public static String printCalendar(Calendar calendar) {
        return printCalendar(calendar, true);
    }

    /**
     * Print the Calendar.
     * Normally the calendar must be printed in the local time, but if the timezone is printed,
     * it must be printing in its timezone.
     */
    public static String printCalendar(Calendar calendar, boolean useLocalTime) {
        String millisString;

        // String zeros = "000000000";
        if (calendar.get(Calendar.MILLISECOND) == 0) {
            millisString = "0";
        } else {
            millisString = buildZeroPrefixAndTruncTrailZeros(calendar.get(Calendar.MILLISECOND), 3);
        }

        StringBuffer timestampBuf = new StringBuffer();
        timestampBuf.append(printDate(calendar, useLocalTime));
        timestampBuf.append(" ");
        timestampBuf.append(printTime(calendar, useLocalTime));
        timestampBuf.append(".");
        timestampBuf.append(millisString);

        return timestampBuf.toString();
    }

    /**
     * Print the sql.Timestamp.
     */
    public static String printTimestamp(java.sql.Timestamp timestamp) {
        // PERF: Avoid deprecated get methods, that are now very inefficient and used from toString.
        Calendar calendar = allocateCalendar();
        calendar.setTime(timestamp);

        String nanosString;

        // String zeros = "000000000";
        String yearZeros = "0000";

        if (timestamp.getNanos() == 0) {
            nanosString = "0";
        } else {
            nanosString = buildZeroPrefixAndTruncTrailZeros(timestamp.getNanos(), 9);
        }

        StringBuffer timestampBuf = new StringBuffer();
        timestampBuf.append(printDate(calendar));
        timestampBuf.append(" ");
        timestampBuf.append(printTime(calendar));
        timestampBuf.append(".");
        timestampBuf.append(nanosString);

        releaseCalendar(calendar);

        return (timestampBuf.toString());
    }

    /**
     * Build a numerical string with leading 0s. number is an existing number that
     * the new string will be built on. totalDigits is the number of the required
     * digits of the string.
     */
    public static String buildZeroPrefix(int number, int totalDigits) {
        String zeros = "000000000";
        int absValue = (number < 0) ? (-number) : number;
        String numbString = Integer.toString(absValue);

        // Add leading zeros
        numbString = zeros.substring(0, (totalDigits - numbString.length())) + numbString;

        if (number < 0) {
            numbString = "-" + numbString;
        } else {
            numbString = "+" + numbString;
        }
        return numbString;
    }
 
    /**
     * Build a numerical string with leading 0s and truncate trailing zeros. number is
     * an existing number that the new string will be built on. totalDigits is the number
     * of the required digits of the string.
     */
    public static String buildZeroPrefixAndTruncTrailZeros(int number, int totalDigits) {
        String zeros = "000000000";
        String numbString = Integer.toString(number);

        // Add leading zeros
        numbString = zeros.substring(0, (totalDigits - numbString.length())) + numbString;
        // Truncate trailing zeros
        char[] numbChar = new char[numbString.length()];
        numbString.getChars(0, numbString.length(), numbChar, 0);
        int truncIndex = totalDigits - 1;
        while (numbChar[truncIndex] == '0') {
            truncIndex--;
        }
        return new String(numbChar, 0, truncIndex + 1);
    }

    /**
     * Print the sql.Timestamp without the nanos portion.
     */
    public static String printTimestampWithoutNanos(java.sql.Timestamp timestamp) {
        // PERF: Avoid deprecated get methods, that are now very inefficient and used from toString.
        Calendar calendar = allocateCalendar();
        calendar.setTime(timestamp);
        String string = printCalendarWithoutNanos(calendar);
        releaseCalendar(calendar);
        return string;
    }

    /**
     * Print the Calendar without the nanos portion.
     */
    public static String printCalendarWithoutNanos(Calendar calendar) {
        StringBuffer timestampBuf = new StringBuffer();
        timestampBuf.append(printDate(calendar));
        timestampBuf.append(" ");
        timestampBuf.append(printTime(calendar));
        return timestampBuf.toString();
    }

    /**
     * Answer a sql.Date from a Calendar.
     */
    public static java.sql.Date dateFromCalendar(Calendar calendar) {
        if (!defaultTimeZone.equals(calendar.getTimeZone())) {
            // Must convert the calendar to the local timezone if different, as dates have no timezone (always local).
            Calendar localCalendar = allocateCalendar();
            JavaPlatform.setTimeInMillis(localCalendar, JavaPlatform.getTimeInMillis(calendar));
            java.sql.Date date = dateFromYearMonthDate(localCalendar.get(Calendar.YEAR), localCalendar.get(Calendar.MONTH), localCalendar.get(Calendar.DATE));
            releaseCalendar(localCalendar);
            return date;
        }
        return dateFromYearMonthDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE));
    }

    /**
     * Can be used to mark code if a workaround is added for a JDBC driver or other bug.
     */
    public static void systemBug(String description) {
        // Use sender to find what is needy.
    }

    /**
     * Answer a Time from a Date
     *
     * This implementation is based on the java.sql.Date class, not java.util.Date.
     * @param timestampObject - time representation of date
     * @return - time representation of dateObject
     */
    public static java.sql.Time timeFromDate(java.util.Date date) {
        // PERF: Avoid deprecated get methods, that are now very inefficient.
        Calendar calendar = allocateCalendar();
        calendar.setTime(date);
        java.sql.Time time = timeFromCalendar(calendar);
        releaseCalendar(calendar);
        return time;
    }

    /**
     * Answer a Time from a long
     *
     * @param longObject - milliseconds from the epoch (00:00:00 GMT
     * Jan 1, 1970). Negative values represent dates prior to the epoch.
     */
    public static java.sql.Time timeFromLong(Long longObject) {
        return new java.sql.Time(longObject.longValue());
    }
        
    /**
     * Answer a Time with the hour, minute, second.
     * This builds a time avoiding the deprecated, inefficient and concurrency bottleneck date constructors.
     * The hour, minute, second are the values calendar uses,
     * i.e. year is from 0, month is 0-11, date is 1-31.
     */
    public static java.sql.Time timeFromHourMinuteSecond(int hour, int minute, int second) {
        // Use a calendar to compute the correct millis for the date.
        Calendar localCalendar = allocateCalendar();
        localCalendar.clear();
        localCalendar.set(1970, 0, 1, hour, minute, second);
        long millis = JavaPlatform.getTimeInMillis(localCalendar);
        java.sql.Time time = new java.sql.Time(millis);
        releaseCalendar(localCalendar);
        return time;
    }

    /**
     * Answer a Time from a string representation.
     * This method will accept times in the following
     * formats: HH-MM-SS, HH:MM:SS
     *
     * @param timeString - string representation of time
     * @return - time representation of string
     */
    public static java.sql.Time timeFromString(String timeString) throws ConversionException {
        int hour;
        int minute;
        int second;
        String timePortion = timeString;

        if (timeString.length() > 12) {
            // Longer strings are Timestamp format (ie. Sybase & Oracle)
            timePortion = timeString.substring(11, 19);
        }

        if ((timePortion.indexOf('-') == -1) && (timePortion.indexOf('/') == -1) && (timePortion.indexOf('.') == -1) && (timePortion.indexOf(':') == -1)) {
            throw ConversionException.incorrectTimeFormat(timePortion);
        }
        StringTokenizer timeStringTokenizer = new StringTokenizer(timePortion, " /:.-");

        try {
            hour = Integer.parseInt(timeStringTokenizer.nextToken());
            minute = Integer.parseInt(timeStringTokenizer.nextToken());
            second = Integer.parseInt(timeStringTokenizer.nextToken());
        } catch (NumberFormatException exception) {
            throw ConversionException.incorrectTimeFormat(timeString);
        }

        return timeFromHourMinuteSecond(hour, minute, second);
    }

    /**
     * Answer a Time from a Timestamp
     * Usus the Hours, Minutes, Seconds instead of getTime() ms value.
     */
    public static java.sql.Time timeFromTimestamp(java.sql.Timestamp timestamp) {
        return timeFromDate(timestamp);
    }

    /**
     * Answer a sql.Time from a Calendar.
     */
    public static java.sql.Time timeFromCalendar(Calendar calendar) {
        if (!defaultTimeZone.equals(calendar.getTimeZone())) {
            // Must convert the calendar to the local timezone if different, as dates have no timezone (always local).
            Calendar localCalendar = allocateCalendar();
            JavaPlatform.setTimeInMillis(localCalendar, JavaPlatform.getTimeInMillis(calendar));
            java.sql.Time date = timeFromHourMinuteSecond(localCalendar.get(Calendar.HOUR_OF_DAY), localCalendar.get(Calendar.MINUTE), localCalendar.get(Calendar.SECOND));
            releaseCalendar(localCalendar);
            return date;
        }
        return timeFromHourMinuteSecond(calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND));
    }

    /**
     * Answer a Timestamp from a Calendar.
     */
    public static java.sql.Timestamp timestampFromCalendar(Calendar calendar) {
        return timestampFromLong(JavaPlatform.getTimeInMillis(calendar));
    }

    /**
     * Answer a Timestamp from a java.util.Date.
     */
    public static java.sql.Timestamp timestampFromDate(java.util.Date date) {
        return timestampFromLong(date.getTime());
    }

    /**
     * Answer a Time from a long
     *
     * @param longObject - milliseconds from the epoch (00:00:00 GMT
     * Jan 1, 1970). Negative values represent dates prior to the epoch.
     */
    public static java.sql.Timestamp timestampFromLong(Long millis) {
        return timestampFromLong(millis.longValue());
    }

    /**
     * Answer a Time from a long
     *
     * @param longObject - milliseconds from the epoch (00:00:00 GMT
     * Jan 1, 1970). Negative values represent dates prior to the epoch.
     */
    public static java.sql.Timestamp timestampFromLong(long millis) {
        java.sql.Timestamp timestamp = new java.sql.Timestamp(millis);

        // P2.0.1.3: Didn't account for negative millis < 1970
        // Must account for the jdk millis bug where it does not set the nanos.
        if ((millis % 1000) > 0) {
            timestamp.setNanos((int)(millis % 1000) * 1000000);
        } else if ((millis % 1000) < 0) {
            timestamp.setNanos((int)(1000000000 - (Math.abs((millis % 1000) * 1000000))));
        }
        return timestamp;
    }

    /**
     * Answer a Timestamp from a string representation.
     * This method will accept strings in the following
     * formats: YYYY/MM/DD HH:MM:SS, YY/MM/DD HH:MM:SS, YYYY-MM-DD HH:MM:SS, YY-MM-DD HH:MM:SS
     *
     * @param timestampString - string representation of timestamp
     * @return - timestamp representation of string
     */
    public static java.sql.Timestamp timestampFromString(String timestampString) throws ConversionException {
        if ((timestampString.indexOf('-') == -1) && (timestampString.indexOf('/') == -1) && (timestampString.indexOf('.') == -1) && (timestampString.indexOf(':') == -1)) {
            throw ConversionException.incorrectTimestampFormat(timestampString);
        }
        StringTokenizer timestampStringTokenizer = new StringTokenizer(timestampString, " /:.-");

        int year;
        int month;
        int day;
        int hour;
        int minute;
        int second;
        int nanos;
        try {
            year = Integer.parseInt(timestampStringTokenizer.nextToken());
            month = Integer.parseInt(timestampStringTokenizer.nextToken());
            day = Integer.parseInt(timestampStringTokenizer.nextToken());
            try {
                hour = Integer.parseInt(timestampStringTokenizer.nextToken());
                minute = Integer.parseInt(timestampStringTokenizer.nextToken());
                second = Integer.parseInt(timestampStringTokenizer.nextToken());
            } catch (java.util.NoSuchElementException endOfStringException) {
                // May be only a date string desired to be used as a timestamp.
                hour = 0;
                minute = 0;
                second = 0;
            }
        } catch (NumberFormatException exception) {
            throw ConversionException.incorrectTimestampFormat(timestampString);
        }

        try {
            String nanoToken = timestampStringTokenizer.nextToken();
            nanos = Integer.parseInt(nanoToken);
            for (int times = 0; times < (9 - nanoToken.length()); times++) {
                nanos = nanos * 10;
            }
        } catch (java.util.NoSuchElementException endOfStringException) {
            nanos = 0;
        } catch (NumberFormatException exception) {
            throw ConversionException.incorrectTimestampFormat(timestampString);
        }

        // Java dates are based on year after 1900 so I need to delete it.
        year = year - 1900;

        // Java returns the month in terms of 0 - 11 instead of 1 - 12.
        month = month - 1;

        java.sql.Timestamp timestamp;
        // This was not converted to use Calendar for the conversion because calendars do not take nanos.
        // but it should be, and then just call setNanos.
        timestamp = new java.sql.Timestamp(year, month, day, hour, minute, second, nanos);
        return timestamp;
    }
    
    /**
     * Answer a Timestamp with the year, month, day, hour, minute, second.
     * The hour, minute, second are the values calendar uses,
     * i.e. year is from 0, month is 0-11, date is 1-31, time is 0-23/59.
     */
    public static java.sql.Timestamp timestampFromYearMonthDateHourMinuteSecondNanos(int year, int month, int date, int hour, int minute, int second, int nanos) {
        // This was not converted to use Calendar for the conversion because calendars do not take nanos.
        // but it should be, and then just call setNanos.
        return new java.sql.Timestamp(year - 1900, month, date, hour, minute, second, nanos);
    }

    /**
     * Can be used to mark code as need if something strange is seen.
     */
    public static void toDo(String description) {
        // Use sender to find what is needy.
    }

    /**
     * If the size of the original string is larger than the passed in size,
     * this method will remove the vowels from the original string.
     *
     * The removal starts backward from the end of original string, and stops if the
     * resulting string size is equal to the passed in size.
     *
     * If the resulting string is still larger than the passed in size after
     * removing all vowels, the end of the resulting string will be truncated.
     */
    public static String truncate(String originalString, int size) {
        if (originalString.length() <= size) {
            //no removal and truncation needed
            return originalString;
        }
        String vowels = "AaEeIiOoUu";
        StringBuffer newStringBufferTmp = new StringBuffer(originalString.length());

        //need to remove the extra characters
        int counter = originalString.length() - size;
        for (int index = (originalString.length() - 1); index >= 0; index--) {
            //search from the back to the front, if vowel found, do not append it to the resulting (temp) string!
            //i.e. if vowel not found, append the chararcter to the new string buffer.
            if (vowels.indexOf(originalString.charAt(index)) == -1) {
                newStringBufferTmp.append(originalString.charAt(index));
            } else {
                //vowel found! do NOT append it to the temp buffer, and decrease the counter
                counter--;
                if (counter == 0) {
                    //if the exceeded characters (counter) of vowel haven been removed, the total
                    //string size should be equal to the limits, so append the reversed remaining string
                    //to the new string, break the loop and return the shrunk string.
                    StringBuffer newStringBuffer = new StringBuffer(size);
                    newStringBuffer.append(originalString.substring(0, index));
                    //need to reverse the string
                    //bug fix: 3016423. append(BunfferString) is jdk1.4 version api. Use append(String) instead
                    //in order to support jdk1.3.
                    newStringBuffer.append(newStringBufferTmp.reverse().toString());
                    return newStringBuffer.toString();
                }
            }
        }

        //the shrunk string still too long, revrese the order back and truncate it!
        return newStringBufferTmp.reverse().toString().substring(0, size);
    }

    /**
     * Answer a Date from a long
     *
     * This implementation is based on the java.sql.Date class, not java.util.Date.
     * @param longObject - milliseconds from the epoch (00:00:00 GMT
     * Jan 1, 1970). Negative values represent dates prior to the epoch.
     */
    public static java.util.Date utilDateFromLong(Long longObject) {
        return new java.util.Date(longObject.longValue());
    }

    /**
     * Answer a java.util.Date from a sql.date
     *
     * @param sqlDate - sql.date representation of date
     * @return - java.util.Date representation of the sql.date
     */
    public static java.util.Date utilDateFromSQLDate(java.sql.Date sqlDate) {
        return new java.util.Date(sqlDate.getTime());
    }

    /**
     * Answer a java.util.Date from a sql.Time
     *
     * @param time - time representation of util date
     * @return - java.util.Date representation of the time
     */
    public static java.util.Date utilDateFromTime(java.sql.Time time) {
        return new java.util.Date(time.getTime());
    }

    /**
     * Answer a java.util.Date from a timestamp
     *
     * @param timestampObject - timestamp representation of date
     * @return - java.util.Date representation of timestampObject
     */
    public static java.util.Date utilDateFromTimestamp(java.sql.Timestamp timestampObject) {
        // Bug 2719624 - Conditionally remove workaround for java bug which truncated
        // nanoseconds from timestamp.getTime(). We will now only recalculate the nanoseconds
        // When timestamp.getTime() results in nanoseconds == 0;
        long time = timestampObject.getTime();
        boolean appendNanos = ((time % 1000) == 0);
        if (appendNanos) {
            return new java.util.Date(time + (timestampObject.getNanos() / 1000000));
        } else {
            return new java.util.Date(time);
        }
    }

    /**
    * Convert the specified array into a vector.
    */
    public static Vector vectorFromArray(Object[] array) {
        Vector result = new Vector(array.length);
        for (int i = 0; i < array.length; i++) {
            result.addElement(array[i]);
        }
        return result;
    }

    /**
     * Convert the byte array to a HEX string.
     * HEX allows for binary data to be printed.
     */
    public static void writeHexString(byte[] bytes, Writer writer) throws IOException {
        writer.write(buildHexStringFromBytes(bytes));
    }
}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package oracle.toplink.essentials.internal.helper;

import java.io.Serializable;
import java.util.Dictionary;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author adam
 */
public abstract class IdentityHashtable extends Dictionary implements Cloneable, Serializable{

        public abstract boolean containsKey(Object key);
        public abstract boolean contains(Object obj);
        
        public abstract void clear();
        
    @Override
        public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException ex) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError();
        }
     }


}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.identitymaps;

import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;

import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.internal.localization.*;
import oracle.toplink.essentials.logging.SessionLog;
import oracle.toplink.essentials.sessions.SessionProfiler;
import oracle.toplink.essentials.sessions.Record;
import oracle.toplink.essentials.internal.security.PrivilegedAccessHelper;
import oracle.toplink.essentials.internal.security.PrivilegedGetConstructorFor;
import oracle.toplink.essentials.internal.security.PrivilegedMethodInvoker;
import oracle.toplink.essentials.internal.security.PrivilegedInvokeConstructor;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: Maintain identity maps for domain classes mapped with TopLink.
 * <p><b>Responsibilities</b>:<ul>
 * <li> Build new identity maps lazily using info from the descriptor
 * <li> Insert objects into appropriate identity map
 * <li> Get object from appropriate identity map using object or primary key with class
 * <li> Get and Set write lock values for cached objects
 * </ul>
 * @since TOPLink/Java 1.0
 */
public class IdentityMapManager implements Serializable, Cloneable {

    /** A table of identity maps with the key being the domain Class. */
    protected Hashtable identityMaps;

    /** A table of identity maps with the key being the query */
    protected Map queryResults;

    /** A reference to the session owning this manager. */
    protected AbstractSession session;

    /** Ensure mutual exclusion depending on the cache isolation.*/
    protected transient ConcurrencyManager cacheMutex;

    /** Optimize the object retrival from the identity map. */
    protected IdentityMap lastAccessedIdentityMap = null;
    protected Class lastAccessedIdentityMapClass = null;

    /** Used to store the write lock manager used for merging. */
    protected transient WriteLockManager writeLockManager;

    /** PERF: Used to avoid readLock and profiler checks to improve performance. */
    protected Boolean isCacheAccessPreCheckRequired;

    public IdentityMapManager(AbstractSession session) {
        this.session = session;
        this.cacheMutex = new ConcurrencyManager();
        this.identityMaps = new Hashtable();
        this.queryResults = JavaPlatform.getQueryCacheMap();
    }

    /**
     * Provides access for setting a deferred lock on an object in the IdentityMap.
     */
    public CacheKey acquireDeferredLock(Vector primaryKey, Class domainClass, ClassDescriptor descriptor) {
        CacheKey cacheKey = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = getIdentityMap(descriptor).acquireDeferredLock(primaryKey);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            cacheKey = getIdentityMap(descriptor).acquireDeferredLock(primaryKey);
        }

        return cacheKey;
    }

    /**
     * Provides access for setting a concurrency lock on an object in the IdentityMap.
     * called with true from the merge process, if true then the refresh will not refresh the object
     * @see IdentityMap#aquire
     */
    public CacheKey acquireLock(Vector primaryKey, Class domainClass, boolean forMerge, ClassDescriptor descriptor) {
        CacheKey cacheKey = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = getIdentityMap(descriptor).acquireLock(primaryKey, forMerge);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            cacheKey = getIdentityMap(descriptor).acquireLock(primaryKey, forMerge);
        }

        return cacheKey;
    }

    /**
     * Provides access for setting a concurrency lock on an object in the IdentityMap.
     * called with true from the merge process, if true then the refresh will not refresh the object
     * @see IdentityMap#aquire
     */
    public CacheKey acquireLockNoWait(Vector primaryKey, Class domainClass, boolean forMerge, ClassDescriptor descriptor) {
        CacheKey cacheKey = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = getIdentityMap(descriptor).acquireLockNoWait(primaryKey, forMerge);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            cacheKey = getIdentityMap(descriptor).acquireLockNoWait(primaryKey, forMerge);
        }

        return cacheKey;
    }

    /**
     * PERF: Used to micro optimize cache access.
     * Avoid the readLock and profile checks if not required.
     */
    protected boolean isCacheAccessPreCheckRequired() {
        if (this.isCacheAccessPreCheckRequired == null) {
            if ((getSession().getProfiler() != null) || getSession().getDatasourceLogin().shouldSynchronizedReadOnWrite()) {
                this.isCacheAccessPreCheckRequired = Boolean.TRUE;
            } else {
                this.isCacheAccessPreCheckRequired = Boolean.FALSE;
            }
        }
        return this.isCacheAccessPreCheckRequired.booleanValue();
    }

    /**
     * Clear the cache access pre-check flag, used from session when profiler .
     */
    public void clearCacheAccessPreCheck() {
        this.isCacheAccessPreCheckRequired = null;
    }

    /**
     * Provides access for setting a concurrency lock on an IdentityMap.
     * @see IdentityMap#aquire
     */
    public void acquireReadLock() {
        getSession().startOperationProfile(SessionProfiler.CACHE);

        if (getSession().getDatasourceLogin().shouldSynchronizedReadOnWrite()) {
            getCacheMutex().acquireReadLock();
        }

        getSession().endOperationProfile(SessionProfiler.CACHE);
    }

    /**
     * INTERNAL:
     * Find the cachekey for the provided primary key and place a readlock on it.
     * This will allow multiple users to read the same object but prevent writes to
     * the object while the read lock is held.
     */
    public CacheKey acquireReadLockOnCacheKey(Vector primaryKey, Class domainClass, ClassDescriptor descriptor) {
        CacheKey cacheKey = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = getIdentityMap(descriptor).acquireReadLockOnCacheKey(primaryKey);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            cacheKey = getIdentityMap(descriptor).acquireReadLockOnCacheKey(primaryKey);
        }

        return cacheKey;
    }

    /**
     * INTERNAL:
     * Find the cachekey for the provided primary key and place a readlock on it.
     * This will allow multiple users to read the same object but prevent writes to
     * the object while the read lock is held.
     * If no readlock can be acquired then do not wait but return null.
     */
    public CacheKey acquireReadLockOnCacheKeyNoWait(Vector primaryKey, Class domainClass, ClassDescriptor descriptor) {
        CacheKey cacheKey = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = getIdentityMap(descriptor).acquireReadLockOnCacheKeyNoWait(primaryKey);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            cacheKey = getIdentityMap(descriptor).acquireReadLockOnCacheKeyNoWait(primaryKey);
        }

        return cacheKey;
    }

    /**
     * Lock the entire cache if the cache isolation requires.
     * By default concurrent reads and writes are allowed.
     * By write, unit of work merge is meant.
     */
    public boolean acquireWriteLock() {
        if (getSession().getDatasourceLogin().shouldSynchronizedReadOnWrite() || getSession().getDatasourceLogin().shouldSynchronizeWrites()) {
            getCacheMutex().acquire();
            return true;
        }
        return false;
    }

    /**
     * INTERNAL: (Public to allow testing to access)
     * Return a new empty identity map to cache instances of the class.
     */
    public IdentityMap buildNewIdentityMap(ClassDescriptor descriptor) throws ValidationException, DescriptorException {
        if (getSession().isUnitOfWork()) {
                if (((UnitOfWorkImpl)getSession()).getReferenceMode() == ReferenceMode.WEAK){
                        return new WeakIdentityMap(100);
                }
            return new FullIdentityMap(100);
        }

        try {
            Constructor constructor = null;
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    constructor = (Constructor)AccessController.doPrivileged(new PrivilegedGetConstructorFor(descriptor.getIdentityMapClass(), new Class[] { ClassConstants.PINT }, false));
                    return (IdentityMap)AccessController.doPrivileged(new PrivilegedInvokeConstructor(constructor, new Object[] { new Integer(descriptor.getIdentityMapSize())}));
                } catch (PrivilegedActionException exception) {
                    throw DescriptorException.invalidIdentityMap(descriptor, exception.getException());
                }
            } else {
                constructor = PrivilegedAccessHelper.getConstructorFor(descriptor.getIdentityMapClass(), new Class[] { ClassConstants.PINT }, false);
                return (IdentityMap)PrivilegedAccessHelper.invokeConstructor(constructor, new Object[] { new Integer(descriptor.getIdentityMapSize())});

            }
        } catch (Exception exception) {
            throw DescriptorException.invalidIdentityMap(descriptor, exception);
        }
    }

    /**
     * INTERNAL:
     * Clear the the lastAccessedIdentityMap and the lastAccessedIdentityMapClass
     */
    public void clearLastAccessedIdentityMap() {
        lastAccessedIdentityMap = null;
        lastAccessedIdentityMapClass = null;
    }

    /**
     * INTERNAL:
     * Clones itself, used for uow commit and resume on failure.
     */
    public Object clone() {
        IdentityMapManager manager = null;

        try {
            manager = (IdentityMapManager)super.clone();
            manager.setIdentityMaps(new Hashtable());
            for (Enumeration identityMapEnum = getIdentityMaps().keys();
                     identityMapEnum.hasMoreElements();) {
                Class theClass = (Class)identityMapEnum.nextElement();
                manager.getIdentityMaps().put(theClass, ((IdentityMap)getIdentityMaps().get(theClass)).clone());
            }
        } catch (Exception e) {
            ;
        }

        return manager;
    }

    /**
     * Clear all the query caches
     */
    public void clearQueryCache() {
        this.queryResults = JavaPlatform.getQueryCacheMap();
    }

    /**
     * Remove the cache key related to a query.
     * Note this method is not synchronized and care should be taken to ensure
     * there are no other threads accessing the cache key. This is used to clean up
     * cached clones of queries
     */
    public void clearQueryCache(ReadQuery query) {
        if (query != null) {
            queryResults.remove(query);
        }
    }

    public boolean containsKey(Vector key, Class theClass, ClassDescriptor descriptor) {
        // Check for null, contains causes null pointer.
        for (int index = 0; index < key.size(); index++) {
            if (key.elementAt(index) == null) {
                return false;
            }
        }

        IdentityMap map = getIdentityMap(descriptor);
        boolean contains;

        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                contains = map.containsKey(key);
            } finally {
                releaseReadLock();
                getSession().endOperationProfile(SessionProfiler.CACHE);
            }
        } else {
            contains = map.containsKey(key);
        }

        return contains;
    }

    /**
     * Query the cache in-memory.
     */
    public Vector getAllFromIdentityMap(Expression selectionCriteria, Class theClass, Record translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy, boolean shouldReturnInvalidatedObjects) {
        ClassDescriptor descriptor = getSession().getDescriptor(theClass);
        getSession().startOperationProfile(SessionProfiler.CACHE);
        Vector objects = null;
        try {
            Expression selectionCriteriaClone = selectionCriteria;

            // Only clone if required.
            if ((selectionCriteria != null) && (selectionCriteriaClone.getBuilder().getSession() == null)) {
                selectionCriteriaClone = (Expression)selectionCriteria.clone();
                selectionCriteriaClone.getBuilder().setSession(getSession().getRootSession(null));
                selectionCriteriaClone.getBuilder().setQueryClass(theClass);
            }
            objects = new Vector();
            IdentityMap map = getIdentityMap(descriptor);

            // cache the current time to avoid calculating it every time through the loop
            long currentTimeInMillis = System.currentTimeMillis();
            for (Enumeration cacheEnum = map.keys(); cacheEnum.hasMoreElements();) {
                CacheKey key = (CacheKey)cacheEnum.nextElement();
                if ((key.getObject() == null) || (!shouldReturnInvalidatedObjects && getSession().getDescriptor(theClass).getCacheInvalidationPolicy().isInvalidated(key, currentTimeInMillis))) {
                    continue;
                }
                Object object = key.getObject();

                // Bug # 3216337 - key.getObject() should check for null; object may be GC'd (MWN)
                if (object == null) {
                    continue;
                }

                // Must check for inheritance.
                if ((object.getClass() == theClass) || (theClass.isInstance(object))) {
                    if (selectionCriteriaClone == null) {
                        objects.addElement(object);
                        getSession().incrementProfile(SessionProfiler.CacheHits);
                    } else {
                        try {
                            if (selectionCriteriaClone.doesConform(object, getSession(), (AbstractRecord)translationRow, valueHolderPolicy)) {
                                objects.addElement(object);
                                getSession().incrementProfile(SessionProfiler.CacheHits);
                            }
                        } catch (QueryException queryException) {
                            if (queryException.getErrorCode() == QueryException.MUST_INSTANTIATE_VALUEHOLDERS) {
                                if (valueHolderPolicy.shouldIgnoreIndirectionExceptionReturnConformed()) {
                                    objects.addElement(object);
                                    getSession().incrementProfile(SessionProfiler.CacheHits);
                                } else if (valueHolderPolicy.shouldThrowIndirectionException()) {
                                    throw queryException;
                                }
                            } else {
                                throw queryException;
                            }
                        }
                    }
                }
            }
        } finally {
            getSession().endOperationProfile(SessionProfiler.CACHE);
        }
        return objects;
    }

    /**
     * INTERNAL:
     * Retrieve the cache key for the given identity information
     * @param Vector the primary key of the cache key to be retrieved
     * @param Class the class of the cache key to be retrieved
     * @return CacheKey
     */
    public CacheKey getCacheKeyForObject(Vector primaryKey, Class myClass, ClassDescriptor descriptor) {
        IdentityMap map = getIdentityMap(descriptor);
        CacheKey cacheKey = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = map.getCacheKey(primaryKey);
            } finally {
                releaseReadLock();
                getSession().endOperationProfile(SessionProfiler.CACHE);
            }
        } else {
            cacheKey = map.getCacheKey(primaryKey);
        }
        return cacheKey;
    }

    /**
     * Return the cache mutex.
     * This allows for the entire cache to be locked.
     * This is done for transaction isolations on merges, although never locked by default.
     */
    public ConcurrencyManager getCacheMutex() {
        return cacheMutex;
    }

    /**
     * INTERNAL:
     * This method is used to get a list of those classes with IdentityMaps in the Session.
     */
    public Vector getClassesRegistered() {
        Enumeration classes = getIdentityMaps().keys();
        Vector results = new Vector(getIdentityMaps().size());
        while (classes.hasMoreElements()) {
            results.add(((Class)classes.nextElement()).getName());
        }
        return results;
    }

    /**
     * Get the object from the identity map which has the same identity information
     * as the given object.
     */
    public Object getFromIdentityMap(Object object) {
        ClassDescriptor descriptor = getSession().getDescriptor(object);
        Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, getSession());
        return getFromIdentityMap(primaryKey, object.getClass(), descriptor);
    }

    /**
     * Get the object from the identity map which has the given primary key and class
     */
    public Object getFromIdentityMap(Vector key, Class theClass, ClassDescriptor descriptor) {
        return getFromIdentityMap(key, theClass, true, descriptor);
    }

    /**
     * Get the object from the identity map which has the given primary key and class
     * Only return the object if it has not Invalidated
     */
    public Object getFromIdentityMap(Vector key, Class theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) {
        if (key == null) {
            return null;
        }

        // Check for null, contains causes null pointer.
        for (int index = 0; index < key.size(); index++) {
            if (key.elementAt(index) == null) {
                return null;
            }
        }

        CacheKey cacheKey;
        IdentityMap map = getIdentityMap(descriptor);
        Object domainObject = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = map.getCacheKey(key);
            } finally {
                releaseReadLock();
            }
        } else {
            cacheKey = map.getCacheKey(key);
        }

        if ((cacheKey != null) && (shouldReturnInvalidatedObjects || !getSession().getDescriptor(theClass).getCacheInvalidationPolicy().isInvalidated(cacheKey, System.currentTimeMillis()))) {
            //bug 4772232- acquire readlock on cachekey then release to ensure object is fully built before being returned
            try {
                cacheKey.acquireReadLock();
            } finally {
                cacheKey.releaseReadLock();
            }
            //bug 4772232- acquire readlock on cachekey then release to ensure object is fully built before being returned
            try {
                cacheKey.acquireReadLock();
                domainObject = cacheKey.getObject();
            } finally {
                cacheKey.releaseReadLock();
            }
            //reslove the inheritance issues
            domainObject = checkForInheritance(domainObject, theClass);
        }

        if (isCacheAccessPreCheckRequired()) {
            getSession().endOperationProfile(SessionProfiler.CACHE);
            if (domainObject == null) {
                getSession().incrementProfile(SessionProfiler.CacheMisses);
            } else {
                getSession().incrementProfile(SessionProfiler.CacheHits);
            }
        }

        return domainObject;
    }

    public Object getFromIdentityMap(Expression selectionCriteria, Class theClass, Record translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy, boolean conforming, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) {
        UnitOfWorkImpl unitOfWork = (conforming) ? (UnitOfWorkImpl)getSession() : null;
        getSession().startOperationProfile(SessionProfiler.CACHE);
        try {
            Expression selectionCriteriaClone = selectionCriteria;

            // Only clone if required.
            if ((selectionCriteria != null) && (selectionCriteriaClone.getBuilder().getSession() == null)) {
                selectionCriteriaClone = (Expression)selectionCriteria.clone();
                selectionCriteriaClone.getBuilder().setSession(getSession().getRootSession(null));
                selectionCriteriaClone.getBuilder().setQueryClass(theClass);
            }
            IdentityMap map = getIdentityMap(descriptor);

            // cache the current time to avoid calculating it every time through the loop
            long currentTimeInMillis = System.currentTimeMillis();
            for (Enumeration cacheEnum = map.keys(); cacheEnum.hasMoreElements();) {
                CacheKey key = (CacheKey)cacheEnum.nextElement();
                if (!shouldReturnInvalidatedObjects && descriptor.getCacheInvalidationPolicy().isInvalidated(key, currentTimeInMillis)) {
                    continue;
                }
                Object object = key.getObject();

                // Bug # 3216337 - key.getObject() should check for null; object may be GC'd (MWN)
                if (object == null) {
                    continue;
                }

                // Must check for inheritance.
                if ((object.getClass() == theClass) || (theClass.isInstance(object))) {
                    if (selectionCriteriaClone == null) {
                        // bug 2782991: if first found was deleted nothing returned.
                        if (!(conforming && unitOfWork.isObjectDeleted(object))) {
                            getSession().incrementProfile(SessionProfiler.CacheHits);
                            return object;
                        }
                    }

                    //CR 3677 integration of a ValueHolderPolicy
                    try {
                        if (selectionCriteriaClone.doesConform(object, getSession(), (AbstractRecord)translationRow, valueHolderPolicy)) {
                            // bug 2782991: if first found was deleted nothing returned.
                            if (!(conforming && unitOfWork.isObjectDeleted(object))) {
                                getSession().incrementProfile(SessionProfiler.CacheHits);
                                return object;
                            }
                        }
                    } catch (QueryException queryException) {
                        if (queryException.getErrorCode() == QueryException.MUST_INSTANTIATE_VALUEHOLDERS) {
                            if (valueHolderPolicy.shouldIgnoreIndirectionExceptionReturnConformed()) {
                                // bug 2782991: if first found was deleted nothing returned.
                                if (!(conforming && unitOfWork.isObjectDeleted(object))) {
                                    getSession().incrementProfile(SessionProfiler.CacheHits);
                                    return object;
                                }
                            } else if (valueHolderPolicy.shouldIgnoreIndirectionExceptionReturnNotConformed()) {
                                // For bug 2667870 just skip this item, but do not abort.
                            } else {
                                throw queryException;
                            }
                        } else {
                            throw queryException;
                        }
                    }
                }
            }
        } finally {
            getSession().endOperationProfile(SessionProfiler.CACHE);
        }
        return null;
    }

    /**
     * Get the object from the cache with the given primary key and class
     * do not return the object if it was invalidated
     */
    public Object getFromIdentityMapWithDeferredLock(Vector key, Class theClass, boolean shouldReturnInvalidatedObjects, ClassDescriptor descriptor) {
        if (key == null) {
            getSession().incrementProfile(SessionProfiler.CacheMisses);
            return null;
        }

        // Check for null, contains causes null pointer.
        for (int index = 0; index < key.size(); index++) {
            if (key.elementAt(index) == null) {
                getSession().incrementProfile(SessionProfiler.CacheMisses);
                return null;
            }
        }

        IdentityMap map = getIdentityMap(descriptor);
        CacheKey cacheKey;
        Object domainObject = null;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                cacheKey = map.getCacheKey(key);
            } finally {
                releaseReadLock();
            }
        } else {
            cacheKey = map.getCacheKey(key);
        }

        if ((cacheKey != null) && (shouldReturnInvalidatedObjects || !descriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey, System.currentTimeMillis()))) {
            cacheKey.acquireDeferredLock();
            domainObject = cacheKey.getObject();
            cacheKey.releaseDeferredLock();
        }

        //reslove the inheritance issues
        domainObject = checkForInheritance(domainObject, theClass);

        if (isCacheAccessPreCheckRequired()) {
            getSession().endOperationProfile(SessionProfiler.CACHE);
            if (domainObject == null) {
                getSession().incrementProfile(SessionProfiler.CacheMisses);
            } else {
                getSession().incrementProfile(SessionProfiler.CacheHits);
            }
        }

        return domainObject;
    }

    /**
     * INTERNAL: (public to allow test cases to check)
     * Return the identity map for the class, if missing create a new one.
     */
    public IdentityMap getIdentityMap(ClassDescriptor descriptor) {
        IdentityMap identityMap;

        // Enusre that an im is only used for the root descriptor for inheritence.
        // This is required to obtain proper cache hits.
        if (descriptor.hasInheritance()) {
            descriptor = descriptor.getInheritancePolicy().getRootParentDescriptor();
        }
        Class descriptorClass = descriptor.getJavaClass();

        // PERF: Synchronize around caching of last accessed map and lookup/build,
        // note that get is synchronized anyway so this is not adding any additional synching.
        synchronized (this) {
            // Optimazition for object retrival.
            IdentityMap tempMap = this.lastAccessedIdentityMap;
            if ((tempMap != null) && (this.lastAccessedIdentityMapClass == descriptorClass)) {
                return tempMap;
            }

            // PERF: Only synch around creation.
            identityMap = (IdentityMap)getIdentityMaps().get(descriptorClass);
            if (identityMap == null) {
                identityMap = buildNewIdentityMap(descriptor);
                getIdentityMaps().put(descriptorClass, identityMap);
            }
            this.lastAccessedIdentityMap = identityMap;
            this.lastAccessedIdentityMapClass = descriptorClass;
        }
        return identityMap;
    }

    protected Hashtable getIdentityMaps() {
        return identityMaps;
    }
    
    /**
     * INTERNAL:
     *
     * @return an enumeration of the classes in the identity map.
     */
    public Enumeration getIdentityMapClasses() {
        return identityMaps.keys();
    }

    protected Vector getKey(Object domainObject) {
        return getSession().keyFromObject(domainObject);
    }

    protected AbstractSession getSession() {
        return session;
    }

    /**
     * Get the wrapper object from the cache key associated with the given primary key,
     * this is used for EJB.
     */
    public Object getWrapper(Vector primaryKey, Class theClass) {
        ClassDescriptor descriptor = getSession().getDescriptor(theClass);
        IdentityMap map = getIdentityMap(descriptor);
        Object wrapper;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                wrapper = map.getWrapper(primaryKey);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            wrapper = map.getWrapper(primaryKey);
        }
        return wrapper;
    }

    /**
     * INTERNAL:
     * Returns the single write Lock manager for this session
     */
    public WriteLockManager getWriteLockManager() {
        // With Isolated Sessions not all Identity maps need a WriteLockManager so
        //lazy initialize
        synchronized (this) {
            if (this.writeLockManager == null) {
                this.writeLockManager = new WriteLockManager();
            }
        }
        return this.writeLockManager;
    }

    /**
     * Retrieve the write lock value of the cache key associated with the given primary key,
     */
    public Object getWriteLockValue(Vector primaryKey, Class domainClass, ClassDescriptor descriptor) {
        IdentityMap map = getIdentityMap(descriptor);
        Object value;
        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            acquireReadLock();
            try {
                value = map.getWriteLockValue(primaryKey);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            value = map.getWriteLockValue(primaryKey);
        }
        return value;
    }

    /**
     * Reset the identity map for only the instances of the class.
     * For inheritence the user must make sure that they only use the root class.
     */
    public void initializeIdentityMap(Class theClass) throws TopLinkException {
        ClassDescriptor descriptor = getSession().getDescriptor(theClass);

        if (descriptor == null) {
            throw ValidationException.missingDescriptor(String.valueOf(theClass));
        }
        if (descriptor.isChildDescriptor()) {
            throw ValidationException.childDescriptorsDoNotHaveIdentityMap();
        }

        // Bug 3736313 - look up identity map by descriptor's java class
        Class javaClass = descriptor.getJavaClass();

        // bug 3745484 - clear the cached identity map if we are clearing the associated identity map
        if (javaClass == lastAccessedIdentityMapClass) {
            clearLastAccessedIdentityMap();
        }
        IdentityMap identityMap = buildNewIdentityMap(descriptor);
        getIdentityMaps().put(javaClass, identityMap);
    }

    public void initializeIdentityMaps() {
        clearLastAccessedIdentityMap();
        setIdentityMaps(new Hashtable());
        clearQueryCache();
    }

    /**
     * INTERNAL:
     * Used to print all the objects in the identity map of the passed in class.
     * The output of this method will be logged to this session's SessionLog at SEVERE level.
     */
    public void printIdentityMap(Class businessClass) {
        String cr = Helper.cr();
        ClassDescriptor descriptor = getSession().getDescriptor(businessClass);
        int cacheCounter = 0;
        StringWriter writer = new StringWriter();
        if (descriptor.isAggregateDescriptor()) {
            return;//do nothing if descriptor is aggregate
        }

        IdentityMap map = getIdentityMap(descriptor);
        writer.write(LoggingLocalization.buildMessage("identitymap_for", new Object[] { cr, Helper.getShortClassName(map.getClass()), Helper.getShortClassName(businessClass) }));
        if (descriptor.hasInheritance()) {
            if (descriptor.getInheritancePolicy().isRootParentDescriptor()) {
                writer.write(LoggingLocalization.buildMessage("includes"));
                Vector childDescriptors;
                childDescriptors = descriptor.getInheritancePolicy().getChildDescriptors();
                if ((childDescriptors != null) && (childDescriptors.size() != 0)) {//Bug#2675242
                    Enumeration enum2 = childDescriptors.elements();
                    writer.write(Helper.getShortClassName((Class)((ClassDescriptor)enum2.nextElement()).getJavaClass()));
                    while (enum2.hasMoreElements()) {
                        writer.write(", " + Helper.getShortClassName((Class)((ClassDescriptor)enum2.nextElement()).getJavaClass()));
                    }
                }
                writer.write(")");
            }
        }

        for (Enumeration enumtr = map.keys(); enumtr.hasMoreElements();) {
            oracle.toplink.essentials.internal.identitymaps.CacheKey cacheKey = (oracle.toplink.essentials.internal.identitymaps.CacheKey)enumtr.nextElement();
            Object object = cacheKey.getObject();
            if (businessClass.isInstance(object)) {
                cacheCounter++;
                if (object == null) {
                    writer.write(LoggingLocalization.buildMessage("key_object_null", new Object[] { cr, cacheKey.getKey(), "\t" }));
                } else {
                    writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, cacheKey.getKey(), "\t", String.valueOf(System.identityHashCode(object)), object }));
                }
            }
        }
        writer.write(LoggingLocalization.buildMessage("elements", new Object[] { cr, String.valueOf(cacheCounter) }));
        getSession().log(SessionLog.SEVERE, SessionLog.CACHE, writer.toString(), null, null, false);
    }

    /**
     * INTERNAL:
     * Used to print all the objects in every identity map in this session.
     * The output of this method will be logged to this session's SessionLog at SEVERE level.
     */
    public void printIdentityMaps() {
        for (Iterator iterator = getSession().getDescriptors().keySet().iterator();
                 iterator.hasNext();) {
            Class businessClass = (Class)iterator.next();
            ClassDescriptor descriptor = getSession().getDescriptor(businessClass);
            if (descriptor.hasInheritance()) {
                if (descriptor.getInheritancePolicy().isRootParentDescriptor()) {
                    printIdentityMap(businessClass);
                }
            } else {
                printIdentityMap(businessClass);
            }
        }
    }

    /**
     * INTERNAL:
     * Used to print all the Locks in every identity map in this session.
     * The output of this method will be logged to this session's SessionLog at FINEST level.
     */
    public void printLocks() {
        StringWriter writer = new StringWriter();
        HashMap threadCollection = new HashMap();
        writer.write(TraceLocalization.buildMessage("lock_writer_header", (Object[])null) + Helper.cr());
        Iterator idenityMapsIterator = this.session.getIdentityMapAccessorInstance().getIdentityMapManager().getIdentityMaps().values().iterator();
        while (idenityMapsIterator.hasNext()) {
            IdentityMap idenityMap = (IdentityMap)idenityMapsIterator.next();
            idenityMap.collectLocks(threadCollection);
        }
        Object[] parameters = new Object[1];
        for (Iterator threads = threadCollection.keySet().iterator(); threads.hasNext();) {
            Thread activeThread = (Thread)threads.next();
            parameters[0] = activeThread.getName();
            writer.write(TraceLocalization.buildMessage("active_thread", parameters) + Helper.cr());
            for (Iterator cacheKeys = ((HashSet)threadCollection.get(activeThread)).iterator();
                     cacheKeys.hasNext();) {
                CacheKey cacheKey = (CacheKey)cacheKeys.next();
                parameters[0] = cacheKey.getObject();
                writer.write(TraceLocalization.buildMessage("locked_object", parameters) + Helper.cr());
                parameters[0] = new Integer(cacheKey.getMutex().getDepth());
                writer.write(TraceLocalization.buildMessage("depth", parameters) + Helper.cr());
            }
            DeferredLockManager deferredLockManager = ConcurrencyManager.getDeferredLockManager(activeThread);
            if (deferredLockManager != null) {
                for (Iterator deferredLocks = deferredLockManager.getDeferredLocks().iterator();
                         deferredLocks.hasNext();) {
                    ConcurrencyManager lock = (ConcurrencyManager)deferredLocks.next();
                    parameters[0] = lock.getOwnerCacheKey().getObject();
                    writer.write(TraceLocalization.buildMessage("deferred_locks", parameters) + Helper.cr());
                }
            }
        }
        writer.write(Helper.cr() + TraceLocalization.buildMessage("lock_writer_footer", (Object[])null) + Helper.cr());
        getSession().log(SessionLog.FINEST, SessionLog.CACHE, writer.toString(), null, null, false);
    }

    /**
     * INTERNAL:
     * Used to print all the Locks in the specified identity map in this session.
     * The output of this method will be logged to this session's SessionLog at FINEST level.
     */
    public void printLocks(Class theClass) {
        ClassDescriptor descriptor = getSession().getDescriptor(theClass);
        StringWriter writer = new StringWriter();
        HashMap threadCollection = new HashMap();
        writer.write(TraceLocalization.buildMessage("lock_writer_header", (Object[])null) + Helper.cr());
        IdentityMap identityMap = getIdentityMap(descriptor);
        identityMap.collectLocks(threadCollection);

        Object[] parameters = new Object[1];
        for (Iterator threads = threadCollection.keySet().iterator(); threads.hasNext();) {
            Thread activeThread = (Thread)threads.next();
            parameters[0] = activeThread.getName();
            writer.write(TraceLocalization.buildMessage("active_thread", parameters) + Helper.cr());
            for (Iterator cacheKeys = ((HashSet)threadCollection.get(activeThread)).iterator();
                     cacheKeys.hasNext();) {
                CacheKey cacheKey = (CacheKey)cacheKeys.next();
                parameters[0] = cacheKey.getObject();
                writer.write(TraceLocalization.buildMessage("locked_object", parameters) + Helper.cr());
                parameters[0] = new Integer(cacheKey.getMutex().getDepth());
                writer.write(TraceLocalization.buildMessage("depth", parameters) + Helper.cr());
            }
            DeferredLockManager deferredLockManager = ConcurrencyManager.getDeferredLockManager(activeThread);
            if (deferredLockManager != null) {
                for (Iterator deferredLocks = deferredLockManager.getDeferredLocks().iterator();
                         deferredLocks.hasNext();) {
                    ConcurrencyManager lock = (ConcurrencyManager)deferredLocks.next();
                    parameters[0] = lock.getOwnerCacheKey().getObject();
                    writer.write(TraceLocalization.buildMessage("deferred_locks", parameters) + Helper.cr());
                }
            }
        }
        writer.write(Helper.cr() + TraceLocalization.buildMessage("lock_writer_footer", (Object[])null) + Helper.cr());
        getSession().log(SessionLog.FINEST, SessionLog.CACHE, writer.toString(), null, null, false);
    }

    /**
     * Register the object with the identity map.
     * The object must always be registered with its version number if optimistic locking is used.
     * The readTime may also be included in the cache key as it is constructed
     */
    public CacheKey putInIdentityMap(Object domainObject, Vector keys, Object writeLockValue, long readTime, ClassDescriptor descriptor) {
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(domainObject, getSession());

        IdentityMap map = getIdentityMap(descriptor);
        CacheKey cacheKey;

        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            // This is atomic so considered a read lock.
            acquireReadLock();
            try {
                cacheKey = map.put(keys, implementation, writeLockValue, readTime);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            cacheKey = map.put(keys, implementation, writeLockValue, readTime);
        }
        return cacheKey;
    }

    /**
     * Read-release the local-map and the entire cache.
     */
    protected void releaseReadLock() {
        if (getSession().getDatasourceLogin().shouldSynchronizedReadOnWrite()) {
            getCacheMutex().releaseReadLock();
        }
    }

    /**
     * Lock the entire cache if the cache isolation requires.
     * By default concurrent reads and writes are allowed.
     * By write, unit of work merge is meant.
     */
    public void releaseWriteLock() {
       if (getSession().getDatasourceLogin().shouldSynchronizedReadOnWrite() || getSession().getDatasourceLogin().shouldSynchronizeWrites()) {
             getCacheMutex().release();
        }
    }

    /**
     * Remove the object from the object cache.
     */
    public Object removeFromIdentityMap(Vector key, Class domainClass, ClassDescriptor descriptor) {
        IdentityMap map = getIdentityMap(descriptor);
        Object value;

        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            // This is atomic so considered a read lock.
            acquireReadLock();
            try {
                value = map.remove(key);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            value = map.remove(key);
        }
        return value;
    }

    /**
     * Set the cache mutex.
     * This allows for the entire cache to be locked.
     * This is done for transaction isolations on merges, although never locked by default.
     */
    protected void setCacheMutex(ConcurrencyManager cacheMutex) {
        this.cacheMutex = cacheMutex;
    }

    public void setIdentityMaps(Hashtable identityMaps) {
        clearLastAccessedIdentityMap();
        this.identityMaps = identityMaps;
    }

    protected void setSession(AbstractSession session) {
        this.session = session;
    }

    /**
     * Update the wrapper object the cache key associated with the given primary key,
     * this is used for EJB.
     */
    public void setWrapper(Vector primaryKey, Class theClass, Object wrapper) {
        ClassDescriptor descriptor = getSession().getDescriptor(theClass);
        IdentityMap map = getIdentityMap(descriptor);

        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            // This is atomic so considered a read lock.
            acquireReadLock();
            try {
                map.setWrapper(primaryKey, wrapper);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            map.setWrapper(primaryKey, wrapper);
        }
    }

    /**
     * Update the write lock value of the cache key associated with the given primary key,
     */
    public void setWriteLockValue(Vector primaryKey, Class theClass, Object writeLockValue) {
        ClassDescriptor descriptor = getSession().getDescriptor(theClass);
        IdentityMap map = getIdentityMap(descriptor);

        if (isCacheAccessPreCheckRequired()) {
            getSession().startOperationProfile(SessionProfiler.CACHE);
            // This is atomic so considered a read lock.
            acquireReadLock();
            try {
                map.setWriteLockValue(primaryKey, writeLockValue);
            } finally {
                releaseReadLock();
            }
            getSession().endOperationProfile(SessionProfiler.CACHE);
        } else {
            map.setWriteLockValue(primaryKey, writeLockValue);
        }
    }

    /**
     * This method is used to resolve the inheritance issues arisen when conforming from the identity map
     * 1. Avoid reading the unintended subclass during in-memory queyr(e.g. when querying on large project, do not want
     * to check small project, both are inheritanced from the project, and stored in the same identity map).
     * 2. EJB container-generated classes broke the inheritance hirearchy. Need to use associated descriptor to track
     * the relationship. CR4005-2612426, King-Sept-18-2002
     */
    private Object checkForInheritance(Object domainObject, Class superClass) {
        if ((domainObject != null) && ((domainObject.getClass() != superClass) && (!superClass.isInstance(domainObject)))) {
            //before returning null, check if we are using EJB inheritance.
            ClassDescriptor descriptor = getSession().getDescriptor(superClass);

            if (descriptor.hasInheritance() && descriptor.getInheritancePolicy().getUseDescriptorsToValidateInheritedObjects()) {
                //EJB inheritance on the descriptors, not the container-generated classes/objects. We need to check the
                //identity map for the bean instance through the descriptor.
                if (descriptor.getInheritancePolicy().getSubclassDescriptor(domainObject.getClass()) == null) {
                    return null;
                }

                //else
                return domainObject;
            }

            //else
            return null;
        }

        //else
        return domainObject;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.descriptors;

import java.io.*;
import java.lang.reflect.*;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.descriptors.OptimisticLockingPolicy;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.internal.security.PrivilegedAccessHelper;
import oracle.toplink.essentials.internal.security.PrivilegedClassForName;

/**
 * <p><b>Purpose</b>: Allows customization of an object's inheritance.
 * The primary supported inheritance model uses a class type indicator
 * column in the table that stores the object's class type.
 * The class-to-type mapping is specified on this policy.
 * The full class name can also be used for the indicator instead of the mapping.
 * <p>Each subclass can either share their parents table, or in addition add their
 * own table(s).
 * <p>For legacy models a customized inheritance class-extractor can be provided.
 * This allows Java code to be used to compute the class type to use for a row.
 * When this customized inheritance model is used an only-instances and with-all-subclasses
 * filter expression may be required for concrete and branch querying.
 */
public class InheritancePolicy implements Serializable, Cloneable {
    protected Class parentClass;
    protected String parentClassName;
    protected ClassDescriptor parentDescriptor;
    protected Vector childDescriptors;
    protected transient DatabaseField classIndicatorField;
     protected transient Map classIndicatorMapping;
     protected transient Map classNameIndicatorMapping;
    protected transient boolean shouldUseClassNameAsIndicator;
    protected transient Boolean shouldReadSubclasses;
    protected transient DatabaseTable readAllSubclassesView;
    protected transient Vector allChildClassIndicators;
    protected transient Expression onlyInstancesExpression;
    protected transient Expression withAllSubclassesExpression;
    // null if there are no childrenTables, otherwise all tables for reference class plus childrenTables
    protected transient Vector allTables;
    // all tables for all subclasses (subclasses of subclasses included), should be in sync with childrenTablesJoinExpressions.
    protected transient List childrenTables;
    // join expression for each child table, keyed by the table, should be in sync with childrenTables.
    protected transient Map childrenTablesJoinExpressions;
    // all expressions from childrenTablesJoinExpressions ANDed together
    protected transient Expression childrenJoinExpression;

    /** Allow for class extraction method to be specified. */
    protected transient ClassExtractor classExtractor;
    protected ClassDescriptor descriptor;
    protected boolean shouldAlwaysUseOuterJoin;

    //CR 4005
    protected boolean useDescriptorsToValidateInheritedObjects;

    // used by the entity-mappings XML writer to determine inheritance strategy
    protected boolean isJoinedStrategy;

    /**
     * INTERNAL:
     * Create a new policy.
     * Only descriptors involved in inheritence should have a policy.
     */
    public InheritancePolicy() {
         this.classIndicatorMapping = new HashMap(10);
         this.classNameIndicatorMapping = new HashMap(10);
        this.shouldUseClassNameAsIndicator = false;
         this.allChildClassIndicators = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
         this.childDescriptors = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(5);
        this.setJoinedStrategy();
    }

    /**
     * INTERNAL:
     * Create a new policy.
     * Only descriptors involved in inheritence should have a policy.
     */
    public InheritancePolicy(ClassDescriptor descriptor) {
        this();
        this.descriptor = descriptor;
    }

    /**
     * INTERNAL:
     * Add child descriptor to the parent descriptor.
     */
    public void addChildDescriptor(ClassDescriptor childDescriptor) {
        getChildDescriptors().addElement(childDescriptor);
    }

    /**
     * INTERNAL:
     * childrenTablesJoinExpressions, childrenTables, allTables and childrenJoinExpression
     * are created simultaneously and kept in sync.
     */
    protected void addChildTableJoinExpression(DatabaseTable table, Expression expression) {
        if(childrenTablesJoinExpressions == null) {
           childrenTablesJoinExpressions = new HashMap();
           // childrenTables should've been null, too
           childrenTables = new ArrayList();
           // allTables should've been null, too
           allTables = new Vector(getDescriptor().getTables());
        }
        childrenTables.add(table);
        allTables.add(table);
        childrenTablesJoinExpressions.put(table, expression);
        childrenJoinExpression = expression.and(childrenJoinExpression);
    }

    /**
     * INTERNAL:
     * call addChildTableJoinExpression on all parents
     */
    public void addChildTableJoinExpressionToAllParents(DatabaseTable table, Expression expression) {
        ClassDescriptor parentDescriptor = getParentDescriptor();
        while(parentDescriptor != null) {
            InheritancePolicy parentPolicy = parentDescriptor.getInheritancePolicy();
            parentPolicy.addChildTableJoinExpression(table, expression);
            parentDescriptor = parentPolicy.getParentDescriptor();
        }
    }

    /**
     * PUBLIC:
     * Add a class indicator for the root classes subclass.
     * The indicator is used to determine the class to use for a row read from the database,
     * and to query only instances of a class from the database.
     * Every concrete persistent subclass must have a single unique indicator defined for it.
     * If the root class is concrete then it must also define an indicator.
     * Only the root class's descriptor of the entire inheritance hierarchy can define the class indicator mapping.
     */
    public void addClassIndicator(Class childClass, Object typeValue) {
        // Note we should think about supporting null values.
        // Store as key and value for bi-diractional lookup.
        getClassIndicatorMapping().put(typeValue, childClass);
        getClassIndicatorMapping().put(childClass, typeValue);
    }

    /**
     * INTERNAL:
     * Add the class name reference by class name, used by the MW.
     */
    public void addClassNameIndicator(String childClassName, Object typeValue) {
        getClassNameIndicatorMapping().put(childClassName, typeValue);
    }

    /**
     * INTERNAL:
     * Add abstract class indicator information to the database row. This is
     * required when building a row for an insert or an update of a concrete child
     * descriptor.
     * This is only used to build a template row.
     */
    public void addClassIndicatorFieldToInsertRow(AbstractRecord databaseRow) {
        if (hasClassExtractor()) {
            return;
        }

        DatabaseField field = getClassIndicatorField();
        databaseRow.put(field, null);
    }

    /**
     * INTERNAL:
     * Add abstract class indicator information to the database row. This is
     * required when building a row for an insert or an update of a concrete child
     * descriptor.
     */
    public void addClassIndicatorFieldToRow(AbstractRecord databaseRow) {
        if (hasClassExtractor()) {
            return;
        }

        DatabaseField field = getClassIndicatorField();
        Object value = getClassIndicatorValue();

        databaseRow.put(field, value);
    }

    /**
     * INTERNAL:
     * Post initialize the child descriptors
     */
    protected void addClassIndicatorTypeToParent(Object indicator) {
        ClassDescriptor parentDescriptor = getDescriptor().getInheritancePolicy().getParentDescriptor();

        if (parentDescriptor.getInheritancePolicy().isChildDescriptor()) {
            if (parentDescriptor.getInheritancePolicy().shouldReadSubclasses()) {
                parentDescriptor.getInheritancePolicy().getAllChildClassIndicators().addElement(indicator);
            }
            parentDescriptor.getInheritancePolicy().addClassIndicatorTypeToParent(indicator);
        }
    }

    /**
     * INTERNAL:
     * Recursively adds fields to all the parents
     */
    protected void addFieldsToParent(Vector fields) {
        if (isChildDescriptor()) {
            if (getParentDescriptor().isInvalid()) {
                return;
            }
            ClassDescriptor parentDescriptor = getParentDescriptor();
            if (parentDescriptor.getInheritancePolicy().shouldReadSubclasses()) {
                Helper.addAllUniqueToVector(parentDescriptor.getAllFields(), fields);
            }
            parentDescriptor.getInheritancePolicy().addFieldsToParent(fields);
        }
    }

    /**
     * INTERNAL:
     * Return a select statement that will be used to query the class indicators required to query.
     * This is used in the abstract-multiple read.
     */
    public SQLSelectStatement buildClassIndicatorSelectStatement(ObjectLevelReadQuery query) {
        SQLSelectStatement selectStatement;
        selectStatement = new SQLSelectStatement();
        selectStatement.useDistinct();
        selectStatement.addTable(classIndicatorField.getTable());
        selectStatement.addField(getClassIndicatorField());
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        HardIdentityHashtable clonedExpressions = new HardIdentityHashtable();
        selectStatement.setWhereClause(((ExpressionQueryMechanism)query.getQueryMechanism()).buildBaseSelectionCriteria(false, clonedExpressions));
        appendWithAllSubclassesExpression(selectStatement);
        selectStatement.setTranslationRow(query.getTranslationRow());
        selectStatement.normalize(query.getSession(), getDescriptor(), clonedExpressions);
        ExpressionQueryMechanism m = (ExpressionQueryMechanism)query.getQueryMechanism();

        return selectStatement;
    }

    /**
     * INTERNAL:
     * Append the branch with all subclasses expression to the statement.
     */
    public void appendWithAllSubclassesExpression(SQLSelectStatement selectStatement) {
        if (getWithAllSubclassesExpression() != null) {
            // For Flashback: Must always rebuild with simple expression on right.
            if (selectStatement.getWhereClause() == null) {
                selectStatement.setWhereClause((Expression)getWithAllSubclassesExpression().clone());
            } else {
                selectStatement.setWhereClause(selectStatement.getWhereClause().and(getWithAllSubclassesExpression()));
            }
        }
    }

    /**
     * INTERNAL:
     * Build a select statement for all subclasses on the view using the same
     * selection criteria as the query.
     */
    public SQLSelectStatement buildViewSelectStatement(ObjectLevelReadQuery query) {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        HardIdentityHashtable clonedExpressions = new HardIdentityHashtable();
        ExpressionQueryMechanism mechanism = (ExpressionQueryMechanism)query.getQueryMechanism();

        // CR#3166555 - Have the mechanism build the statement to avoid duplicating code and ensure that lock-mode, hints, hierarchical, etc. are set.
        SQLSelectStatement selectStatement = mechanism.buildBaseSelectStatement(false, clonedExpressions);
         selectStatement.setTables(oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1));
        selectStatement.addTable(getReadAllSubclassesView());

        // Case, normal read for branch inheritence class that reads subclasses all in its own table(s).
        if (getWithAllSubclassesExpression() != null) {
            Expression branchIndicator = (Expression)getWithAllSubclassesExpression().clone();
            if (branchIndicator != null) {
                selectStatement.setWhereClause(branchIndicator.and(selectStatement.getWhereClause()));
            }
        }

        selectStatement.setFields(mechanism.getSelectionFields(selectStatement, true));
        selectStatement.normalizeForView(query.getSession(), getDescriptor(), clonedExpressions);
        // Allow for joining indexes to be computed to ensure distinct rows
        ((ObjectLevelReadQuery)query).getJoinedAttributeManager().computeJoiningMappingIndexes(false, query.getSession(), 0);

        return selectStatement;
    }

    /**
     * INTERNAL:
     * This method is invoked only for the abstract descriptors.
     */
    public Class classFromRow(AbstractRecord rowFromDatabase, AbstractSession session) throws DescriptorException {
        if (hasClassExtractor()) {
            return getClassExtractor().extractClassFromRow(rowFromDatabase, session);
        }

        Object classFieldValue = session.getDatasourcePlatform().getConversionManager().convertObject(rowFromDatabase.get(getClassIndicatorField()), getClassIndicatorField().getType());

        if (classFieldValue == null) {
            throw DescriptorException.missingClassIndicatorField(rowFromDatabase, getDescriptor());
        }

        Class concreteClass;
        if (!shouldUseClassNameAsIndicator()) {
            concreteClass = (Class)getClassIndicatorMapping().get(classFieldValue);
            if (concreteClass == null) {
                throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor());
            }
        } else {
            try {
                String className = (String)classFieldValue;
                //PWK 2.5.1.7 can not use class for name, must go through conversion manager.
                //Should use the root Descriptor's classloader to avoid loading from a loader other
                //than the one that loaded the project
                concreteClass = getDescriptor().getJavaClass().getClassLoader().loadClass(className);
                if (concreteClass == null) {
                    throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor());
                }
            } catch (ClassNotFoundException e) {
                throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor());
            } catch (ClassCastException e) {
                throw DescriptorException.missingClassForIndicatorFieldValue(classFieldValue, getDescriptor());
            }
        }

        return concreteClass;
    }

    /**
     * INTERNAL:
     * Clone the policy
     */
    public Object clone() {
        InheritancePolicy clone = null;

        try {
            clone = (InheritancePolicy)super.clone();
            if (hasClassIndicator()) {
                clone.setClassIndicatorField((DatabaseField)clone.getClassIndicatorField().clone());
            }
        } catch (Exception exception) {
            throw new InternalError("clone failed");
        }

        return clone;
    }

    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this InheritancePolicy to actual class-based
     * settings. This method is used when converting a project that has been built
     * with class names to a project with classes.
     * @param classLoader
     */
    public void convertClassNamesToClasses(ClassLoader classLoader){
        if (parentClassName == null){
            return;
        }
        Class parentClass = null;
        try{
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    parentClass = (Class)AccessController.doPrivileged(new PrivilegedClassForName(parentClassName, true, classLoader));
                } catch (PrivilegedActionException exception) {
                    throw ValidationException.classNotFoundWhileConvertingClassNames(parentClassName, exception.getException());
                }
            } else {
                parentClass = oracle.toplink.essentials.internal.security.PrivilegedAccessHelper.getClassForName(parentClassName, true, classLoader);
            }
        } catch (ClassNotFoundException exc){
            throw ValidationException.classNotFoundWhileConvertingClassNames(parentClassName, exc);
        }
        setParentClass(parentClass);
    }

    /**
     * PUBLIC:
     * Set the descriptor to only read instance of itself when queried.
     * This is used with inheritance to configure the result of queries.
     * By default this is true for root inheritance descriptors, and false for all others.
     */
    public void dontReadSubclassesOnQueries() {
        setShouldReadSubclasses(false);
    }

    /**
     * PUBLIC:
     * Set the descriptor not to use the class' full name as the indicator.
     * The class indicator is used with inheritance to determine the class from a row.
     * By default a class indicator mapping is required, this can be set to true if usage of the class name is desired.
     * The field must be of a large enough size to store the fully qualified class name.
     */
    public void dontUseClassNameAsIndicator() {
        setShouldUseClassNameAsIndicator(false);
    }

    /**
     * INTERNAL:
     * Stores class indicators for all child and children's children.
     * Used for queries on branch classes only.
     */
    protected Vector getAllChildClassIndicators() {
        return allChildClassIndicators;
    }

    /**
     * INTERNAL:
     * Returns all the child descriptors, even descriptors for subclasses of
     * subclasses.
     * Required for bug 3019934.
     */
    public Vector getAllChildDescriptors() {
        // Guess the number of child descriptors...
        Vector allChildDescriptors = new Vector(this.getAllChildClassIndicators().size());
        return getAllChildDescriptors(allChildDescriptors);
    }

    /**
     * INTERNAL:
     * Recursive subroutine of getAllChildDescriptors.
     */
    protected Vector getAllChildDescriptors(Vector allChildDescriptors) {
        for (Enumeration enumtr = getChildDescriptors().elements(); enumtr.hasMoreElements();) {
            ClassDescriptor childDescriptor = (ClassDescriptor)enumtr.nextElement();
            allChildDescriptors.addElement(childDescriptor);
            childDescriptor.getInheritancePolicyOrNull().getAllChildDescriptors(allChildDescriptors);
        }
        return allChildDescriptors;
    }

    /**
     * INTERNAL:
     * if reads subclasses, all tables for all read subclasses (indirect included).
     */
    public List getChildrenTables() {
        return childrenTables;
    }
    
    /**
     * INTERNAL:
     * join expression for each child table, keyed by the table
     */
    public Map getChildrenTablesJoinExpressions() {
        return childrenTablesJoinExpressions;
    }
    
    /**
     * INTERNAL:
     * all expressions from childrenTablesJoinExpressions ANDed together
     */
    public Expression getChildrenJoinExpression() {
        return childrenJoinExpression;
    }
    
    /**
     * INTERNAL:
     * all tables for reference class plus childrenTables
     */
    public Vector getAllTables() {
        if(allTables == null) {
            return this.getDescriptor().getTables();
        } else {
            return allTables;
        }
    }
    
    /**
     * INTERNAL:
     * Return all the immediate child descriptors. Only descriptors from
     * direct subclasses are returned.
     */
    public Vector getChildDescriptors() {
        return childDescriptors;
    }

    /**
     * INTERNAL:
     * Return all the classExtractionMethod
     */
    protected Method getClassExtractionMethod() {
        if (classExtractor instanceof MethodClassExtractor) {
            return ((MethodClassExtractor)classExtractor).getClassExtractionMethod();
        } else {
            return null;
        }
    }

    /**
     * ADVANCED:
     * A class extraction method can be registered with the descriptor to override the default inheritance mechanism.
     * This allows for the class indicator field to not be used, and a user defined one instead.
     * The method registered must be a static method on the class that the descriptor is for,
     * the method must take DatabaseRow as argument, and must return the class to use for that row.
     * This method will be used to decide which class to instantiate when reading from the database.
     * It is the application's responsiblity to populate any typing information in the database required
     * to determine the class from the row.
     * If this method is used then the class indicator field and mapping cannot be used,
     * also the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly.
     *
     * @see #setWithAllSubclassesExpression(Expression)
     * @see #setOnlyInstancesExpression(Expression)
     */
    public String getClassExtractionMethodName() {
        if (classExtractor instanceof MethodClassExtractor) {
            return ((MethodClassExtractor)classExtractor).getClassExtractionMethodName();
        } else {
            return null;
        }
    }

    /**
     * ADVANCED:
     * A class extractor can be registered with the descriptor to override the default inheritance mechanism.
     * This allows for the class indicator field to not be used, and a user defined one instead.
     * The instance registered must extend the ClassExtractor class and implement the extractClass(Map) method,
     * the method must take database row (Map) as argument, and must return the class to use for that row.
     * This method will be used to decide which class to instantiate when reading from the database.
     * It is the application's responsiblity to populate any typing information in the database required
     * to determine the class from the row, such as usage of a direct or transformation mapping for the type fields.
     * If this method is used then the class indicator field and mapping cannot be used,
     * also the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly.
     *
     * @see #setWithAllSubclassesExpression(Expression)
     * @see #setOnlyInstancesExpression(Expression)
     */
    public ClassExtractor getClassExtractor() {
        return classExtractor;
    }

    /**
     * ADVANCED:
     * A class extractor can be registered with the descriptor to override the default inheritance mechanism.
     * This allows for the class indicator field to not be used, and a user defined one instead.
     * The instance registered must extend the ClassExtractor class and implement the extractClass(Map) method,
     * the method must take database row (Map) as argument, and must return the class to use for that row.
     * This method will be used to decide which class to instantiate when reading from the database.
     * It is the application's responsiblity to populate any typing information in the database required
     * to determine the class from the row, such as usage of a direct or transformation mapping for the type fields.
     * If this method is used then the class indicator field and mapping cannot be used,
     * also the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly.
     *
     * @see #setWithAllSubclassesExpression(Expression)
     * @see #setOnlyInstancesExpression(Expression)
     */
    public void setClassExtractor(ClassExtractor classExtractor) {
        this.classExtractor = classExtractor;
    }

    /**
     * INTERNAL:
     * Return the class indicator associations for XML.
     * List of class-name/value associations.
     */
    public Vector getClassIndicatorAssociations() {
        Vector associations = new Vector(getClassNameIndicatorMapping().size() / 2);
         Iterator classesEnum = getClassNameIndicatorMapping().keySet().iterator();
         Iterator valuesEnum = getClassNameIndicatorMapping().values().iterator();
         while (classesEnum.hasNext()) {
             Object className = classesEnum.next();

            // If the project was built in runtime is a class, MW is a string.
            if (className instanceof Class) {
                className = ((Class)className).getName();
            }
             Object value = valuesEnum.next();
            associations.addElement(new TypedAssociation(className, value));
        }

        return associations;
    }

    /**
     * INTERNAL:
     * Returns field that the class type indicator is store when using inheritence.
     */
    public DatabaseField getClassIndicatorField() {
        return classIndicatorField;
    }

    /**
     * PUBLIC:
     * Return the class indicator field name.
     * This is the name of the field in the table that stores what type of object this is.
     */
    public String getClassIndicatorFieldName() {
        if (getClassIndicatorField() == null) {
            return null;
        } else {
            return getClassIndicatorField().getQualifiedName();
        }
    }

    /**
     * INTERNAL:
     * Return the association of indicators and classes
     */
     public Map getClassIndicatorMapping() {
        return getClassIndicatorMapping(ConversionManager.getDefaultManager());
    }
    
    /**
     * INTERNAL:
     * Return the association of indicators and classes using specified ConversionManager
     */
    public Map getClassIndicatorMapping(ConversionManager conversionManager) {
        if (classIndicatorMapping.isEmpty() && !classNameIndicatorMapping.isEmpty()) {
             Iterator keysEnum = classNameIndicatorMapping.keySet().iterator();
             Iterator valuesEnum = classNameIndicatorMapping.values().iterator();
             while (keysEnum.hasNext()) {
                 Object key = keysEnum.next();
                 Object value = valuesEnum.next();
                Class theClass = (Class)conversionManager.convertObject((String)key, ClassConstants.CLASS);
                classIndicatorMapping.put(theClass, value);
                classIndicatorMapping.put(value, theClass);
            }
        }
        return classIndicatorMapping;
    }

    /**
     * INTERNAL:
     * Return the mapping from class name to indicator, used by MW.
     */
     public Map getClassNameIndicatorMapping() {
        if (classNameIndicatorMapping.isEmpty() && !classIndicatorMapping.isEmpty()) {
             Iterator keysEnum = classIndicatorMapping.keySet().iterator();
             Iterator valuesEnum = classIndicatorMapping.values().iterator();
             while (keysEnum.hasNext()) {
                 Object key = keysEnum.next();
                 Object value = valuesEnum.next();
                if (key instanceof Class) {
                    String className = ((Class)key).getName();
                    classNameIndicatorMapping.put(className, value);
                }
            }
        }

        return classNameIndicatorMapping;
    }

    /**
     * INTERNAL:
     * Returns value of the abstract class indicator for the Java class.
     */
    protected Object getClassIndicatorValue() {
        return getClassIndicatorValue(getDescriptor().getJavaClass());
    }

    /**
     * INTERNAL:
     * Returns the indicator field value for the given class
     * If no abstract indicator mapping is specified, use the class name.
     */
    protected Object getClassIndicatorValue(Class javaClass) {
        if (shouldUseClassNameAsIndicator()) {
            return javaClass.getName();
        } else {
            return getClassIndicatorMapping().get(javaClass);
        }
    }

    /**
     * INTERNAL:
     * Returns the descriptor which the policy belongs to.
     */
    public ClassDescriptor getDescriptor() {
        return descriptor;
    }

    /**
     * ADVANCED:
     * Return the 'only instances expression'.
     */
    public Expression getOnlyInstancesExpression() {
        return onlyInstancesExpression;
    }

    /**
     * PUBLIC:
     * Return the parent class.
     */
    public Class getParentClass() {
        return parentClass;
    }

    /**
     * INTERNAL:
     * Return the parent class name.
     */
    public String getParentClassName() {
        if ((parentClassName == null) && (parentClass != null)) {
            parentClassName = parentClass.getName();
        }
        return parentClassName;
    }

    /**
     * INTERNAL:
     * Return the parent descirptor
     */
    public ClassDescriptor getParentDescriptor() {
        return parentDescriptor;
    }

    /**
     * INTERNAL:
     * The view can be used to optimize/customize the query for all subclasses where they have multiple tables.
     * This view can do the outer join, we require the view because we cannot generate dynmic platform independent SQL
     * for outer joins (i.e. not possible to do so either).
     */
    public DatabaseTable getReadAllSubclassesView() {
        return readAllSubclassesView;
    }

    /**
     * ADVANCED:
     * The view can be used to optimize/customize the query for all subclasses where they have multiple tables.
     * This view can use outer joins or unions to combine the results of selecting from all of the subclass tables.
     * If a view is not given then TopLink must make an individual call for each subclass.
     */
    public String getReadAllSubclassesViewName() {
        if (getReadAllSubclassesView() == null) {
            return null;
        }
        return getReadAllSubclassesView().getName();
    }

    /**
     * INTERNAL:
     * Return the root parent descriptor
     */
    public ClassDescriptor getRootParentDescriptor() {
        if (isRootParentDescriptor()) {
            return getDescriptor();
        } else {
            return getParentDescriptor().getInheritancePolicy().getRootParentDescriptor();
        }
    }

    /**
     * INTERNAL:
     * use aggregate in inheritance
     */
    public ClassDescriptor getSubclassDescriptor(Class theClass) {
        if (hasChildren()) {
            for (Iterator enumtr = getChildDescriptors().iterator(); enumtr.hasNext();) {
                ClassDescriptor childDescriptor = (ClassDescriptor)enumtr.next();
                if (childDescriptor.getJavaClass().equals(theClass)) {
                    return childDescriptor;
                } else {
                    ClassDescriptor descriptor = childDescriptor.getInheritancePolicy().getSubclassDescriptor(theClass);
                    if (descriptor != null) {
                        return descriptor;
                    }
                }
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * return if we should use the descriptor inheritance to determine
     * if an object can be returned from the identity map or not.
     */
    public boolean getUseDescriptorsToValidateInheritedObjects() {
        return useDescriptorsToValidateInheritedObjects;
    }

    /**
     * ADVANCED:
     * Return the Expression which gets all subclasses.
     */
    public Expression getWithAllSubclassesExpression() {
        return withAllSubclassesExpression;
    }

    /**
     * INTERNAL:
     * Check if descriptor has children
     */
    public boolean hasChildren() {
        return !getChildDescriptors().isEmpty();
    }

    /**
     * INTERNAL:
     */
    public boolean hasClassExtractor() {
        return getClassExtractor() != null;
    }

    /**
     * INTERNAL:
     * Checks if the class is invloved in inheritence
     */
    public boolean hasClassIndicator() {
        return getClassIndicatorField() != null;
    }

    /**
     * INTERNAL:
     * Return if any children of this descriptor require information from another table
     * not specified at the parent level.
     */
    public boolean hasMultipleTableChild() {
        return childrenTables != null;
    }

    /**
     * INTERNAL:
     * Return if a view is used for inheritance reads.
     */
    public boolean hasView() {
        return getReadAllSubclassesView() != null;
    }

    /**
     * INTERNAL:
     * Initialized the inheritence properties of the descriptor once the mappings are initialized.
     * This is done before formal postInitialize during the end of mapping initialize.
     */
    public void initialize(AbstractSession session) {
        // Must reset this in the case that a child thinks it wants to read its subclasses.
        if ((shouldReadSubclasses == null) || shouldReadSubclasses()) {
            setShouldReadSubclasses(!getChildDescriptors().isEmpty());
        }

        if (isChildDescriptor()) {
            getDescriptor().setMappings(Helper.concatenateVectors(getParentDescriptor().getMappings(), getDescriptor().getMappings()));
             getDescriptor().setQueryKeys(Helper.concatenateMaps(getParentDescriptor().getQueryKeys(), getDescriptor().getQueryKeys()));
            addFieldsToParent(getDescriptor().getFields());
            // Parents fields must be first for indexing to work.
            Vector parentsFields = (Vector)getParentDescriptor().getFields().clone();

            //bug fix on Oracle duplicate field SQL using "order by"
            Helper.addAllUniqueToVector(parentsFields, getDescriptor().getFields());
            getDescriptor().setFields(parentsFields);

            if (getClassIndicatorValue() != null) {
                if (shouldReadSubclasses()) {
                    getAllChildClassIndicators().addElement(getClassIndicatorValue());
                }
                addClassIndicatorTypeToParent(getClassIndicatorValue());
            }

            // CR#3214106, do not override if specified in subclass.
            if (!getDescriptor().usesOptimisticLocking() && getParentDescriptor().usesOptimisticLocking()) {
                getDescriptor().setOptimisticLockingPolicy((OptimisticLockingPolicy)getParentDescriptor().getOptimisticLockingPolicy().clone());
                getDescriptor().getOptimisticLockingPolicy().setDescriptor(getDescriptor());
            }

            // create CMPPolicy on child if parent has one and it does not. Then copy individual fields
            CMPPolicy parentCMPPolicy = getDescriptor().getInheritancePolicy().getParentDescriptor().getCMPPolicy();
            if (parentCMPPolicy != null) {
                CMPPolicy cmpPolicy = getDescriptor().getCMPPolicy();
                if (cmpPolicy == null) {
                    cmpPolicy = new CMPPolicy();
                    getDescriptor().setCMPPolicy(cmpPolicy);
                }
            }
        }

        initializeOnlyInstancesExpression();
        initializeWithAllSubclassesExpression();
    }

    /**
     * INTERNAL:
     * Setup the default classExtractionMethod, or if one was specified by the user make sure it is valid.
     */
    protected void initializeClassExtractor(AbstractSession session) throws DescriptorException {
        if (getClassExtractor() == null) {
            if (isChildDescriptor()) {
                setClassExtractor(getParentDescriptor().getInheritancePolicy().getClassExtractor());
            }
        } else {
            getClassExtractor().initialize(getDescriptor(), session);
        }
    }

    /**
     * INTERNAL:
     * Initialize the expression to use to check the specific type field.
     */
    protected void initializeOnlyInstancesExpression() throws DescriptorException {
        if (getOnlyInstancesExpression() == null) {
            if (hasClassExtractor()) {
                return;
            }
            Object typeValue = getClassIndicatorValue();
            if (typeValue == null) {
                if (shouldReadSubclasses()) {
                    return;// No indicator is allowed in this case.
                }

                throw DescriptorException.valueNotFoundInClassIndicatorMapping(getParentDescriptor(), getDescriptor());
            }

            DatabaseField typeField = getClassIndicatorField();
            if (typeField == null) {
                throw DescriptorException.classIndicatorFieldNotFound(getParentDescriptor(), getDescriptor());
            }

            // cr3546
            if (shouldAlwaysUseOuterJoin()) {
                setOnlyInstancesExpression(new ExpressionBuilder().getField(typeField).equalOuterJoin(typeValue));
            } else {
                setOnlyInstancesExpression(new ExpressionBuilder().getField(typeField).equal(typeValue));
            }
        }

        // If subclasses are read, this is anded dynamically.
        if (!shouldReadSubclasses()) {
            getDescriptor().getQueryManager().setAdditionalJoinExpression(getOnlyInstancesExpression().and(getDescriptor().getQueryManager().getAdditionalJoinExpression()));
        }
    }

    /**
     * INTERNAL:
     * Initialize the expression to use for queries to the class and its subclasses.
     */
    protected void initializeWithAllSubclassesExpression() throws DescriptorException {
        if (getWithAllSubclassesExpression() == null) {
            if (hasClassExtractor()) {
                return;
            }
            if (isChildDescriptor() && shouldReadSubclasses()) {
                setWithAllSubclassesExpression(new ExpressionBuilder().getField(getClassIndicatorField()).in(getAllChildClassIndicators()));
            }
        }
    }

    /**
     * INTERNAL:
     * Check if it is a child descriptor.
     */
    public boolean isChildDescriptor() {
        return getParentClassName() != null;
    }
    
    /**
     * INTERNAL:
     * Indicate whether a single table or joined inheritance strategy is being used. Since we currently do
     * not support TABLE_PER_CLASS, indicating either joined/not joined is sufficient.
     *
     * @return isJoinedStrategy value
     */
    public boolean isJoinedStrategy() {
        return isJoinedStrategy;
    }
    
    /**
     * INTERNAL:
     * Return whether or not is root parent descriptor
     */
    public boolean isRootParentDescriptor() {
        return getParentDescriptor() == null;
    }

    /**
     * INTERNAL:
     * Initialized the inheritence properties that cannot be initialized
     * unitl after the mappings have been.
     */
    public void postInitialize(AbstractSession session) {
    }

    /**
     * INTERNAL:
     * Allow the inheritence properties of the descriptor to be initialized.
     * The descriptor's parent must first be initialized.
     */
    public void preInitialize(AbstractSession session) throws DescriptorException {
        // Make sure that parent is already preinitialized.
        if (isChildDescriptor()) {
            // Unique is required because the builder can add the same table many times.
            Vector<DatabaseTable> childTables = getDescriptor().getTables();
            Vector<DatabaseTable> parentTables = getParentDescriptor().getTables();
            Vector<DatabaseTable> uniqueTables = Helper.concatenateUniqueVectors(parentTables, childTables);
            getDescriptor().setTables(uniqueTables);
            
            // After filtering out any duplicate tables, set the default table
            // if one is not already set. This must be done now before any other
            // initialization occurs. In a joined strategy case, the default
            // table will be at an index greater than 0. Which is where
            // setDefaultTable() assumes it is. Therefore, we need to send the
            // actual default table instead.
            if (childTables.isEmpty()) {
                getDescriptor().setInternalDefaultTable();
            } else {
                getDescriptor().setInternalDefaultTable(uniqueTables.get(uniqueTables.indexOf(childTables.get(0))));
            }

            setClassIndicatorMapping(getParentDescriptor().getInheritancePolicy().getClassIndicatorMapping(session.getDatasourcePlatform().getConversionManager()));
            setShouldUseClassNameAsIndicator(getParentDescriptor().getInheritancePolicy().shouldUseClassNameAsIndicator());

            // Initialize properties.
            getDescriptor().setPrimaryKeyFields(getParentDescriptor().getPrimaryKeyFields());
            getDescriptor().setAdditionalTablePrimaryKeyFields(Helper.concatenateMaps(getParentDescriptor().getAdditionalTablePrimaryKeyFields(), getDescriptor().getAdditionalTablePrimaryKeyFields()));

            Expression localExpression = getDescriptor().getQueryManager().getMultipleTableJoinExpression();
            Expression parentExpression = getParentDescriptor().getQueryManager().getMultipleTableJoinExpression();

            if (localExpression != null) {
                getDescriptor().getQueryManager().setInternalMultipleTableJoinExpression(localExpression.and(parentExpression));
            } else if (parentExpression != null) {
                getDescriptor().getQueryManager().setInternalMultipleTableJoinExpression(parentExpression);
            }

            Expression localAdditionalExpression = getDescriptor().getQueryManager().getAdditionalJoinExpression();
            Expression parentAdditionalExpression = getParentDescriptor().getQueryManager().getAdditionalJoinExpression();

            if (localAdditionalExpression != null) {
                getDescriptor().getQueryManager().setAdditionalJoinExpression(localAdditionalExpression.and(parentAdditionalExpression));
            } else if (parentAdditionalExpression != null) {
                getDescriptor().getQueryManager().setAdditionalJoinExpression(parentAdditionalExpression);
            }

            setClassIndicatorField(getParentDescriptor().getInheritancePolicy().getClassIndicatorField());

            //if child has sequencing setting, do not bother to call the parent
            if (!getDescriptor().usesSequenceNumbers()) {
                getDescriptor().setSequenceNumberField(getParentDescriptor().getSequenceNumberField());
                getDescriptor().setSequenceNumberName(getParentDescriptor().getSequenceNumberName());
            }
        } else {
            // This must be done now before any other initialization occurs.
            getDescriptor().setInternalDefaultTable();
        }

        initializeClassExtractor(session);

        if (!isChildDescriptor()) {
            // build abstract class indicator field.
            if ((getClassIndicatorField() == null) && (!hasClassExtractor())) {
                session.getIntegrityChecker().handleError(DescriptorException.classIndicatorFieldNotFound(getDescriptor(), getDescriptor()));
            }
            if (getClassIndicatorField() != null) {
                getDescriptor().buildField(getClassIndicatorField());
                // Determine and set the class indicator classification.
                if (shouldUseClassNameAsIndicator()) {
                    getClassIndicatorField().setType(ClassConstants.STRING);
                } else if (!getClassIndicatorMapping(session.getDatasourcePlatform().getConversionManager()).isEmpty()) {
                    Class type = null;
                    Iterator fieldValuesEnum = getClassIndicatorMapping(session.getDatasourcePlatform().getConversionManager()).values().iterator();
                     while (fieldValuesEnum.hasNext() && (type == null)) {
                         Object value = fieldValuesEnum.next();
                        if (value.getClass() != getClass().getClass()) {
                            type = value.getClass();
                        }
                    }
                    getClassIndicatorField().setType(type);
                }
                getDescriptor().getFields().addElement(getClassIndicatorField());
            }
        }
    }

    /**
     * PUBLIC:
     * Set the descriptor to read instance of itself and its subclasses when queried.
     * This is used with inheritance to configure the result of queries.
     * By default this is true for root inheritance descriptors, and false for all others.
     */
    public void readSubclassesOnQueries() {
        setShouldReadSubclasses(true);
    }

    /**
     * INTERNAL:
     * Return if this descriptor has children that define additional tables and needs to read them.
     * This case requires a special read, because the query cannot be done through a single SQL call with normal joins.
     */
    public boolean requiresMultipleTableSubclassRead() {
        return hasMultipleTableChild() && shouldReadSubclasses();
    }

    /**
     * INTERNAL:
     * Select all rows from a abstract table descriptor.
     * This is accomplished by selecting for all of the concrete classes and then merging the rows.
     * This does not optimize using type select, as the type infomation is not known.
     * @return vector containing database rows.
     * @exception DatabaseException - an error has occurred on the database.
     */
    protected Vector selectAllRowUsingCustomMultipleTableSubclassRead(ReadAllQuery query) throws DatabaseException {
        Vector rows = new Vector();
        // CR#3701077, it must either have a filter only instances expression, or not have subclasses.
        // This method recurses, so even though this is only called when shouldReadSubclasses is true, it may be false for subclasses.
        if ((getOnlyInstancesExpression() != null) || (! shouldReadSubclasses())) {
            ReadAllQuery concreteQuery = (ReadAllQuery)query.clone();
            concreteQuery.setReferenceClass(getDescriptor().getJavaClass());
            concreteQuery.setDescriptor(getDescriptor());

            Vector concreteRows = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectAllRowsFromConcreteTable();
            rows = Helper.concatenateVectors(rows, concreteRows);
        }

        // Recursively collect all rows from all concrete children and their children.
        for (Enumeration childrenEnum = getChildDescriptors().elements();
                 childrenEnum.hasMoreElements();) {
            ClassDescriptor concreteDescriptor = (ClassDescriptor)childrenEnum.nextElement();
            Vector concreteRows = concreteDescriptor.getInheritancePolicy().selectAllRowUsingCustomMultipleTableSubclassRead(query);
            rows = Helper.concatenateVectors(rows, concreteRows);
        }

        return rows;
    }

    /**
     * INTERNAL:
     * Select all rows from a abstract table descriptor.
     * This is accomplished by selecting for all of the concrete classes and then merging the rows.
     * @return vector containing database rows.
     * @exception DatabaseException - an error has occurred on the database.
     */
    protected Vector selectAllRowUsingDefaultMultipleTableSubclassRead(ReadAllQuery query) throws DatabaseException, QueryException {
        // Get all rows for the given class indicator field
        // The indicator select is prepared in the original query, so can just be executed.
        Vector classIndicators = ((ExpressionQueryMechanism)query.getQueryMechanism()).selectAllRowsFromTable();

        Vector classes = new Vector();
        for (Enumeration rowsEnum = classIndicators.elements(); rowsEnum.hasMoreElements();) {
            AbstractRecord row = (AbstractRecord)rowsEnum.nextElement();
            Class concreteClass = classFromRow(row, query.getSession());
            if (!classes.contains(concreteClass)) {//Ensure unique ** we should do a distinct.. we do
                classes.addElement(concreteClass);
            }
        }

        Vector rows = new Vector();
        // joinedMappingIndexes contains Integer indexes corrsponding to the number of fields
        // to which the query rference class is mapped, for instance:
        // referenceClass = SmallProject => joinedMappingIndexes(0) = 6;
        // referenceClass = LargeProject => joinedMappingIndexes(0) = 8;
        // This information should be preserved in the main query against the parent class,
        // therefore in this case joinedMappedIndexes contains a Map of classes to Integers:
        // referenceClass = Project => joinedMappingIndexes(0) = Map {SmallProject -> 6; LargeProject -> 8}.
        // These maps are populated in the loop below, and set into the main query joinedMappingIndexes.
        HashMap joinedMappingIndexes = null;
        if(query.getJoinedAttributeManager().hasJoinedAttributes()) {
            joinedMappingIndexes = new HashMap();
        }
        for (Enumeration classesEnum = classes.elements(); classesEnum.hasMoreElements();) {
            Class concreteClass = (Class)classesEnum.nextElement();
            ClassDescriptor concreteDescriptor = query.getSession().getDescriptor(concreteClass);
            if (concreteDescriptor == null) {
                throw QueryException.noDescriptorForClassFromInheritancePolicy(query, concreteClass);
            }
            ReadAllQuery concreteQuery = (ReadAllQuery)query.clone();
            concreteQuery.setReferenceClass(concreteClass);
            concreteQuery.setDescriptor(concreteDescriptor);

            Vector concreteRows = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectAllRowsFromConcreteTable();
            rows = Helper.concatenateVectors(rows, concreteRows);
            
            if(joinedMappingIndexes != null) {
                Iterator it = concreteQuery.getJoinedAttributeManager().getJoinedMappingIndexes_().entrySet().iterator();
                while(it.hasNext()) {
                    Map.Entry entry = (Map.Entry)it.next();
                    HashMap map = (HashMap)joinedMappingIndexes.get(entry.getKey());
                    if(map == null) {
                        map = new HashMap(classes.size());
                        joinedMappingIndexes.put(entry.getKey(), map);
                    }
                    map.put(concreteClass, entry.getValue());
                }
            }
        }
        if(joinedMappingIndexes != null) {
            query.getJoinedAttributeManager().setJoinedMappingIndexes_(joinedMappingIndexes);
        }

        return rows;
    }

    /**
     * INTERNAL:
     * Select all rows from a abstract table descriptor.
     * This is accomplished by selecting for all of the concrete classes and then merging the rows.
     * @return vector containing database rows.
     * @exception DatabaseException - an error has occurred on the database.
     */
    public Vector selectAllRowUsingMultipleTableSubclassRead(ReadAllQuery query) throws DatabaseException {
        if (hasClassExtractor()) {
            return selectAllRowUsingCustomMultipleTableSubclassRead(query);
        } else {
            return selectAllRowUsingDefaultMultipleTableSubclassRead(query);
        }
    }

    /**
     * INTERNAL:
     * Select one rows from a abstract table descriptor.
     * This is accomplished by selecting for all of the concrete classes until a row is found.
     * This does not optimize using type select, as the type infomation is not known.
     * @exception DatabaseException - an error has occurred on the database.
     */
    protected AbstractRecord selectOneRowUsingCustomMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException {
        // CR#3701077, it must either have a filter only instances expression, or not have subclasses.
        // This method recurses, so even though this is only called when shouldReadSubclasses is true, it may be false for subclasses.
        if ((getOnlyInstancesExpression() != null) || (! shouldReadSubclasses())) {
            ReadObjectQuery concreteQuery = (ReadObjectQuery)query.clone();
            concreteQuery.setReferenceClass(getDescriptor().getJavaClass());
            concreteQuery.setDescriptor(getDescriptor());

            AbstractRecord row = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectOneRowFromConcreteTable();

            if (row != null) {
                return row;
            }
        }

        // Recursively collect all rows from all concrete children and their children.
        for (Enumeration childrenEnum = getChildDescriptors().elements();
                 childrenEnum.hasMoreElements();) {
            ClassDescriptor concreteDescriptor = (ClassDescriptor)childrenEnum.nextElement();
            AbstractRecord row = concreteDescriptor.getInheritancePolicy().selectOneRowUsingCustomMultipleTableSubclassRead(query);

            if (row != null) {
                return row;
            }
        }

        return null;
    }

    /**
     * INTERNAL:
     * Select one row of any concrete subclass,
     * This must use two selects, the first retreives the type field only.
     */
    protected AbstractRecord selectOneRowUsingDefaultMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException, QueryException {
        // Get the row for the given class indicator field
        // The indicator select is prepared in the original query, so can just be executed.
        AbstractRecord typeRow = ((ExpressionQueryMechanism)query.getQueryMechanism()).selectOneRowFromTable();

        if (typeRow == null) {
            return null;
        }

        Class concreteClass = classFromRow(typeRow, query.getSession());
        ClassDescriptor concreteDescriptor = query.getSession().getDescriptor(concreteClass);
        if (concreteDescriptor == null) {
            throw QueryException.noDescriptorForClassFromInheritancePolicy(query, concreteClass);
        }

        ReadObjectQuery concreteQuery = (ReadObjectQuery)query.clone();
        concreteQuery.setReferenceClass(concreteClass);
        concreteQuery.setDescriptor(concreteDescriptor);

        AbstractRecord resultRow = ((ExpressionQueryMechanism)concreteQuery.getQueryMechanism()).selectOneRowFromConcreteTable();

        return resultRow;
    }

    /**
     * INTERNAL:
     * Select one row of any concrete subclass,
     * This must use two selects, the first retreives the type field only.
     */
    public AbstractRecord selectOneRowUsingMultipleTableSubclassRead(ReadObjectQuery query) throws DatabaseException, QueryException {
        if (hasClassExtractor()) {
            return selectOneRowUsingCustomMultipleTableSubclassRead(query);
        } else {
            return selectOneRowUsingDefaultMultipleTableSubclassRead(query);
        }
    }

    /**
     * INTERNAL:
     */
    protected void setAllChildClassIndicators(Vector allChildClassIndicators) {
        this.allChildClassIndicators = allChildClassIndicators;
    }

    /**
     * INTERNAL:
     */
    public void setChildDescriptors(Vector theChildDescriptors) {
        childDescriptors = theChildDescriptors;
    }

    /**
     * ADVANCED:
     * A class extraction method can be registered with the descriptor to override the default inheritance mechanism.
     * This allows for the class indicator field to not be used, and a user defined one instead.
     * The method registered must be a static method on the class that the descriptor is for,
     * the method must take DatabaseRow as argument, and must return the class to use for that row.
     * This method will be used to decide which class to instantiate when reading from the database.
     * It is the application's responsiblity to populate any typing information in the database required
     * to determine the class from the row.
     * If this method is used then the class indicator field and mapping cannot be used,
     * also the descriptor's withAllSubclasses and onlyInstances expressions must also be setup correctly.
     *
     * @see #setWithAllSubclassesExpression(Expression)
     * @see #setOnlyInstancesExpression(Expression)
     */
    public void setClassExtractionMethodName(String staticClassClassExtractionMethod) {
        if ((staticClassClassExtractionMethod == null) || (staticClassClassExtractionMethod.length() == 0)) {
            return;
        }
        if (!(getClassExtractor() instanceof MethodClassExtractor)) {
            setClassExtractor(new MethodClassExtractor());
        }
        ((MethodClassExtractor)getClassExtractor()).setClassExtractionMethodName(staticClassClassExtractionMethod);
    }

    /**
     * INTERNAL:
     * Set the class indicator associations from reading the deployment XML.
     */
    public void setClassIndicatorAssociations(Vector classIndicatorAssociations) {
         setClassNameIndicatorMapping(new HashMap(classIndicatorAssociations.size() + 1));
         setClassIndicatorMapping(new HashMap((classIndicatorAssociations.size() * 2) + 1));
        for (Enumeration associationsEnum = classIndicatorAssociations.elements();
                 associationsEnum.hasMoreElements();) {
            Association association = (Association)associationsEnum.nextElement();
            Object classValue = association.getKey();
            if (classValue instanceof Class) {
                // 904 projects will be a class type.
                addClassIndicator((Class)association.getKey(), association.getValue());
            } else {
                addClassNameIndicator((String)association.getKey(), association.getValue());
            }
        }
    }

    /**
     * ADVANCED:
     * To set the class indicator field.
     * This can be used for advanced field types, such as XML nodes, or to set the field type.
     */
    public void setClassIndicatorField(DatabaseField classIndicatorField) {
        this.classIndicatorField = classIndicatorField;
    }

    /**
     * PUBLIC:
     * To set the class indicator field name.
     * This is the name of the field in the table that stores what type of object this is.
     */
    public void setClassIndicatorFieldName(String fieldName) {
        if (fieldName == null) {
            setClassIndicatorField(null);
        } else {
            setClassIndicatorField(new DatabaseField(fieldName));
        }
    }

    /**
     * PUBLIC:
     * Set the association of indicators and classes.
     * This may be desired to be used by clients in strange inheritence models.
     */
     public void setClassIndicatorMapping(Map classIndicatorMapping) {
        this.classIndicatorMapping = classIndicatorMapping;
    }

    /**
     * INTERNAL:
     * Set the class name indicator mapping, used by the MW.
     */
     public void setClassNameIndicatorMapping(Map classNameIndicatorMapping) {
        this.classNameIndicatorMapping = classNameIndicatorMapping;
    }

    /**
     * INTERNAL:
     * Set the descriptor.
     */
    public void setDescriptor(ClassDescriptor descriptor) {
        this.descriptor = descriptor;
    }

    /**
     * INTERNAL:
     * Used to indicate a JOINED inheritance strategy.
     *
     */
    public void setJoinedStrategy() {
        isJoinedStrategy = true;
    }
    
    /**
     * ADVANCED:
     * Sets the expression used to select instance of the class only. Can be used to customize the
     * inheritance class indicator expression.
     */
    public void setOnlyInstancesExpression(Expression onlyInstancesExpression) {
        this.onlyInstancesExpression = onlyInstancesExpression;
    }

    /**
     * PUBLIC:
     * Set the parent class.
     * A descriptor can inherit from another descriptor through defining it as its parent.
     * The root descriptor must define a class indicator field and mapping.
     * All children must share the same table as their parent but can add additional tables.
     * All children must share the root descriptor primary key.
     */
    public void setParentClass(Class parentClass) {
        this.parentClass = parentClass;
        if (parentClass != null) {
            setParentClassName(parentClass.getName());
        }
    }

    /**
     * INTERNAL:
     * Set the parent class name, used by MW to avoid referencing the real class for
     * deployment XML generation.
     */
    public void setParentClassName(String parentClassName) {
        this.parentClassName = parentClassName;
    }

    /**
     * INTERNAL:
     */
    public void setParentDescriptor(ClassDescriptor parentDescriptor) {
        this.parentDescriptor = parentDescriptor;
    }

    /**
     * INTERNAL:
     * The view can be used to optimize/customize the query for all subclasses where they have multiple tables.
     * This view can do the outer join, we require the view because we cannot generate dynmic platform independent SQL
     * for outer joins (i.e. not possible to do so either).
     */
    protected void setReadAllSubclassesView(DatabaseTable readAllSubclassesView) {
        this.readAllSubclassesView = readAllSubclassesView;
    }

    /**
     * ADVANCED:
     * The view can be used to optimize/customize the query for all subclasses where they have multiple tables.
     * This view can use outer joins or unions to combine the results of selecting from all of the subclass tables.
     * If a view is not given then TopLink must make an individual call for each subclass.
     */
    public void setReadAllSubclassesViewName(String readAllSubclassesViewName) {
        if (readAllSubclassesViewName == null) {
            setReadAllSubclassesView(null);
        } else {
            setReadAllSubclassesView(new DatabaseTable(readAllSubclassesViewName));
        }
    }

    /**
     * INTERNAL:
     * Set the descriptor to read instance of itself and its subclasses when queried.
     * This is used with inheritence to configure the result of queries.
     * By default this is true for root inheritence descriptors, and false for all others.
     */
    public void setShouldReadSubclasses(Boolean shouldReadSubclasses) {
        this.shouldReadSubclasses = shouldReadSubclasses;
    }

    /**
     * PUBLIC:
     * Set the descriptor to read instance of itself and its subclasses when queried.
     * This is used with inheritence to configure the result of queries.
     * By default this is true for root inheritence descriptors, and false for all others.
     */
    public void setShouldReadSubclasses(boolean shouldReadSubclasses) {
        this.shouldReadSubclasses = Boolean.valueOf(shouldReadSubclasses);
    }

    /**
     * PUBLIC:
     * Set if the descriptor uses the classes fully qualified name as the indicator.
     * The class indicator is used with inheritence to determine the class from a row.
     * By default a class indicator mapping is required, this can be set to true if usage of the class
     * name is desired.
     * The field must be of a large enough size to store the fully qualified class name.
     */
    public void setShouldUseClassNameAsIndicator(boolean shouldUseClassNameAsIndicator) {
        this.shouldUseClassNameAsIndicator = shouldUseClassNameAsIndicator;
    }

    /**
     * PUBLIC:
     * Sets the inheritance policy to always use an outer join when quering across a relationship of class.
     * used when using getAllowingNull(), or anyOfAllowingNone()
     */

    // cr3546
    public void setAlwaysUseOuterJoinForClassType(boolean choice) {
        this.shouldAlwaysUseOuterJoin = choice;
    }

    /**
     * INTERNAL:
     * Used to indicate a SINGLE_TABLE inheritance strategy. Since only JOINED and SINGLE_TABLE
     * strategies are supported at this time (no support for TABLE_PER_CLASS) using a
     * !isJoinedStrategy an an indicator for SINGLE_TABLE is sufficient.
     *
     */
    public void setSingleTableStrategy() {
        isJoinedStrategy = false;
    }

    /**
     * INTERNAL:
     * Sets if we should use the descriptor inheritance to determine
     * if an object can be returned from the identity map or not.
     */
    public void setUseDescriptorsToValidateInheritedObjects(boolean useDescriptorsToValidateInheritedObjects) {
        //CR 4005
        this.useDescriptorsToValidateInheritedObjects = useDescriptorsToValidateInheritedObjects;
    }

    /**
     * ADVANCED:
     * Sets the expression to be used for querying for a class and all its subclasses. Can be used
     * to customize the inheritence class indicator expression.
     */
    public void setWithAllSubclassesExpression(Expression withAllSubclassesExpression) {
        this.withAllSubclassesExpression = withAllSubclassesExpression;
    }

    /**
     * PUBLIC:
     * Return true if this descriptor should read instances of itself and subclasses on queries.
     */
    public boolean shouldReadSubclasses() {
        if (shouldReadSubclasses == null) {
            return true;
        }
        return shouldReadSubclasses.booleanValue();
    }

    /**
     * INTERNAL:
     * Return true if this descriptor should read instances of itself and subclasses on queries.
     */
    public Boolean shouldReadSubclassesValue() {
        return shouldReadSubclasses;
    }

    /**
     * PUBLIC:
     * returns if the inheritance policy will always use an outerjoin when selecting class type
     */

    // cr3546
    public boolean shouldAlwaysUseOuterJoin() {
        return this.shouldAlwaysUseOuterJoin;
    }

    /**
     * PUBLIC:
     * Return true if the descriptor use the classes full name as the indicator.
     * The class indicator is used with inheritance to determine the class from a row.
     * By default a class indicator mapping is required, this can be set to true if usage of the class
     * name is desired.
     * The field must be of a large enough size to store the fully qualified class name.
     */
    public boolean shouldUseClassNameAsIndicator() {
        return shouldUseClassNameAsIndicator;
    }

    /**
     * INTERNAL:
     */
    public String toString() {
        return Helper.getShortClassName(getClass()) + "(" + getDescriptor() + ")";
    }

    /**
     * PUBLIC:
     * Set the descriptor to use the classes full name as the indicator.
     * The class indicator is used with inheritance to determine the class from a row.
     * By default a class indicator mapping is required, this can be set to true if usage of the class
     * name is desired.
     * The field must be of a large enough size to store the fully qualified class name.
     */
    public void useClassNameAsIndicator() {
        setShouldUseClassNameAsIndicator(true);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import java.util.*;
import oracle.toplink.essentials.descriptors.VersionLockingPolicy;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.helper.linkedlist.LinkedNode;
import oracle.toplink.essentials.internal.queryframework.ContainerPolicy;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.logging.SessionLog;
import oracle.toplink.essentials.sessions.SessionProfiler;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.descriptors.ObjectBuilder;
import oracle.toplink.essentials.internal.descriptors.OptimisticLockingPolicy;

/**
 * <p><b>Purpose</b>:
 * Used to manage the merge of two objects in a unit of work.
 *
 * @author James Sutherland
 * @since TOPLink/Java 1.1
 */
public class MergeManager {

    /** The unit of work merging for. */
    protected AbstractSession session;

    /** Used only while refreshing objects on remote session */
    protected HardIdentityHashtable objectDescriptors;

    /** Used to unravel recursion. */
    protected HardIdentityHashtable objectsAlreadyMerged;
    
    /** Used to keep track of merged new objects. */
    protected HardIdentityHashtable mergedNewObjects;

    /** Used to store the list of locks that this merge manager has acquired for this merge */
    protected ArrayList acquiredLocks;

    /** If this variable is not null then the mergemanager is waiting on a particular cacheKey */
    protected CacheKey writeLockQueued;

    /** Stores the node that holds this mergemanager within the WriteLocksManager queue */
    protected LinkedNode queueNode;

    /** Policy that determines merge type (i.e. merge is used for several usages). */
    protected int mergePolicy;
    protected static final int WORKING_COPY_INTO_ORIGINAL = 1;
    protected static final int ORIGINAL_INTO_WORKING_COPY = 2;
    protected static final int CLONE_INTO_WORKING_COPY = 3;
    protected static final int WORKING_COPY_INTO_REMOTE = 4;
    protected static final int REFRESH_REMOTE_OBJECT = 5;
    protected static final int CHANGES_INTO_DISTRIBUTED_CACHE = 6;
    protected static final int CLONE_WITH_REFS_INTO_WORKING_COPY = 7;
    protected static final int WORKING_COPY_INTO_BACKUP = 9;

    /** Policy that determines how the merge will cascade to its object's parts. */
    protected int cascadePolicy;
    public static final int NO_CASCADE = 1;
    public static final int CASCADE_PRIVATE_PARTS = 2;
    public static final int CASCADE_ALL_PARTS = 3;
    public static final int CASCADE_BY_MAPPING = 4;
    protected long systemTime = 0;// stored so that all objects merged by a merge manager can have the same readTime
    public static boolean LOCK_ON_MERGE = true;

    /** Force cascade merge even if a clone is already registered */
    // GF#1139 Cascade doesn't work when merging managed entity
    protected boolean forceCascade;

    public MergeManager(AbstractSession session) {
        this.session = session;
        this.mergedNewObjects = new HardIdentityHashtable();
        this.objectsAlreadyMerged = new HardIdentityHashtable();
        this.cascadePolicy = CASCADE_ALL_PARTS;
        this.mergePolicy = WORKING_COPY_INTO_ORIGINAL;
        this.objectDescriptors = new HardIdentityHashtable();
        this.acquiredLocks = new ArrayList();
    }

    /**
     * Build and return an identity set for the specified container.
     */
    protected HardIdentityHashtable buildIdentitySet(Object container, ContainerPolicy containerPolicy, boolean keyByTarget) {
        // find next power-of-2 size
        HardIdentityHashtable result = new HardIdentityHashtable(containerPolicy.sizeFor(container) + 1);
        for (Object iter = containerPolicy.iteratorFor(container); containerPolicy.hasNext(iter);) {
            Object element = containerPolicy.next(iter, getSession());
            if (keyByTarget) {
                result.put(getTargetVersionOfSourceObject(element), element);
            } else {
                result.put(element, element);
            }
        }
        return result;
    }

    /**
     * Cascade all parts, this is the default for the merge.
     */
    public void cascadeAllParts() {
        setCascadePolicy(CASCADE_ALL_PARTS);
    }

    /**
     * Cascade private parts, this can be used to merge clone when using RMI.
     */
    public void cascadePrivateParts() {
        setCascadePolicy(CASCADE_PRIVATE_PARTS);
    }

    /**
     * Merge only direct parts, this can be used to merge clone when using RMI.
     */
    public void dontCascadeParts() {
        setCascadePolicy(NO_CASCADE);
    }

    public ArrayList getAcquiredLocks() {
        return this.acquiredLocks;
    }

    public int getCascadePolicy() {
        return cascadePolicy;
    }

    protected int getMergePolicy() {
        return mergePolicy;
    }

    public HardIdentityHashtable getObjectDescriptors() {
        return objectDescriptors;
    }

    // cr 2855 changed visibility of following method
    public HardIdentityHashtable getObjectsAlreadyMerged() {
        return objectsAlreadyMerged;
    }

    public Object getObjectToMerge(Object sourceValue) {
        if (shouldMergeOriginalIntoWorkingCopy()) {
            return getTargetVersionOfSourceObject(sourceValue);
        }

        return sourceValue;
    }

    /**
     * INTENRAL:
     * Used to get the node that this merge manager is stored in, within the WriteLocksManager write lockers queue
     */
    public LinkedNode getQueueNode() {
        return this.queueNode;
    }

    public AbstractSession getSession() {
        return session;
    }

    /**
     * Get the stored value of the current time. This method lazily initializes
     * so that read times for the same merge manager can all be set to the same read time
     */
    public long getSystemTime() {
        if (systemTime == 0) {
            systemTime = System.currentTimeMillis();
        }
        return systemTime;
    }

    /**
     * Return the coresponding value that should be assigned to the target object for the source object.
     * This value must be local to the targets object space.
     */
    public Object getTargetVersionOfSourceObject(Object source) {
        if (shouldMergeWorkingCopyIntoOriginal() || shouldMergeWorkingCopyIntoRemote()) {
            // Target is in uow parent, or original instance for new object.
            return ((UnitOfWorkImpl)getSession()).getOriginalVersionOfObject(source);
        } else if (shouldMergeCloneIntoWorkingCopy() || shouldMergeOriginalIntoWorkingCopy() || shouldMergeCloneWithReferencesIntoWorkingCopy()) {
            // Target is clone from uow.
            //make sure we use the register for merge
            //bug 3584343
            return registerObjectForMergeCloneIntoWorkingCopy(source);
        } else if (shouldRefreshRemoteObject()) {
            // Target is in session's cache.
            ClassDescriptor descriptor = getSession().getDescriptor(source);
            Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(source, getSession());
            return getSession().getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, source.getClass(), descriptor);
        }

        throw ValidationException.invalidMergePolicy();
    }

    /**
     * INTENRAL:
     * Used to get the object that the merge manager is waiting on, in order to acquire locks
     */
    public CacheKey getWriteLockQueued() {
        return this.writeLockQueued;
    }

    /**
     * Recursively merge changes in the object dependent on the merge policy.
     * The hastable is used to resolv recursion.
     */
    public Object mergeChanges(Object object, ObjectChangeSet objectChangeSet) throws ValidationException {
        if (object == null) {
            return object;
        }

        // Do not merged read-only objects in a unit of work.
        if (getSession().isClassReadOnly(object.getClass())) {
            return object;
        }

        // Means that object is either already merged or in the process of being merged.
        if (getObjectsAlreadyMerged().containsKey(object)) {
            return object;
        }

        // Put the object to be merged in the set.
        getObjectsAlreadyMerged().put(object, object);

        Object mergedObject;
        if (shouldMergeWorkingCopyIntoOriginal()) {
            mergedObject = mergeChangesOfWorkingCopyIntoOriginal(object, objectChangeSet);
        } else if (shouldMergeCloneIntoWorkingCopy() || shouldMergeCloneWithReferencesIntoWorkingCopy()) {
            mergedObject = mergeChangesOfCloneIntoWorkingCopy(object);
        } else if (shouldMergeOriginalIntoWorkingCopy()) {
            mergedObject = mergeChangesOfOriginalIntoWorkingCopy(object);
        } else {
            throw ValidationException.invalidMergePolicy();
        }

        return mergedObject;
    }

    /**
     * INTERNAL:
     * Merge the changes to all objects to session's cache.
     */
    public void mergeChangesFromChangeSet(UnitOfWorkChangeSet uowChangeSet) {
        getSession().startOperationProfile(SessionProfiler.DistributedMerge);
        // Ensure concurrency if cache isolation requires.
        getSession().getIdentityMapAccessorInstance().acquireWriteLock();
        getSession().log(SessionLog.FINER, SessionLog.PROPAGATION, "received_updates_from_remote_server");
        getSession().getEventManager().preDistributedMergeUnitOfWorkChangeSet(uowChangeSet);

        try {
            // Iterate over each clone and let the object build merge to clones into the originals.
            getSession().getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(this, uowChangeSet);
            Enumeration objectChangeEnum = uowChangeSet.getAllChangeSets().keys();
            while (objectChangeEnum.hasMoreElements()) {
                ObjectChangeSet objectChangeSet = (ObjectChangeSet)objectChangeEnum.nextElement();
                Object object = objectChangeSet.getTargetVersionOfSourceObject(getSession(), false);

                // Don't read the object here. If it is null then we won't merge it at this stage, unless it
                // is being referenced which will force the load later
                Object mergedObject = this.mergeChanges(object, objectChangeSet);

                // if mergedObject is null, it might be because objectChangeSet represents a new object and could not look it up
                // Check the descriptor setting for this change set to see if the new object should be added to the cache.
                if (mergedObject == null) {
                    if (objectChangeSet.isNew()) {
                        mergedObject = mergeNewObjectIntoCache(objectChangeSet);
                    }
                }
                if (mergedObject == null) {
                    getSession().incrementProfile(SessionProfiler.ChangeSetsNotProcessed);
                } else {
                    getSession().incrementProfile(SessionProfiler.ChangeSetsProcessed);
                }
            }
            Enumeration deletedObjects = uowChangeSet.getDeletedObjects().elements();
            while (deletedObjects.hasMoreElements()) {
                ObjectChangeSet changeSet = (ObjectChangeSet)deletedObjects.nextElement();
                changeSet.removeFromIdentityMap(getSession());
                getSession().incrementProfile(SessionProfiler.DeletedObject);
            }
        } catch (RuntimeException exception) {
            getSession().handleException(exception);
        } finally {
            getSession().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(this);
            getSession().getIdentityMapAccessorInstance().releaseWriteLock();
            getSession().getEventManager().postDistributedMergeUnitOfWorkChangeSet(uowChangeSet);
            getSession().endOperationProfile(SessionProfiler.DistributedMerge);
        }
    }

    /**
     * Merge the changes from the collection of a source object into the target using the backup as the diff.
     * Return true if any changes occurred.
     */
    public boolean mergeChangesInCollection(Object source, Object target, Object backup, DatabaseMapping mapping) {
        ContainerPolicy containerPolicy = mapping.getContainerPolicy();

        // The vectors must be converted to identity sets to avoid dependency on #equals().
        HardIdentityHashtable backupSet = buildIdentitySet(backup, containerPolicy, false);
        HardIdentityHashtable sourceSet = null;
        HardIdentityHashtable targetToSources = null;

        // We need either a source or target-to-source set, depending on the merge type.
        if (shouldMergeWorkingCopyIntoOriginal()) {
            sourceSet = buildIdentitySet(source, containerPolicy, false);
        } else {
            targetToSources = buildIdentitySet(source, containerPolicy, true);
        }

        boolean changeOccured = false;

        // If the backup is also the target, clone it; since it may change during the loop.
        if (backup == target) {
            backup = containerPolicy.cloneFor(backup);
        }

        // Handle removed elements.
        for (Object backupIter = containerPolicy.iteratorFor(backup);
                 containerPolicy.hasNext(backupIter);) {
            Object backupElement = containerPolicy.next(backupIter, getSession());

            // The check for delete depends on the type of merge.
            if (shouldMergeWorkingCopyIntoOriginal()) {// Source and backup are the same space.
                if (!sourceSet.containsKey(backupElement)) {
                    changeOccured = true;
                    containerPolicy.removeFrom((Object)null, getTargetVersionOfSourceObject(backupElement), target, getSession());

                    // Registered new object in nested units of work must not be registered into the parent,
                    // so this records them in the merge to parent case.
                    if (mapping.isPrivateOwned()) {
                        registerRemovedNewObjectIfRequired(backupElement);
                    }
                }
            } else {// Target and backup are same for all types of merge.
                if (!targetToSources.containsKey(backupElement)) {// If no source for target then was removed.
                    changeOccured = true;
                    containerPolicy.removeFrom((Object)null, backupElement, target, getSession());// Backup value is same as target value.
                }
            }
        }

        // Handle added elements.
        for (Object sourceIter = containerPolicy.iteratorFor(source);
                 containerPolicy.hasNext(sourceIter);) {
            Object sourceElement = containerPolicy.next(sourceIter, getSession());

            // The target object must be completely merged before adding to the collection
            // otherwise another thread could pick up the partial object.
            mapping.cascadeMerge(sourceElement, this);
            // The check for add depends on the type of merge.
            if (shouldMergeWorkingCopyIntoOriginal()) {// Source and backup are the same space.
                if (!backupSet.containsKey(sourceElement)) {
                    changeOccured = true;
                    containerPolicy.addInto(getTargetVersionOfSourceObject(sourceElement), target, getSession());
                } else {
                    containerPolicy.validateElementAndRehashIfRequired(sourceElement, target, getSession(), getTargetVersionOfSourceObject(sourceElement));
                }
            } else {// Target and backup are same for all types of merge.
                Object targetVersionOfSourceElement = getTargetVersionOfSourceObject(sourceElement);
                if (!backupSet.containsKey(targetVersionOfSourceElement)) {// Backup value is same as target value.
                    changeOccured = true;
                    containerPolicy.addInto(targetVersionOfSourceElement, target, getSession());
                }
            }
        }

        return changeOccured;
    }

    /**
     * Recursively merge to rmi clone into the unit of work working copy.
     * The hastable is used to resolv recursion.
     */
    protected Object mergeChangesOfCloneIntoWorkingCopy(Object rmiClone) {
        ClassDescriptor descriptor = getSession().getDescriptor(rmiClone);
        Object registeredObject = registerObjectForMergeCloneIntoWorkingCopy(rmiClone);

        if (registeredObject == rmiClone && !shouldForceCascade()) {
            //need to find better better fix. prevents merging into itself.
            return rmiClone;
        }

        boolean changeTracked = false;
        try {
            ObjectBuilder builder = descriptor.getObjectBuilder();
            
            if (registeredObject != rmiClone && descriptor.usesVersionLocking() && ! mergedNewObjects.containsKey(registeredObject)) {
                VersionLockingPolicy policy = (VersionLockingPolicy) descriptor.getOptimisticLockingPolicy();
                if (policy.isStoredInObject()) {
                    Object currentValue = builder.extractValueFromObjectForField(registeredObject, policy.getWriteLockField(), session);
                
                    if (policy.isNewerVersion(currentValue, rmiClone, session.keyFromObject(rmiClone), session)) {
                        throw OptimisticLockException.objectChangedSinceLastMerge(rmiClone);
                    }
                }
            }
            
            // Toggle change tracking during the merge.
            descriptor.getObjectChangePolicy().dissableEventProcessing(registeredObject);
            
            boolean cascadeOnly = false;
            if(registeredObject == rmiClone){
                // GF#1139 Cascade merge operations to relationship mappings even if already registered
                cascadeOnly = true;
            }
            // Merge into the clone from the original, use clone as backup as anything different should be merged.
            builder.mergeIntoObject(registeredObject, false, rmiClone, this, cascadeOnly);
        } finally {
            descriptor.getObjectChangePolicy().enableEventProcessing(registeredObject);
        }

        return registeredObject;
    }

    /**
     * Recursively merge to original from its parent into the clone.
     * The hastable is used to resolv recursion.
     */
    protected Object mergeChangesOfOriginalIntoWorkingCopy(Object clone) {
        ClassDescriptor descriptor = getSession().getDescriptor(clone);

        // Find the original object, if it is not there then do nothing.
        Object original = ((UnitOfWorkImpl)getSession()).getOriginalVersionOfObjectOrNull(clone);

        if (original == null) {
            return clone;
        }

        // Merge into the clone from the original, use clone as backup as anything different should be merged.
        descriptor.getObjectBuilder().mergeIntoObject(clone, false, original, this);

        //update the change policies with the refresh
        descriptor.getObjectChangePolicy().revertChanges(clone, descriptor, (UnitOfWorkImpl)this.getSession(), ((UnitOfWorkImpl)this.getSession()).getCloneMapping());
        Vector primaryKey = getSession().keyFromObject(clone);
        if (descriptor.usesOptimisticLocking()) {
            descriptor.getOptimisticLockingPolicy().mergeIntoParentCache((UnitOfWorkImpl)getSession(), primaryKey, clone);
        }

        CacheKey parentCacheKey = ((UnitOfWorkImpl)getSession()).getParent().getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, clone.getClass(), descriptor);
        CacheKey uowCacheKey = getSession().getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, clone.getClass(), descriptor);

        // Check for null because when there is NoIdentityMap, CacheKey will be null
        if ((parentCacheKey != null) && (uowCacheKey != null)) {
            uowCacheKey.setReadTime(parentCacheKey.getReadTime());
        }

        return clone;
    }

    /**
     * Recursively merge to clone into the orignal in its parent.
     * The hastable is used to resolv recursion.
     */
    protected Object mergeChangesOfWorkingCopyIntoOriginal(Object clone, ObjectChangeSet objectChangeSet) {
        UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)getSession();

        // This always finds an original different from the clone, even if it has to create one.
        // This must be done after special cases have been computed because it registers unregistered new objects.
        Object original = unitOfWork.getOriginalVersionOfObjectOrNull(clone);

        ClassDescriptor descriptor = unitOfWork.getDescriptor(clone.getClass());

        // Always merge into the original.
        try {
            if (original == null) {
                // if original does not exist then we must merge the entire object
                original = unitOfWork.buildOriginal(clone);
                if (objectChangeSet == null) {
                    descriptor.getObjectBuilder().mergeIntoObject(original, true, clone, this);
                } else if (!objectChangeSet.isNew()) {
                    //once the original is created we must put it in the cache and
                    //lock it to prevent a reading thread from creating it as well
                    //there will be no deadlock situation because no other threads
                    //will be able to reference this object.
                    AbstractSession parent = unitOfWork.getParent();
                    original = parent.getIdentityMapAccessorInstance().getWriteLockManager().appendLock(objectChangeSet.getPrimaryKeys(), original, descriptor, this, parent);
                    descriptor.getObjectBuilder().mergeIntoObject(original, true, clone, this);
                } else {
                    descriptor.getObjectBuilder().mergeChangesIntoObject(original, objectChangeSet, clone, this);
                }
            } else if (objectChangeSet == null) {
                // if we have no change set then we must merge the entire object
                descriptor.getObjectBuilder().mergeIntoObject(original, false, clone, this);
            } else {
                // not null and we have a valid changeSet then merge the changes
                if (!objectChangeSet.isNew()) {
                    AbstractSession parent = unitOfWork.getParent();
                    if(objectChangeSet.shouldInvalidateObject(original, parent)) {
                        parent.getIdentityMapAccessor().invalidateObject(original);
                        return clone;
                    }
                }
                descriptor.getObjectBuilder().mergeChangesIntoObject(original, objectChangeSet, clone, this);
            }
        } catch (QueryException exception) {
            // Ignore validation errors if unit of work validation is suppressed.
            // Also there is a very specific case under EJB wrappering where
            // a related object may have never been accessed in the unit of work context
            // but is still valid, so this error must be ignored.
            if (unitOfWork.shouldPerformNoValidation() || (descriptor.hasWrapperPolicy())) {
                if ((exception.getErrorCode() != QueryException.BACKUP_CLONE_DELETED) && (exception.getErrorCode() != QueryException.BACKUP_CLONE_IS_ORIGINAL_FROM_PARENT) && (exception.getErrorCode() != QueryException.BACKUP_CLONE_IS_ORIGINAL_FROM_SELF)) {
                    throw exception;
                }
                return clone;
            } else {
                throw exception;
            }
        }

        if (!unitOfWork.isNestedUnitOfWork()) {
            Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, unitOfWork);

            // Must ensure the get and put of the cache occur as a single operation.
            // Cache key hold a reference to a concurrency manager which is used for the lock/release operation
            CacheKey cacheKey = unitOfWork.getParent().getIdentityMapAccessorInstance().acquireLock(primaryKey, original.getClass(), descriptor);
            try {
                if (descriptor.usesOptimisticLocking()) {
                    if (descriptor.getOptimisticLockingPolicy().isChildWriteLockValueGreater(unitOfWork, primaryKey, original.getClass())) {
                        cacheKey.setWriteLockValue(unitOfWork.getIdentityMapAccessor().getWriteLockValue(original));
                    }
                }

                // Always put in the parent im for root because it must now be persistent.
                cacheKey.setObject(original);
                if (descriptor.getCacheInvalidationPolicy().shouldUpdateReadTimeOnUpdate() || ((objectChangeSet != null) && objectChangeSet.isNew())) {
                    cacheKey.setReadTime(getSystemTime());
                }
            } finally {
                cacheKey.updateAccess();
                cacheKey.release();
            }
        }
        return clone;
    }

    /**
     * This can be used by the user for merging clones from RMI into the unit of work.
     */
    public void mergeCloneIntoWorkingCopy() {
        setMergePolicy(CLONE_INTO_WORKING_COPY);
    }

    /**
     * This is used during the merge of dependent objects referencing independent objects, where you want
     * the independent objects merged as well.
     */
    public void mergeCloneWithReferencesIntoWorkingCopy() {
        setMergePolicy(CLONE_WITH_REFS_INTO_WORKING_COPY);
    }

    /**
     * This is used during cache synchronisation to merge the changes into the distributed cache.
     */
    public void mergeIntoDistributedCache() {
        setMergePolicy(CHANGES_INTO_DISTRIBUTED_CACHE);
    }

    /**
     * Merge a change set for a new object into the cache. This method will create a
     * shell for the new object and then merge the changes from the change set into the object.
     * The newly merged object will then be added to the cache.
     */
    public Object mergeNewObjectIntoCache(ObjectChangeSet changeSet) {
        if (changeSet.isNew()) {
            Class objectClass = changeSet.getClassType(session);
            ClassDescriptor descriptor = getSession().getDescriptor(objectClass);
            //Try to find the object first we may have merged it all ready
            Object object = changeSet.getTargetVersionOfSourceObject(getSession(), false);
            if (object == null) {
                if (!getObjectsAlreadyMerged().containsKey(changeSet)) {
                    // if we haven't merged this object allready then build a new object
                    // otherwise leave it as null which will stop the recursion
                    object = descriptor.getObjectBuilder().buildNewInstance();
                    //Store the changeset to prevent us from creating this new object again
                    getObjectsAlreadyMerged().put(changeSet, object);
                } else {
                    //we have all ready created the object, must be in a cyclic
                    //merge on a new object so get it out of the allreadymerged collection
                    object = getObjectsAlreadyMerged().get(changeSet);
                }
            } else {
                object = changeSet.getTargetVersionOfSourceObject(getSession(), true);
            }
            mergeChanges(object, changeSet);
            Object implementation = descriptor.getObjectBuilder().unwrapObject(object, getSession());

            return getSession().getIdentityMapAccessorInstance().putInIdentityMap(implementation, descriptor.getObjectBuilder().extractPrimaryKeyFromObject(implementation, getSession()), changeSet.getWriteLockValue(), getSystemTime(), descriptor);
        }
        return null;
    }

    /**
     * This is used to revert changes to objects, or during refreshes.
     */
    public void mergeOriginalIntoWorkingCopy() {
        setMergePolicy(ORIGINAL_INTO_WORKING_COPY);
    }

    /**
     * This is used during the unit of work commit to merge changes into the parent.
     */
    public void mergeWorkingCopyIntoBackup() {
        setMergePolicy(WORKING_COPY_INTO_BACKUP);
    }

    /**
     * This is used during the unit of work commit to merge changes into the parent.
     */
    public void mergeWorkingCopyIntoOriginal() {
        setMergePolicy(WORKING_COPY_INTO_ORIGINAL);
    }

    /**
     * This is used during the unit of work commit to merge changes into the parent.
     */
    public void mergeWorkingCopyIntoRemote() {
        setMergePolicy(WORKING_COPY_INTO_REMOTE);
    }

    /**
     * INTERNAL:
     * This is used to refresh remote session object
     */
    public void refreshRemoteObject() {
        setMergePolicy(REFRESH_REMOTE_OBJECT);
    }

    /**
     * INTERNAL:
     * When merging froma clone when the cache cannot be gaurenteed the object must be first read if it is existing
     * and not in the cache. Otherwise no changes will be detected as the original state is missing.
     */
    protected Object registerObjectForMergeCloneIntoWorkingCopy(Object clone) {
        ClassDescriptor descriptor = getSession().getDescriptor(clone.getClass());
        Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(clone, getSession());

        //Must use the java class as this may be a bean that we are merging and it may not have the same class as the
        // objects in the cache. As of EJB 2.0
        Object objectFromCache = getSession().getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, descriptor.getJavaClass(), false, descriptor);
        if (objectFromCache != null) {
            return objectFromCache;
        }

        oracle.toplink.essentials.queryframework.DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery();

        // Optimize cache option to avoid executing the does exist query.
        if (existQuery.shouldCheckCacheForDoesExist()) {
            return ((UnitOfWorkImpl)getSession()).internalRegisterObject(clone, descriptor);
        }

        // Check early return to check if it is a new object, i.e. null primary key.
        Boolean doesExist = (Boolean)existQuery.checkEarlyReturn(clone, primaryKey, getSession(), null);
        if (doesExist == Boolean.FALSE) {
            Object registeredObject = ((UnitOfWorkImpl)getSession()).internalRegisterObject(clone, descriptor);
            mergedNewObjects.put(registeredObject, registeredObject);
            return registeredObject;
        }

        // Otherwise it is existing and not in the cache so it must be read.
        Object object = getSession().readObject(clone);
        if (object == null) {
            //bug6180972: avoid internal register's existence check and be sure to put the new object in the mergedNewObjects collection
            object = ((UnitOfWorkImpl)getSession()).cloneAndRegisterNewObject(clone);
            mergedNewObjects.put(object, object);
        }
        return object;
    }

    /**
     * Determine if the object is a registered new object, and that this is a nested unit of work
     * merge into the parent. In this case private mappings will register the object as being removed.
     */
    public void registerRemovedNewObjectIfRequired(Object removedObject) {
        if (getSession().isUnitOfWork()) {
            UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)getSession();

            if (shouldMergeWorkingCopyIntoOriginal() && unitOfWork.getParent().isUnitOfWork() && unitOfWork.isCloneNewObject(removedObject)) {
                Object originalVersionOfRemovedObject = unitOfWork.getOriginalVersionOfObject(removedObject);
                unitOfWork.addRemovedObject(originalVersionOfRemovedObject);
            }
        }
    }

    public void setCascadePolicy(int cascadePolicy) {
        this.cascadePolicy = cascadePolicy;
    }

    protected void setMergePolicy(int mergePolicy) {
        this.mergePolicy = mergePolicy;
    }

    public void setForceCascade(boolean forceCascade) {
        this.forceCascade = forceCascade;
    }

    public void setObjectDescriptors(HardIdentityHashtable objectDescriptors) {
        this.objectDescriptors = objectDescriptors;
    }

    protected void setObjectsAlreadyMerged(HardIdentityHashtable objectsAlreadyMerged) {
        this.objectsAlreadyMerged = objectsAlreadyMerged;
    }

    /**
     * INTENRAL:
     * Used to set the node that this merge manager is stored in, within the WriteLocksManager write lockers queue
     */
    public void setQueueNode(LinkedNode node) {
        this.queueNode = node;
    }

    protected void setSession(AbstractSession session) {
        this.session = session;
    }

    /**
     * INTENRAL:
     * Used to set the object that the merge manager is waiting on, in order to acquire locks
     * If this value is null then the merge manager is not waiting on any locks.
     */
    public void setWriteLockQueued(CacheKey writeLockQueued) {
        this.writeLockQueued = writeLockQueued;
    }

    /**
     * Flag used to determine that the mappings should be checked for
     * cascade requirements.
     */
    public boolean shouldCascadeByMapping() {
        return getCascadePolicy() == CASCADE_BY_MAPPING;
    }

    /**
     * Flag used to determine if all parts should be cascaded
     */
    public boolean shouldCascadeAllParts() {
        return getCascadePolicy() == CASCADE_ALL_PARTS;
    }

    /**
     * Flag used to determine if any parts should be cascaded
     */
    public boolean shouldCascadeParts() {
        return getCascadePolicy() != NO_CASCADE;
    }

    /**
     * Flag used to determine if any private parts should be cascaded
     */
    public boolean shouldCascadePrivateParts() {
        return (getCascadePolicy() == CASCADE_PRIVATE_PARTS) || (getCascadePolicy() == CASCADE_ALL_PARTS);
    }

    /**
     * Refreshes are based on the objects row, so all attributes of the object must be refreshed.
     * However merging from RMI, normally reference are made transient, so should not be merge unless
     * specified.
     */
    public boolean shouldCascadeReferences() {
        return !shouldMergeCloneIntoWorkingCopy();
    }

    /**
     * INTERNAL:
     * This happens when changes from an UnitOfWork is propagated to a distributed class.
     */
    public boolean shouldMergeChangesIntoDistributedCache() {
        return getMergePolicy() == CHANGES_INTO_DISTRIBUTED_CACHE;
    }

    /**
     * This can be used by the user for merging clones from RMI into the unit of work.
     */
    public boolean shouldMergeCloneIntoWorkingCopy() {
        return getMergePolicy() == CLONE_INTO_WORKING_COPY;
    }

    /**
     * This can be used by the user for merging remote EJB objects into the unit of work.
     */
    public boolean shouldMergeCloneWithReferencesIntoWorkingCopy() {
        return getMergePolicy() == CLONE_WITH_REFS_INTO_WORKING_COPY;
    }

    /**
     * This is used to revert changes to objects, or during refreshes.
     */
    public boolean shouldMergeOriginalIntoWorkingCopy() {
        return getMergePolicy() == ORIGINAL_INTO_WORKING_COPY;
    }

    /**
     * This is used during the unit of work commit to merge changes into the parent.
     */
    public boolean shouldMergeWorkingCopyIntoBackup() {
        return getMergePolicy() == WORKING_COPY_INTO_BACKUP;
    }

    /**
     * This is used during the unit of work commit to merge changes into the parent.
     */
    public boolean shouldMergeWorkingCopyIntoOriginal() {
        return getMergePolicy() == WORKING_COPY_INTO_ORIGINAL;
    }

    /**
     * INTERNAL:
     * This happens when serialized remote unit of work has to be merged with local remote unit of work.
     */
    public boolean shouldMergeWorkingCopyIntoRemote() {
        return getMergePolicy() == WORKING_COPY_INTO_REMOTE;
    }

    /**
     * INTERNAL:
     * This is used to refresh objects on the remote session
     */
    public boolean shouldRefreshRemoteObject() {
        return getMergePolicy() == REFRESH_REMOTE_OBJECT;
    }

    /**
     * This is used to cascade merge even if a clone is already registered.
     */
    public boolean shouldForceCascade() {
        return forceCascade;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.descriptors;

import java.io.*;
import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.queryframework.JoinedAttributeManager;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.logging.SessionLog;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.mappings.foundation.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.querykeys.*;
import oracle.toplink.essentials.descriptors.DescriptorEventManager;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.sessions.SessionProfiler;
import oracle.toplink.essentials.sessions.DatabaseRecord;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: Object builder is one of the behaviour class attached to descriptor.
 * It is responsible for building objects, rows, and extracting primary keys from
 * the object and the rows.
 *
 * @author Sati
 * @since TOPLink/Java 1.0
 */
public class ObjectBuilder implements Cloneable, Serializable {
    protected ClassDescriptor descriptor;
    protected Map<String, DatabaseMapping> mappingsByAttribute;
    protected Map<DatabaseField, DatabaseMapping> mappingsByField;
    protected Map<DatabaseField, Vector<DatabaseMapping>> readOnlyMappingsByField;
    protected Vector<DatabaseMapping> primaryKeyMappings;
    protected Vector<Class> primaryKeyClassifications;
    protected transient Vector<DatabaseMapping> nonPrimaryKeyMappings;
    protected transient Expression primaryKeyExpression;

    /** PERF: Cache mapping that use joining. */
    protected Vector<String> joinedAttributes = null;

    /** PERF: Cache mappings that require cloning. */
    protected List<DatabaseMapping> cloningMappings;

    public ObjectBuilder(ClassDescriptor descriptor) {
        this.mappingsByField = new HashMap(20);
        this.readOnlyMappingsByField = new HashMap(20);
        this.mappingsByAttribute = new HashMap(20);
        this.primaryKeyMappings = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(5);
        this.nonPrimaryKeyMappings = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(10);
        this.cloningMappings = new ArrayList(10);
        this.descriptor = descriptor;
    }

    /**
     * Create a new row/record for the object builder.
     * This allows subclasses to define different record types.
     */
    public AbstractRecord createRecord() {
        return new DatabaseRecord();
    }

    /**
     * Create a new row/record for the object builder.
     * This allows subclasses to define different record types.
     */
    public AbstractRecord createRecord(int size) {
        return new DatabaseRecord(size);
    }

    /**
     * Add the primary key and its value to the databaseRow for all the non default tables.
     * This method is used while writing into the multiple tables.
     */
    public void addPrimaryKeyForNonDefaultTable(AbstractRecord databaseRow) {
        // this method has been revised so it calls addPrimaryKeyForNonDefaultTable(DatabaseRow, Object, Session) is similar.
        // the session and object are null in this case.
        addPrimaryKeyForNonDefaultTable(databaseRow, null, null);
    }

    /**
     * Add the primary key and its value to the databaseRow for all the non default tables.
     * This method is used while writing into the multiple tables.
     */
    public void addPrimaryKeyForNonDefaultTable(AbstractRecord databaseRow, Object object, AbstractSession session) {
        if (!getDescriptor().hasMultipleTables()) {
            return;
        }
        Enumeration tablesEnum = getDescriptor().getTables().elements();

        // Skip first table.
        tablesEnum.nextElement();
        while (tablesEnum.hasMoreElements()) {
            DatabaseTable table = (DatabaseTable)tablesEnum.nextElement();
            Map keyMapping = (Map)getDescriptor().getAdditionalTablePrimaryKeyFields().get(table);

            // Loop over the additionalTablePK fields and add the PK info for the table. The join might
            // be between a fk in the source table and pk in secondary table.
            if (keyMapping != null) {
                Iterator primaryKeyFieldEnum = keyMapping.keySet().iterator();
                Iterator secondaryKeyFieldEnum = keyMapping.values().iterator();
                while (primaryKeyFieldEnum.hasNext()) {
                    DatabaseField primaryKeyField = (DatabaseField)primaryKeyFieldEnum.next();
                    DatabaseField secondaryKeyField = (DatabaseField)secondaryKeyFieldEnum.next();
                    Object primaryValue = databaseRow.get(primaryKeyField);

                    // normally the primary key has a value, however if the multiple tables were joined by a foreign
                    // key the foreign key has a value.
                    if ((primaryValue == null) && (!databaseRow.containsKey(primaryKeyField))) {
                        if (object != null) {
                            DatabaseMapping mapping = getMappingForField(secondaryKeyField);
                            if (mapping == null) {
                                throw DescriptorException.missingMappingForField(secondaryKeyField, getDescriptor());
                            }
                            mapping.writeFromObjectIntoRow(object, databaseRow, session);
                        }
                        databaseRow.put(primaryKeyField, databaseRow.get(secondaryKeyField));
                    } else {
                        databaseRow.put(secondaryKeyField, primaryValue);
                    }
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Assign returned row to object
     */
    public void assignReturnRow(Object object, AbstractSession writeSession, AbstractRecord row) throws DatabaseException {
        writeSession.log(SessionLog.FINEST, SessionLog.QUERY, "assign_return_row", row);

        // Require a query context to read into an object.
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSession(writeSession);

        // To avoid processing the same mapping twice,
        // maintain Collection of mappings already used.
        HashSet handledMappings = new HashSet(row.size());
        for (int index = 0; index < row.size(); index++) {
            DatabaseField field = (DatabaseField)row.getFields().elementAt(index);
            assignReturnValueForField(object, query, row, field, handledMappings);
        }
    }

    /**
     * INTERNAL:
     * Assign values from objectRow to the object through all the mappings corresponding to the field.
     */
    public void assignReturnValueForField(Object object, ReadObjectQuery query, AbstractRecord row, DatabaseField field, Collection handledMappings) {
        DatabaseMapping mapping = getMappingForField(field);
        if (mapping != null) {
            assignReturnValueToMapping(object, query, row, field, mapping, handledMappings);
        }
        Vector mappingVector = getReadOnlyMappingsForField(field);
        if (mappingVector != null) {
            for (int j = 0; j < mappingVector.size(); j++) {
                mapping = (DatabaseMapping)mappingVector.elementAt(j);
                assignReturnValueToMapping(object, query, row, field, mapping, handledMappings);
            }
        }
    }

    /**
     * INTERNAL:
     * Assign values from objectRow to the object through the mapping.
     */
    protected void assignReturnValueToMapping(Object object, ReadObjectQuery query, AbstractRecord row, DatabaseField field, DatabaseMapping mapping, Collection handledMappings) {
        if (handledMappings.contains(mapping)) {
            return;
        }
        Object attributeValue;
        if (mapping.isAggregateObjectMapping()) {
            attributeValue = ((AggregateObjectMapping)mapping).readFromReturnRowIntoObject(row, object, query, handledMappings);
        } else if (mapping.isDirectToFieldMapping()) {
            attributeValue = mapping.readFromRowIntoObject(row, null, object, query);
        } else {
            query.getSession().log(SessionLog.FINEST, SessionLog.QUERY, "field_for_unsupported_mapping_returned", field, getDescriptor());
        }
    }

    /**
     * INTERNAL:
     * Update the object primary key by fetching a new sequence number from the accessor.
     * This assume the uses sequence numbers check has already been done.
     * @return the sequence value or null if not assigned.
     * @exception DatabaseException - an error has occurred on the database.
     */
    public Object assignSequenceNumber(Object object, AbstractSession writeSession) throws DatabaseException {
        DatabaseField sequenceNumberField = getDescriptor().getSequenceNumberField();
        Object existingValue = getBaseValueForField(sequenceNumberField, object);

        //** sequencing refactoring
        if (existingValue != null) {
            if (!writeSession.getSequencing().shouldOverrideExistingValue(object.getClass(), existingValue)) {
                return null;
            }
        }
        Object sequenceValue = writeSession.getSequencing().getNextValue(object.getClass());

        //CR#2272
        writeSession.log(SessionLog.FINEST, SessionLog.SEQUENCING, "assign_sequence", sequenceValue, object);

        // Check that the value is not null, this occurs on Sybase identity only **
        if (sequenceValue == null) {
            return null;
        }

        // Now add the value to the object, this gets ugly.
        AbstractRecord tempRow = createRecord(1);
        tempRow.put(sequenceNumberField, sequenceValue);

        // Require a query context to read into an object.
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSession(writeSession);
        DatabaseMapping mapping = getBaseMappingForField(sequenceNumberField);
        Object sequenceIntoObject = getParentObjectForField(sequenceNumberField, object);

        // the following method will return the converted value for the sequence
        Object convertedSequenceValue = mapping.readFromRowIntoObject(tempRow, null, sequenceIntoObject, query);

        return convertedSequenceValue;
    }

    /**
     * Each mapping is recursed to assign values from the databaseRow to the attributes in the domain object.
     */
    public void buildAttributesIntoObject(Object domainObject, AbstractRecord databaseRow, ObjectBuildingQuery query, JoinedAttributeManager joinManager, boolean forRefresh) throws DatabaseException {
        AbstractSession executionSession = query.getSession().getExecutionSession(query);

        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();

        // PERF: Cache if all mappings should be read.
        boolean readAllMappings = query.shouldReadAllMappings();
        int mappingsSize = mappings.size();
        for (int index = 0; index < mappingsSize; index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            if (readAllMappings || query.shouldReadMapping(mapping)) {
                mapping.readFromRowIntoObject(databaseRow, joinManager, domainObject, query, executionSession);
            }
        }

        // PERF: Avoid events if no listeners.
        if (getDescriptor().getEventManager().hasAnyEventListeners()) {
            // Need to run post build or refresh selector, currently check with the query for this,
            // I'm not sure which should be called it case of refresh building a new object, currently refresh is used...
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(domainObject);
            event.setQuery(query);
            event.setSession(query.getSession());
            event.setRecord(databaseRow);
            if (forRefresh) {
                //this method can be called from different places within TopLink. We may be
                //executing refresh query but building the object not refreshing so we must
                //throw the appropriate event.
                //bug 3325315
                event.setEventCode(DescriptorEventManager.PostRefreshEvent);
            } else {
                event.setEventCode(DescriptorEventManager.PostBuildEvent);
            }
            getDescriptor().getEventManager().executeEvent(event);
        }
    }

    /**
     * Returns the clone of the specified object. This is called only from unit of work.
     * The clonedDomainObject sent as parameter is always a working copy from the unit of work.
     */
    public Object buildBackupClone(Object clone, UnitOfWorkImpl unitOfWork) {
        // The copy policy builds clone
        Object backup = getDescriptor().getCopyPolicy().buildClone(clone, unitOfWork);

        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        List mappings = getCloningMappings();
        for (int index = 0; index < mappings.size(); index++) {
            ((DatabaseMapping)mappings.get(index)).buildBackupClone(clone, backup, unitOfWork);
        }

        return backup;
    }

    /**
     * Build and return the expression to use as the where clause to delete an object.
     * The row is passed to allow the version number to be extracted from it.
     */
    public Expression buildDeleteExpression(DatabaseTable table, AbstractRecord row) {
        if (getDescriptor().usesOptimisticLocking() && (getDescriptor().getTables().firstElement().equals(table))) {
            return getDescriptor().getOptimisticLockingPolicy().buildDeleteExpression(table, primaryKeyExpression, row);
        } else {
            return buildPrimaryKeyExpression(table);
        }
    }

    /**
     * Return a new instance of the receiver's javaClass.
     */
    public Object buildNewInstance() {
        return getDescriptor().getInstantiationPolicy().buildNewInstance();
    }

    /**
     * Return an instance of the recievers javaClass. Set the attributes of an instance
     * from the values stored in the database row.
     */
    public Object buildObject(ObjectBuildingQuery query, AbstractRecord databaseRow, JoinedAttributeManager joinManager) throws DatabaseException, QueryException {
        // Profile object building.
        AbstractSession session = query.getSession();
        session.startOperationProfile(SessionProfiler.OBJECT_BUILDING);

        Vector primaryKey = extractPrimaryKeyFromRow(databaseRow, session);

        // Check for null primary key, this is not allowed.
        if ((primaryKey == null) && (!query.hasPartialAttributeExpressions()) && (!getDescriptor().isAggregateCollectionDescriptor())) {
            // Profile object building.
            session.endOperationProfile(SessionProfiler.OBJECT_BUILDING);

            //BUG 3168689: EJBQL: "Select Distinct s.customer from SpouseBean s"
            //BUG 3168699: EJBQL: "Select s.customer from SpouseBean s where s.id = '6'"
            //If we return either a single null, or a Collection containing at least
            //one null, then we want the nulls returned/included if the indicated
            //property is set in the query. (As opposed to throwing an Exception).
            if (query.getProperty("return null if primary key is null") != null) {
                return null;
            } else {
                throw QueryException.nullPrimaryKeyInBuildingObject(query, databaseRow);
            }
        }
        ClassDescriptor concreteDescriptor = getDescriptor();
        if (concreteDescriptor.hasInheritance() && concreteDescriptor.getInheritancePolicy().shouldReadSubclasses()) {
            Class classValue = concreteDescriptor.getInheritancePolicy().classFromRow(databaseRow, session);
            concreteDescriptor = session.getDescriptor(classValue);
            if ((concreteDescriptor == null) && query.hasPartialAttributeExpressions()) {
                concreteDescriptor = getDescriptor();
            }
            if (concreteDescriptor == null) {
                // Profile object building.
                session.endOperationProfile(SessionProfiler.OBJECT_BUILDING);
                throw QueryException.noDescriptorForClassFromInheritancePolicy(query, classValue);
            }
        }
        Object domainObject = null;
        try {
            if (session.isUnitOfWork()) {
                // Do not wrap yet if in UnitOfWork, as there is still much more
                // processing ahead.
                domainObject = buildObjectInUnitOfWork(query, joinManager, databaseRow, (UnitOfWorkImpl)session, primaryKey, concreteDescriptor);
            } else {
                domainObject = buildObject(query, databaseRow, session, primaryKey, concreteDescriptor, joinManager);

                // wrap the object if the query requires it.
                if (query.shouldUseWrapperPolicy()) {
                    domainObject = concreteDescriptor.getObjectBuilder().wrapObject(domainObject, session);
                }
            }
        } finally {
            session.endOperationProfile(SessionProfiler.OBJECT_BUILDING);
        }
        return domainObject;
    }

    /**
     * For executing all reads on the UnitOfWork, the session when building
     * objects from rows will now be the UnitOfWork. Usefull if the rows were
     * read via a dirty write connection and we want to avoid putting uncommitted
     * data in the global cache.
     * <p>
     * Decides whether to call either buildWorkingCopyCloneFromRow (bypassing
     * shared cache) or buildWorkingCopyCloneNormally (placing the result in the
     * shared cache).
     */
    protected Object buildObjectInUnitOfWork(ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Vector primaryKey, ClassDescriptor concreteDescriptor) throws DatabaseException, QueryException {
        // When in transaction we are reading via the write connection
        // and so do not want to corrupt the shared cache with dirty objects.
        // Hence we build and refresh clones directly from the database row.
        if ((unitOfWork.getCommitManager().isActive() || unitOfWork.wasTransactionBegunPrematurely()) && !unitOfWork.isClassReadOnly(concreteDescriptor.getJavaClass())) {
            // It is easier to switch once to the correct builder here.
            return concreteDescriptor.getObjectBuilder().buildWorkingCopyCloneFromRow(query, joinManager, databaseRow, unitOfWork, primaryKey);
        }

        return buildWorkingCopyCloneNormally(query, databaseRow, unitOfWork, primaryKey, concreteDescriptor, joinManager);
    }

    /**
     * buildWorkingCopyCloneFromRow is an alternative to this which is the
     * normal behavior.
     * A row is read from the database, an original is built/refreshed/returned
     * from the shared cache, and the original is registered/conformed/reverted
     * in the UnitOfWork.
     * <p>
     * This default behavior is only safe when the query is executed on a read
     * connection, otherwise uncommitted data might get loaded into the shared
     * cache.
     * <p>
     * Represents the way TopLink has always worked.
     */
    protected Object buildWorkingCopyCloneNormally(ObjectBuildingQuery query, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Vector primaryKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException {
        // This is normal case when we are not in transaction.
        // Pass the query off to the parent. Let it build the object and
        // cache it normally, then register/refresh it.
        AbstractSession session = unitOfWork.getParentIdentityMapSession(query);
        Object original = null;
        Object clone = null;

        // forwarding queries to different sessions is now as simple as setting
        // the session on the query.
        query.setSession(session);
        if (session.isUnitOfWork()) {
            original = buildObjectInUnitOfWork(query, joinManager, databaseRow, (UnitOfWorkImpl)session, primaryKey, concreteDescriptor);
        } else {
            original = buildObject(query, databaseRow, session, primaryKey, concreteDescriptor, joinManager);
        }
        query.setSession(unitOfWork);
        //GFBug#404 Pass in joinManager or not based on if shouldCascadeCloneToJoinedRelationship is set to true
        if (unitOfWork.shouldCascadeCloneToJoinedRelationship()) {
            clone = query.registerIndividualResult(original, unitOfWork, false, joinManager);// false == no longer building directly from rows.
        } else {
            clone = query.registerIndividualResult(original, unitOfWork, false, null);// false == no longer building directly from rows.
        }
        return clone;
    }

    /**
     * Return an instance of the recievers javaClass. Set the attributes of an instance
     * from the values stored in the database row.
     */
    protected Object buildObject(ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, Vector primaryKey, ClassDescriptor concreteDescriptor, JoinedAttributeManager joinManager) throws DatabaseException, QueryException {
        Object domainObject = null;

        //cache key is used for object locking
        CacheKey cacheKey = null;
        try {
            // Check if the objects exists in the identity map.
            if (query.shouldMaintainCache()) {
                //lock the object in the IM
                // PERF: Only use deferred locking if required.
                // CR#3876308 If joining is used, deferred locks are still required.
                if (DeferredLockManager.SHOULD_USE_DEFERRED_LOCKS && (concreteDescriptor.shouldAcquireCascadedLocks() || joinManager.hasJoinedAttributes())) {
                    cacheKey = session.getIdentityMapAccessorInstance().acquireDeferredLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor);
                    domainObject = cacheKey.getObject();

                    int counter = 0;
                    while ((domainObject == null) && (counter < 1000)) {
                        if (cacheKey.getMutex().getActiveThread() == Thread.currentThread()) {
                            break;
                        }
                        //must release lock here to prevent acquiring multiple deferred locks but only
                        //releasing one at the end of the build object call.
                        //BUG 5156075
                        cacheKey.releaseDeferredLock();

                        //sleep and try again if we arenot the owner of the lock for CR 2317
                        // prevents us from modifying a cache key that another thread has locked.
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException exception) {
                        }
                        cacheKey = session.getIdentityMapAccessorInstance().acquireDeferredLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor);
                        domainObject = cacheKey.getObject();
                        counter++;
                    }
                    if (counter == 1000) {
                        throw ConcurrencyException.maxTriesLockOnBuildObjectExceded(cacheKey.getMutex().getActiveThread(), Thread.currentThread());
                    }
                } else {
                    cacheKey = session.getIdentityMapAccessorInstance().acquireLock(primaryKey, concreteDescriptor.getJavaClass(), concreteDescriptor);
                    domainObject = cacheKey.getObject();
                }
            }

            if (domainObject == null) {
                if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) {
                    domainObject = ((ReadObjectQuery)query).getSelectionObject();
                } else {
                    domainObject = concreteDescriptor.getObjectBuilder().buildNewInstance();
                }

                // The object must be registered before building its attributes to resolve circular dependancies.
                if (query.shouldMaintainCache()) {
                    cacheKey.setObject(domainObject);

                    copyQueryInfoToCacheKey(cacheKey, query, databaseRow, session, concreteDescriptor);

                    //set the fetch group to the domain object
                    if (concreteDescriptor.hasFetchGroupManager()) {
                        concreteDescriptor.getFetchGroupManager().setObjectFetchGroup(domainObject, query.getFetchGroup());
                    }
                }

                concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(domainObject, databaseRow, query, joinManager, false);
            } else {
                if (query.isReadObjectQuery() && ((ReadObjectQuery)query).shouldLoadResultIntoSelectionObject()) {
                    copyInto(domainObject, ((ReadObjectQuery)query).getSelectionObject());
                    domainObject = ((ReadObjectQuery)query).getSelectionObject();
                }

                //check if the cached object has been invalidated
                boolean isInvalidated = concreteDescriptor.getCacheInvalidationPolicy().isInvalidated(cacheKey, query.getExecutionTime());

                //CR #4365 - Queryid comparison used to prevent infinit recursion on refresh object cascade all
                //if the concurrency manager is locked by the merge process then no refresh is required.
                // bug # 3388383 If this thread does not have the active lock then someone is building the object so in order to maintain data integrity this thread will not
                // fight to overwrite the object ( this also will avoid potential deadlock situations
                if ((cacheKey.getMutex().getActiveThread() == Thread.currentThread()) && ((query.shouldRefreshIdentityMapResult() || concreteDescriptor.shouldAlwaysRefreshCache() || isInvalidated) && ((cacheKey.getLastUpdatedQueryId() != query.getQueryId()) && !cacheKey.getMutex().isLockedByMergeManager()))) {
                    //cached object might be partially fetched, only refresh the fetch group attributes of the query if
                    //the cached partial object is not invalidated and does not contain all data for the fetch group.
                    if (concreteDescriptor.hasFetchGroupManager() && concreteDescriptor.getFetchGroupManager().isPartialObject(domainObject)) {
                        //only ObjectLevelReadQuery and above support partial objects
                        revertFetchGroupData(domainObject, concreteDescriptor, cacheKey, ((ObjectLevelReadQuery)query), joinManager, databaseRow, session);
                    } else {
                        boolean refreshRequired = true;
                        if (concreteDescriptor.usesOptimisticLocking()) {
                            OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy();
                            Object cacheValue = policy.getValueToPutInCache(databaseRow, session);
                            if (concreteDescriptor.shouldOnlyRefreshCacheIfNewerVersion()) {
                                refreshRequired = policy.isNewerVersion(databaseRow, domainObject, primaryKey, session);
                                if (!refreshRequired) {
                                    cacheKey.setReadTime(query.getExecutionTime());
                                }
                            }
                            if (refreshRequired) {
                                //update the wriet lock value
                                cacheKey.setWriteLockValue(cacheValue);
                            }
                        }
                        if (refreshRequired) {
                            //CR #4365 - used to prevent infinit recursion on refresh object cascade all
                            cacheKey.setLastUpdatedQueryId(query.getQueryId());
                            concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(domainObject, databaseRow, query, joinManager, true);
                            cacheKey.setReadTime(query.getExecutionTime());
                        }
                    }
                } else if (concreteDescriptor.hasFetchGroupManager() && (concreteDescriptor.getFetchGroupManager().isPartialObject(domainObject) && (!concreteDescriptor.getFetchGroupManager().isObjectValidForFetchGroup(domainObject, query.getFetchGroup())))) {
                    //the fetched object is not sufficient for the fetch group of the query
                    //refresh attributes of the query's fetch group
                    concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(domainObject, databaseRow, query, joinManager, false);
                    concreteDescriptor.getFetchGroupManager().unionFetchGroupIntoObject(domainObject, query.getFetchGroup());
                }
                // 3655915: a query with join/batch'ing that gets a cache hit
                // may require some attributes' valueholders to be re-built.
                else if (joinManager.hasJoinedAttributeExpressions()) {
                    for (Iterator e = joinManager.getJoinedAttributeExpressions().iterator();
                             e.hasNext();) {
                        QueryKeyExpression qke = (QueryKeyExpression)e.next();

                        // only worry about immediate attributes
                        if (qke.getBaseExpression().isExpressionBuilder()) {
                            DatabaseMapping dm = getMappingForAttributeName(qke.getName());

                            if (dm == null) {
                                throw ValidationException.missingMappingForAttribute(concreteDescriptor, qke.getName(), this.toString());
                            } else {
                                // Bug 4230655 - do not replace instantiated valueholders
                                Object attributeValue = dm.getAttributeValueFromObject(domainObject);
                                if (!((attributeValue != null) && dm.isForeignReferenceMapping() && ((ForeignReferenceMapping)dm).usesIndirection() && ((ForeignReferenceMapping)dm).getIndirectionPolicy().objectIsInstantiated(attributeValue))) {
                                    dm.readFromRowIntoObject(databaseRow, joinManager, domainObject, query, query.getSession().getExecutionSession(query));
                                }
                            }
                        }
                    }
                }
            }
        } finally {
            if (query.shouldMaintainCache() && (cacheKey != null)) {
                // bug 2681401:
                // in case of exception (for instance, thrown by buildNewInstance())
                // cacheKey.getObject() may be null.
                if (cacheKey.getObject() != null) {
                    cacheKey.updateAccess();
                }

                // PERF: Only use deferred locking if required.
                if (DeferredLockManager.SHOULD_USE_DEFERRED_LOCKS && (concreteDescriptor.shouldAcquireCascadedLocks() || joinManager.hasJoinedAttributes())) {
                    cacheKey.releaseDeferredLock();
                } else {
                    cacheKey.release();
                }
            }
        }

        return domainObject;
    }

    /**
     * Clean up the cached object data and only revert the fetch group data back to the cached object.
     */
    private void revertFetchGroupData(Object domainObject, ClassDescriptor concreteDescriptor, CacheKey cacheKey, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, AbstractSession session) {
        //the cached object is either invalidated, or staled as the version is newer, or a refresh is explicitly set on the query.
        //clean all data of the cache object.
        concreteDescriptor.getFetchGroupManager().reset(domainObject);
        //read in the fetch group data only
        concreteDescriptor.getObjectBuilder().buildAttributesIntoObject(domainObject, databaseRow, query, joinManager, false);
        //set fetch group refrence to the cached object
        concreteDescriptor.getFetchGroupManager().setObjectFetchGroup(domainObject, query.getFetchGroup());
        //set refresh on fetch group
        concreteDescriptor.getFetchGroupManager().setRefreshOnFetchGroupToObject(domainObject, (query.shouldRefreshIdentityMapResult() || concreteDescriptor.shouldAlwaysRefreshCache()));
        //set query id to prevent infinite recursion on refresh object cascade all
        cacheKey.setLastUpdatedQueryId(query.getQueryId());
        //register the object into the IM and set the write lock object if applied.
        if (concreteDescriptor.usesOptimisticLocking()) {
            OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy();
            cacheKey.setWriteLockValue(policy.getValueToPutInCache(databaseRow, session));
        }
        cacheKey.setReadTime(query.getExecutionTime());
        //validate the cached object
        cacheKey.setInvalidationState(CacheKey.CHECK_INVALIDATION_POLICY);
    }

    /**
     * Return a container which contains the instances of the receivers javaClass.
     * Set the fields of the instance to the values stored in the database rows.
     */
    public Object buildObjectsInto(ReadAllQuery query, Vector databaseRows, Object domainObjects) throws DatabaseException {
        Set identitySet = null;
        for (Enumeration rowsEnum = databaseRows.elements(); rowsEnum.hasMoreElements();) {
            AbstractRecord databaseRow = (AbstractRecord)rowsEnum.nextElement();

            // Skip null rows from 1-m joining duplicate row filtering.
            if (databaseRow != null) {
                Object domainObject = buildObject(query, databaseRow, query.getJoinedAttributeManager());

                // Avoid duplicates if -m joining was used and a cache hit occured.
                if (query.getJoinedAttributeManager().isToManyJoin()) {
                    if (identitySet == null) {
                        identitySet = new TopLinkIdentityHashSet(databaseRows.size());
                    }
                    if (!identitySet.contains(domainObject)) {
                        identitySet.add(domainObject);
                        query.getContainerPolicy().addInto(domainObject, domainObjects, query.getSession());
                    }
                } else {
                    query.getContainerPolicy().addInto(domainObject, domainObjects, query.getSession());
                }
            }
        }

        return domainObjects;
    }

    /**
     * Build the primary key expression for the secondary table.
     */
    public Expression buildPrimaryKeyExpression(DatabaseTable table) throws DescriptorException {
        if (getDescriptor().getTables().firstElement().equals(table)) {
            return getPrimaryKeyExpression();
        }

        Map keyMapping = (Map)getDescriptor().getAdditionalTablePrimaryKeyFields().get(table);
        if (keyMapping == null) {
            throw DescriptorException.multipleTablePrimaryKeyNotSpecified(getDescriptor());
        }

        ExpressionBuilder builder = new ExpressionBuilder();
        Expression expression = null;
        for (Iterator primaryKeyEnum = keyMapping.values().iterator(); primaryKeyEnum.hasNext();) {
            DatabaseField field = (DatabaseField)primaryKeyEnum.next();
            expression = (builder.getField(field).equal(builder.getParameter(field))).and(expression);
        }

        return expression;
    }

    /**
     * Build the primary key expression from the specified primary key values.
     */
    public Expression buildPrimaryKeyExpressionFromKeys(Vector primaryKeyValues, AbstractSession session) {
        Expression expression = null;
        Expression subExpression;
        Expression builder = new ExpressionBuilder();
        List primaryKeyFields = getDescriptor().getPrimaryKeyFields();

        for (int index = 0; index < primaryKeyFields.size(); index++) {
            Object value = primaryKeyValues.get(index);
            DatabaseField field = (DatabaseField)primaryKeyFields.get(index);
            if (value != null) {
                subExpression = builder.getField(field).equal(value);
                expression = subExpression.and(expression);
            }
        }

        return expression;
    }

    /**
     * Build the primary key expression from the specified domain object.
     */
    public Expression buildPrimaryKeyExpressionFromObject(Object domainObject, AbstractSession session) {
        return buildPrimaryKeyExpressionFromKeys(extractPrimaryKeyFromObject(domainObject, session), session);
    }

    /**
     * Build the row representation of an object.
     */
    public AbstractRecord buildRow(Object object, AbstractSession session) {
        return buildRow(createRecord(), object, session);
    }

    /**
     * Build the row representation of an object.
     */
    public AbstractRecord buildRow(AbstractRecord databaseRow, Object object, AbstractSession session) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        int mappingsSize = mappings.size();
        for (int index = 0; index < mappingsSize; index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            mapping.writeFromObjectIntoRow(object, databaseRow, session);
        }

        // If this descriptor is involved in inheritence add the class type.
        if (getDescriptor().hasInheritance()) {
            getDescriptor().getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow);
        }

        // If this descriptor has multiple tables then we need to append the primary keys for
        // the non default tables.
        if (!getDescriptor().isAggregateDescriptor()) {
            addPrimaryKeyForNonDefaultTable(databaseRow);
        }

        return databaseRow;
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for non-relationship fields.
     */
    public AbstractRecord buildRowForShallowDelete(Object object, AbstractSession session) {
        return buildRowForShallowDelete(createRecord(), object, session);
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for non-relationship fields.
     */
    public AbstractRecord buildRowForShallowDelete(AbstractRecord databaseRow, Object object, AbstractSession session) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        int mappingsSize = mappings.size();
        for (int index = 0; index < mappingsSize; index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            mapping.writeFromObjectIntoRowForShallowDelete(object, databaseRow, session);
        }
        return databaseRow;
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildRowForShallowInsert(Object object, AbstractSession session) {
        return buildRowForShallowInsert(createRecord(), object, session);
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildRowForShallowInsert(AbstractRecord databaseRow, Object object, AbstractSession session) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        int mappingsSize = mappings.size();
        for (int index = 0; index < mappingsSize; index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            mapping.writeFromObjectIntoRowForShallowInsert(object, databaseRow, session);
        }

        // If this descriptor is involved in inheritence add the class type.
        if (getDescriptor().hasInheritance()) {
            getDescriptor().getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow);
        }

        // If this descriptor has multiple tables then we need to append the primary keys for
        // the non default tables.
        if (!getDescriptor().isAggregateDescriptor()) {
            addPrimaryKeyForNonDefaultTable(databaseRow);
        }

        return databaseRow;
    }

    /**
     * Build the row representation of an object.
     */
    public AbstractRecord buildRowWithChangeSet(ObjectChangeSet objectChangeSet, AbstractSession session) {
        return buildRowWithChangeSet(createRecord(), objectChangeSet, session);
    }

    /**
     * Build the row representation of an object.
     */
    public AbstractRecord buildRowWithChangeSet(AbstractRecord databaseRow, ObjectChangeSet objectChangeSet, AbstractSession session) {
        for (Enumeration changeRecords = objectChangeSet.getChanges().elements();
                 changeRecords.hasMoreElements();) {
            ChangeRecord changeRecord = (ChangeRecord)changeRecords.nextElement();
            DatabaseMapping mapping = changeRecord.getMapping();
            mapping.writeFromObjectIntoRowWithChangeRecord(changeRecord, databaseRow, session);
        }

        // If this descriptor is involved in inheritence add the class type.
        if (getDescriptor().hasInheritance()) {
            getDescriptor().getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow);
        }

        return databaseRow;
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildRowForShallowInsertWithChangeSet(ObjectChangeSet objectChangeSet, AbstractSession session) {
        return buildRowForShallowInsertWithChangeSet(createRecord(), objectChangeSet, session);
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildRowForShallowInsertWithChangeSet(AbstractRecord databaseRow, ObjectChangeSet objectChangeSet, AbstractSession session) {
        for (Iterator changeRecords = objectChangeSet.getChanges().iterator();
                 changeRecords.hasNext();) {
            ChangeRecord changeRecord = (ChangeRecord)changeRecords.next();
            DatabaseMapping mapping = changeRecord.getMapping();
            mapping.writeFromObjectIntoRowForShallowInsertWithChangeRecord(changeRecord, databaseRow, session);
        }

        // If this descriptor is involved in inheritence add the class type.
        if (getDescriptor().hasInheritance()) {
            getDescriptor().getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow);
        }

        // If this descriptor has multiple tables then we need to append the primary keys for
        // the non default tables.
        if (!getDescriptor().isAggregateDescriptor()) {
            addPrimaryKeyForNonDefaultTable(databaseRow);
        }

        return databaseRow;
    }

    /**
     * Build the row representation of an object. The row built is used only for translations
     * for the expressions in the expresion framework.
     */
    public AbstractRecord buildRowForTranslation(Object object, AbstractSession session) {
        AbstractRecord databaseRow = createRecord();

        for (Iterator mappings = getPrimaryKeyMappings().iterator(); mappings.hasNext();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.next();
            if (mapping != null) {
                mapping.writeFromObjectIntoRow(object, databaseRow, session);
            }
        }

        // If this descriptor has multiple tables then we need to append the primary keys for
        // the non default tables, this is require for m-m, dc defined in the Builder that prefixes the wrong table name.
        // Ideally the mappings should take part in building the translation row so they can add required values.
        addPrimaryKeyForNonDefaultTable(databaseRow, object, session);

        return databaseRow;
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildRowForUpdate(WriteObjectQuery query) {
        AbstractRecord databaseRow = createRecord();

        for (Iterator mappings = getNonPrimaryKeyMappings().iterator();
                 mappings.hasNext();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.next();
            mapping.writeFromObjectIntoRowForUpdate(query, databaseRow);
        }

        // If this descriptor is involved in inheritence and is an Aggregate, add the class type.
        // Added Nov 8, 2000 Mostly by PWK but also JED
        // Prs 24801
        // Modified Dec 11, 2000 TGW with assitance from PWK
        // Prs 27554
        if (getDescriptor().hasInheritance() && getDescriptor().isAggregateDescriptor()) {
            if (query.getObject() != null) {
                if (query.getBackupClone() == null) {
                    getDescriptor().getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow);
                } else {
                    if (!query.getObject().getClass().equals(query.getBackupClone().getClass())) {
                        getDescriptor().getInheritancePolicy().addClassIndicatorFieldToRow(databaseRow);
                    }
                }
            }
        }

        return databaseRow;
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildRowForUpdateWithChangeSet(WriteObjectQuery query) {
        AbstractRecord databaseRow = createRecord();

        for (Iterator changeRecords = query.getObjectChangeSet().getChanges().iterator();
                 changeRecords.hasNext();) {
            ChangeRecord changeRecord = (ChangeRecord)changeRecords.next();
            DatabaseMapping mapping = changeRecord.getMapping();
            mapping.writeFromObjectIntoRowWithChangeRecord(changeRecord, databaseRow, query.getSession());
        }

        return databaseRow;
    }

    /**
     * Build the row representation of an object.
     */
    public AbstractRecord buildRowForWhereClause(ObjectLevelModifyQuery query) {
        AbstractRecord databaseRow = createRecord();

        for (Iterator mappings = getDescriptor().getMappings().iterator();
                 mappings.hasNext();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.next();
            mapping.writeFromObjectIntoRowForWhereClause(query, databaseRow);
        }

        // If this descriptor has multiple tables then we need to append the primary keys for
        // the non default tables.
        if (!getDescriptor().isAggregateDescriptor()) {
            addPrimaryKeyForNonDefaultTable(databaseRow);
        }

        return databaseRow;
    }

    /**
     * Build the row from the primary key values.
     */
    public AbstractRecord buildRowFromPrimaryKeyValues(Vector key, AbstractSession session) {
        AbstractRecord databaseRow = createRecord(key.size());
        int keySize = key.size();
        for (int index = 0; index < keySize; index++) {
            DatabaseField field = (DatabaseField)getDescriptor().getPrimaryKeyFields().get(index);
            Object value = key.elementAt(index);
            value = session.getPlatform(getDescriptor().getJavaClass()).getConversionManager().convertObject(value, field.getType());
            databaseRow.put(field, value);
        }

        return databaseRow;
    }

    /**
     * Build the row of all of the fields used for insertion.
     */
    public AbstractRecord buildTemplateInsertRow(AbstractSession session) {
        AbstractRecord databaseRow = createRecord();
        buildTemplateInsertRow(session, databaseRow);
        return databaseRow;
    }

    public void buildTemplateInsertRow(AbstractSession session, AbstractRecord databaseRow) {
        for (Iterator mappings = getDescriptor().getMappings().iterator();
                 mappings.hasNext();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.next();
            mapping.writeInsertFieldsIntoRow(databaseRow, session);
        }

        // If this descriptor is involved in inheritence add the class type.
        if (getDescriptor().hasInheritance()) {
            getDescriptor().getInheritancePolicy().addClassIndicatorFieldToInsertRow(databaseRow);
        }

        // If this descriptor has multiple tables then we need to append the primary keys for
        // the non default tables.
        if (!getDescriptor().isAggregateDescriptor()) {
            addPrimaryKeyForNonDefaultTable(databaseRow);
        }

        if (getDescriptor().usesOptimisticLocking()) {
            getDescriptor().getOptimisticLockingPolicy().addLockFieldsToUpdateRow(databaseRow, session);
        }

        //** sequencing refactoring
        if (getDescriptor().usesSequenceNumbers() && session.getSequencing().shouldAcquireValueAfterInsert(getDescriptor().getJavaClass())) {
            databaseRow.remove(getDescriptor().getSequenceNumberField());
        }
    }

    /**
     * Build the row representation of the object for update. The row built does not
     * contain entries for uninstantiated attributes.
     */
    public AbstractRecord buildTemplateUpdateRow(AbstractSession session) {
        AbstractRecord databaseRow = createRecord();

        for (Iterator mappings = getNonPrimaryKeyMappings().iterator();
                 mappings.hasNext();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.next();
            mapping.writeUpdateFieldsIntoRow(databaseRow, session);
        }

        if (getDescriptor().usesOptimisticLocking()) {
            getDescriptor().getOptimisticLockingPolicy().addLockFieldsToUpdateRow(databaseRow, session);
        }

        return databaseRow;
    }

    /**
     * Build and return the expression to use as the where clause to an update object.
     * The row is passed to allow the version number to be extracted from it.
     */
    public Expression buildUpdateExpression(DatabaseTable table, AbstractRecord transactionRow, AbstractRecord modifyRow) {
        // Only the first table must use the lock check.
        Expression primaryKeyExpression = buildPrimaryKeyExpression(table);
        if (getDescriptor().usesOptimisticLocking()) {
            return getDescriptor().getOptimisticLockingPolicy().buildUpdateExpression(table, primaryKeyExpression, transactionRow, modifyRow);
        } else {
            return primaryKeyExpression;
        }
    }

    /**
     * INTERNAL:
     * Build just the primary key mappings into the object.
     */
    public void buildPrimaryKeyAttributesIntoObject(Object original, AbstractRecord databaseRow, ObjectBuildingQuery query) throws DatabaseException, QueryException {
        AbstractSession executionSession = query.getSession().getExecutionSession(query);

        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getPrimaryKeyMappings();
        int mappingsSize = mappings.size();
        for (int i = 0; i < mappingsSize; i++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(i);
            mapping.buildShallowOriginalFromRow(databaseRow, original, query, executionSession);
        }
    }

    /**
     * INTERNAL:
     * For reading through the write connection when in transaction,
     * We need a partially populated original, so that we
     * can build a clone using the copy policy, even though we can't
     * put this original in the shared cache yet; just build a
     * shallow original (i.e. just enough to copy over the primary
     * key and some direct attributes) and keep it on the UOW.
     */
    public void buildAttributesIntoShallowObject(Object original, AbstractRecord databaseRow, ObjectBuildingQuery query) throws DatabaseException, QueryException {
        AbstractSession executionSession = query.getSession().getExecutionSession(query);

        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector pkMappings = getPrimaryKeyMappings();
        int mappingsSize = pkMappings.size();
        for (int i = 0; i < mappingsSize; i++) {
            DatabaseMapping mapping = (DatabaseMapping)pkMappings.get(i);

            //if (query.shouldReadMapping(mapping)) {
            if (!mapping.isDirectToFieldMapping()) {
                mapping.buildShallowOriginalFromRow(databaseRow, original, query, executionSession);
            }
        }
        Vector mappings = getDescriptor().getMappings();
        mappingsSize = mappings.size();
        for (int i = 0; i < mappingsSize; i++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(i);

            //if (query.shouldReadMapping(mapping)) {
            if (mapping.isDirectToFieldMapping()) {
                mapping.buildShallowOriginalFromRow(databaseRow, original, query, executionSession);
            }
        }
    }

    /**
     * INTERNAL:
     * For reading through the write connection when in transaction,
     * populate the clone directly from the database row.
     */
    public void buildAttributesIntoWorkingCopyClone(Object clone, ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, boolean forRefresh) throws DatabaseException, QueryException {
        AbstractSession executionSession = unitOfWork;
        //execution session is UnitOfWork as these objects are being built within
        //the unit of work
        Vector mappings = getDescriptor().getMappings();
        int mappingsSize = mappings.size();
        for (int i = 0; i < mappingsSize; i++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(i);
            if (query.shouldReadMapping(mapping)) {
                mapping.buildCloneFromRow(databaseRow, joinManager, clone, query, unitOfWork, executionSession);
            }
        }

        // PERF: Avoid events if no listeners.
        if (getDescriptor().getEventManager().hasAnyEventListeners()) {
            // Need to run post build or refresh selector, currently check with the query for this,
            // I'm not sure which should be called it case of refresh building a new object, currently refresh is used...
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(clone);
            event.setQuery(query);
            event.setSession(query.getSession());
            event.setRecord(databaseRow);
            if (forRefresh) {
                event.setEventCode(DescriptorEventManager.PostRefreshEvent);
            } else {
                event.setEventCode(DescriptorEventManager.PostBuildEvent);
            }
            getDescriptor().getEventManager().executeEvent(event);
        }
    }

    /**
     * INTERNAL:
     * Builds a working copy clone directly from the database row.
     * This is the key method that allows us to execute queries against a
     * UnitOfWork while in transaction and not cache the results in the shared
     * cache. This is because we might violate transaction isolation by
     * putting uncommitted versions of objects in the shared cache.
     */
    protected Object buildWorkingCopyCloneFromRow(ObjectBuildingQuery query, JoinedAttributeManager joinManager, AbstractRecord databaseRow, UnitOfWorkImpl unitOfWork, Vector primaryKey) throws DatabaseException, QueryException {
        // If the clone already exists then it may only need to be refreshed or returned.
        // We call directly on the identity map to avoid going to the parent,
        // registering if found, and wrapping the result.
        Object workingClone = unitOfWork.getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(primaryKey, getDescriptor().getJavaClass(), getDescriptor());

        // If there is a clone, and it is not a refresh then just return it.
        boolean wasAClone = workingClone != null;
        boolean isARefresh = query.shouldRefreshIdentityMapResult() || (query.isLockQuery() && (!wasAClone || !query.isClonePessimisticLocked(workingClone, unitOfWork)));
        if (wasAClone && (!isARefresh)) {
            return workingClone;
        }

        boolean wasAnOriginal = false;
        Object original = null;

        // If not refreshing can get the object from the cache.
        if (!isARefresh && !unitOfWork.shouldReadFromDB()) {
            CacheKey originalCacheKey = unitOfWork.getParentIdentityMapSession(query).getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, getDescriptor().getJavaClass(), getDescriptor());
            if (originalCacheKey != null) {
                //bug 4772232- acquire readlock on cachekey then release to ensure object is fully built before being returned
                try {
                    originalCacheKey.acquireReadLock();
                    original = originalCacheKey.getObject();
                } finally {
                    originalCacheKey.releaseReadLock();
                }
                wasAnOriginal = original != null;
                // If the original is invalid or always refresh then need to refresh.
                isARefresh = wasAnOriginal && (getDescriptor().shouldAlwaysRefreshCache() || getDescriptor().getCacheInvalidationPolicy().isInvalidated(originalCacheKey, query.getExecutionTime()));
                // Otherwise can just register the cached original object and return it.
                if (wasAnOriginal && (!isARefresh)) {
                    return unitOfWork.cloneAndRegisterObject(original, originalCacheKey);
                }
            }
        }

        CacheKey unitOfWorkCacheKey = null;
        if (!wasAClone) {
            // This code is copied from UnitOfWork.cloneAndRegisterObject. Unlike
            // that method we don't need to lock the shared cache, because
            // are not building off of an original in the shared cache.
            // The copy policy is easier to invoke if we have an original.
            if (wasAnOriginal) {
                workingClone = instantiateWorkingCopyClone(original, unitOfWork);
                // intentionally put nothing in clones to originals, unless really was one.
                unitOfWork.getCloneToOriginals().put(workingClone, original);
            } else {
                // What happens if a copy policy is defined is not pleasant.
                workingClone = instantiateWorkingCopyCloneFromRow(databaseRow, query);
            }

            // This must be registered before it is built to avoid cycles.
            // The version and read is set below in copyQueryInfoToCacheKey.
            unitOfWorkCacheKey = unitOfWork.getIdentityMapAccessorInstance().internalPutInIdentityMap(workingClone, primaryKey, null, 0, getDescriptor());

            // This must be registered before it is built to avoid cycles.
            unitOfWork.getCloneMapping().put(workingClone, workingClone);
        }

        // Since there is no original cache key, we may need all the
        // info for the shared cache key in the UnitOfWork cache key.
        // PERF: Only lookup cache-key if did not just put it there.
        if (unitOfWorkCacheKey == null) {
            unitOfWorkCacheKey = unitOfWork.getIdentityMapAccessorInstance().getCacheKeyForObject(primaryKey, getDescriptor().getJavaClass(), getDescriptor());
        }

        // Must avoid infinite loops while refreshing.
        if (wasAClone && (unitOfWorkCacheKey.getLastUpdatedQueryId() >= query.getQueryId())) {
            return workingClone;
        }
        copyQueryInfoToCacheKey(unitOfWorkCacheKey, query, databaseRow, unitOfWork, getDescriptor());

        // If it was a clone the change listener must be cleared after.
        if (!wasAClone) {
            // The change listener must be set before building the clone as aggregate/collections need the listener.
            descriptor.getObjectChangePolicy().setChangeListener(workingClone, unitOfWork, getDescriptor());
        }

        // Turn it 'off' to prevent unwanted events.
        descriptor.getObjectChangePolicy().dissableEventProcessing(workingClone);
        // Build/refresh the clone from the row.
        buildAttributesIntoWorkingCopyClone(workingClone, query, joinManager, databaseRow, unitOfWork, wasAClone);
        Object backupClone = getDescriptor().getObjectChangePolicy().buildBackupClone(workingClone, this, unitOfWork);

        // If it was a clone the change listener must be cleared.
        if (wasAClone) {
            descriptor.getObjectChangePolicy().clearChanges(workingClone, unitOfWork, getDescriptor());
        }
        descriptor.getObjectChangePolicy().enableEventProcessing(workingClone);
        unitOfWork.getCloneMapping().put(workingClone, backupClone);
        query.recordCloneForPessimisticLocking(workingClone, unitOfWork);

        return workingClone;
    }

    /**
     * Returns clone of itself
     */
    public Object clone() {
        Object object = null;

        try {
            object = super.clone();
        } catch (Exception exception) {
            ;
        }

        // Only the shallow copy is created. The entries never change in these data structures
        ((ObjectBuilder)object).setMappingsByAttribute(new HashMap(getMappingsByAttribute()));
        ((ObjectBuilder)object).setMappingsByField(new HashMap(getMappingsByField()));
        ((ObjectBuilder)object).setReadOnlyMappingsByField(new HashMap(getReadOnlyMappingsByField()));
        ((ObjectBuilder)object).setPrimaryKeyMappings((Vector)getPrimaryKeyMappings().clone());
        ((ObjectBuilder)object).setNonPrimaryKeyMappings((Vector)getNonPrimaryKeyMappings().clone());

        return object;
    }

    /**
     * INTERNAL:
     * THis method is used by the UnitOfWork to cascade registration of new objects.
     * It may rais exceptions as described inthe EJB 3.x specification
     */
    public void cascadePerformRemove(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects) {
        Iterator mappings = getDescriptor().getMappings().iterator();
        while (mappings.hasNext()) {
            ((DatabaseMapping)mappings.next()).cascadePerformRemoveIfRequired(object, uow, visitedObjects);
        }
    }

    /**
     * INTERNAL:
     * THis method is used by the UnitOfWork to cascade registration of new objects.
     * It may rais exceptions as described inthe EJB 3.x specification
     */
    public void cascadeRegisterNewForCreate(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects) {
        Iterator mappings = getDescriptor().getMappings().iterator();
        while (mappings.hasNext()) {
            ((DatabaseMapping)mappings.next()).cascadeRegisterNewIfRequired(object, uow, visitedObjects);
        }
    }

    /**
     * INTERNAL:
     * This method creates an records changes for a particular object
     * @return ChangeRecord
     */
    public ObjectChangeSet compareForChange(Object clone, Object backUp, UnitOfWorkChangeSet changeSet, AbstractSession session) {
        // delegate the change comparision to this objects ObjectChangePolicy - TGW
        return descriptor.getObjectChangePolicy().calculateChanges(clone, backUp, changeSet, session, getDescriptor(), true);
    }

    /**
     * Compares the two specified objects
     */
    public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        for (int index = 0; index < mappings.size(); index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);

            if (!mapping.compareObjects(firstObject, secondObject, session)) {
                Object firstValue = mapping.getAttributeValueFromObject(firstObject);
                Object secondValue = mapping.getAttributeValueFromObject(secondObject);
                session.log(SessionLog.FINEST, SessionLog.QUERY, "compare_failed", mapping, firstValue, secondValue);
                return false;
            }
        }

        return true;
    }

    /**
     * Copy each attribute from one object into the other.
     */
    public void copyInto(Object source, Object target, boolean cloneOneToOneValueHolders) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        for (int index = 0; index < mappings.size(); index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            Object value = null;
            if (cloneOneToOneValueHolders && mapping.isForeignReferenceMapping()){
                value = ((ForeignReferenceMapping)mapping).getAttributeValueWithClonedValueHolders(source);
            } else {
                value = mapping.getAttributeValueFromObject(source);
            }
            mapping.setAttributeValueInObject(target, value);
        }
    }
    
    /**
     * Copy each attribute from one object into the other.
     */
    public void copyInto(Object source, Object target) {
        copyInto(source, target, false);
    }
    

    /**
     * Return a copy of the object.
     * This is NOT used for unit of work but for templatizing an object.
     * The depth and primary key reseting are passed in.
     */
    public Object copyObject(Object original, ObjectCopyingPolicy policy) {
        Object copy = policy.getCopies().get(original);
        if (copy != null) {
            return copy;
        }

        copy = instantiateClone(original, policy.getSession());
        policy.getCopies().put(original, copy);

        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        List mappings = getCloningMappings();
        for (int index = 0; index < mappings.size(); index++) {
            ((DatabaseMapping)mappings.get(index)).buildCopy(copy, original, policy);
        }

        if (policy.shouldResetPrimaryKey() && (!(getDescriptor().isAggregateDescriptor() || getDescriptor().isAggregateCollectionDescriptor()))) {
            // Do not reset if any of the keys is mapped through a 1-1, i.e. back reference id has already changed.
            boolean hasOneToOne = false;
            for (Enumeration keyMappingsEnum = getPrimaryKeyMappings().elements();
                     keyMappingsEnum.hasMoreElements();) {
                if (((DatabaseMapping)keyMappingsEnum.nextElement()).isOneToOneMapping()) {
                    hasOneToOne = true;
                }
            }
            if (!hasOneToOne) {
                for (Enumeration keyMappingsEnum = getPrimaryKeyMappings().elements();
                         keyMappingsEnum.hasMoreElements();) {
                    DatabaseMapping mapping = (DatabaseMapping)keyMappingsEnum.nextElement();

                    // Only null out direct mappings, as others will be nulled in the respective objects.
                    if (mapping.isDirectToFieldMapping()) {
                        Object nullValue = ((AbstractDirectMapping)mapping).getAttributeValue(null, policy.getSession());
                        mapping.setAttributeValueInObject(copy, nullValue);
                    } else if (mapping.isTransformationMapping()) {
                        mapping.setAttributeValueInObject(copy, null);
                    }
                }
            }
        }

        // PERF: Avoid events if no listeners.
        if (getDescriptor().getEventManager().hasAnyEventListeners()) {
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(copy);
            event.setSession(policy.getSession());
            event.setOriginalObject(original);
            event.setEventCode(DescriptorEventManager.PostCloneEvent);
            getDescriptor().getEventManager().executeEvent(event);
        }

        return copy;
    }

    /**
     * INTERNAL:
     * Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object
     * @return oracle.toplink.essentials.internal.sessions.ObjectChangeSet the newly created changeSet representing the clone object
     * @param clone java.lang.Object the object to convert to a changeSet
     * @param uowChangeSet oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet the owner of this changeSet
     */
    public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, AbstractSession session) {
        boolean isNew = ((UnitOfWorkImpl)session).isObjectNew(clone);
        return createObjectChangeSet(clone, uowChangeSet, isNew, session);
    }

    /**
     * INTERNAL:
     * Used by the ObjectBuilder to create an ObjectChangeSet for the specified clone object
     * @return oracle.toplink.essentials.internal.sessions.ObjectChangeSet the newly created changeSet representing the clone object
     * @param clone java.lang.Object the object to convert to a changeSet
     * @param uowChangeSet oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet the owner of this changeSet
     * @param isNew boolean signifies if the clone object is a new object.
     */
    public ObjectChangeSet createObjectChangeSet(Object clone, UnitOfWorkChangeSet uowChangeSet, boolean isNew, AbstractSession session) {
        ObjectChangeSet changes = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(clone);
        if (changes == null) {
            if (getDescriptor().isAggregateDescriptor()) {
                changes = new AggregateObjectChangeSet(new Vector(0), getDescriptor().getJavaClass(), clone, uowChangeSet, isNew);
            } else {
                changes = new ObjectChangeSet(extractPrimaryKeyFromObject(clone, session), getDescriptor().getJavaClass(), clone, uowChangeSet, isNew);
            }
            changes.setIsAggregate(getDescriptor().isAggregateDescriptor() || getDescriptor().isAggregateCollectionDescriptor());
            uowChangeSet.addObjectChangeSetForIdentity(changes, clone);
        }
        return changes;
    }

    /**
     * Creates and stores primary key expression.
     */
    public void createPrimaryKeyExpression(AbstractSession session) {
        Expression expression = null;
        Expression builder = new ExpressionBuilder();
        Expression subExp1;
        Expression subExp2;
        Expression subExpression;
        List primaryKeyFields = getDescriptor().getPrimaryKeyFields();

        for (int index = 0; index < primaryKeyFields.size(); index++) {
            DatabaseField primaryKeyField = (DatabaseField)primaryKeyFields.get(index);
            subExp1 = builder.getField(primaryKeyField);
            subExp2 = builder.getParameter(primaryKeyField);
            subExpression = subExp1.equal(subExp2);

            if (expression == null) {
                expression = subExpression;
            } else {
                expression = expression.and(subExpression);
            }
        }

        setPrimaryKeyExpression(expression);
    }

    /**
     * Return the row with primary keys and their values from the given expression.
     */
    public Vector extractPrimaryKeyFromExpression(boolean requiresExactMatch, Expression expression, AbstractRecord translationRow, AbstractSession session) {
        AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size());

        //put the curent session onthe expression builder for use later store current session incase
        //it is required at a later stage
        AbstractSession oldSession = expression.getBuilder().getSession();
        expression.getBuilder().setSession(session.getRootSession(null));
        // Get all the field & values from expression.
        boolean isValid = expression.extractPrimaryKeyValues(requiresExactMatch, getDescriptor(), primaryKeyRow, translationRow);
        if (requiresExactMatch && (!isValid)) {
            return null;
        }

        // Check that the sizes match.
        if (primaryKeyRow.size() != getDescriptor().getPrimaryKeyFields().size()) {
            return null;
        }

        return extractPrimaryKeyFromRow(primaryKeyRow, session);
    }

    /**
     * Extract primary key attribute values from the domainObject.
     */
    public Vector extractPrimaryKeyFromObject(Object domainObject, AbstractSession session) {
        // Allow for inheritance, the concrete descriptor must always be used.
        if (getDescriptor().hasInheritance() && (domainObject.getClass() != getDescriptor().getJavaClass()) && (!domainObject.getClass().getSuperclass().equals(getDescriptor().getJavaClass()))) {
            return session.getDescriptor(domainObject).getObjectBuilder().extractPrimaryKeyFromObject(domainObject, session);
        } else {
            List primaryKeyFields = getDescriptor().getPrimaryKeyFields();
            Vector primaryKeyValues = new Vector(primaryKeyFields.size());

            // PERF: optimize simple case of direct mapped singleton primary key.
            if (getDescriptor().hasSimplePrimaryKey()) {
                // PERF: use index not enumeration
                for (int index = 0; index < getPrimaryKeyMappings().size(); index++) {
                    AbstractDirectMapping mapping = (AbstractDirectMapping)getPrimaryKeyMappings().get(index);
                    Object keyValue = mapping.valueFromObject(domainObject, (DatabaseField)primaryKeyFields.get(index), session);
                    primaryKeyValues.add(keyValue);
                }
            } else {
                AbstractRecord databaseRow = createRecord(getPrimaryKeyMappings().size());

                // PERF: use index not enumeration
                for (int index = 0; index < getPrimaryKeyMappings().size(); index++) {
                    DatabaseMapping mapping = (DatabaseMapping)getPrimaryKeyMappings().get(index);

                    // Primary key mapping may be null for aggregate collection.
                    if (mapping != null) {
                        mapping.writeFromObjectIntoRow(domainObject, databaseRow, session);
                    }
                }

                // PERF: use index not enumeration
                for (int index = 0; index < primaryKeyFields.size(); index++) {
                    // Ensure that the type extracted from the object is the same type as in the descriptor,
                    // the main reason for this is that 1-1 can optimize on vh by getting from the row as the row-type.
                    Class classification = (Class)getPrimaryKeyClassifications().get(index);
                    Object value = databaseRow.get((DatabaseField)primaryKeyFields.get(index));

                    // CR2114 following line modified; domainObject.getClass() passed as an argument
                    primaryKeyValues.addElement(session.getPlatform(domainObject.getClass()).convertObject(value, classification));
                }
            }

            return primaryKeyValues;
        }
    }

    /**
     * Extract primary key values from the specified row.
     * null is returned if the row does not contain the key.
     */
    public Vector extractPrimaryKeyFromRow(AbstractRecord databaseRow, AbstractSession session) {
        List primaryKeyFields = getDescriptor().getPrimaryKeyFields();
        Vector primaryKeyValues = new Vector(primaryKeyFields.size());

        // PERF: use index not enumeration
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            DatabaseField field = (DatabaseField)primaryKeyFields.get(index);

            // Ensure that the type extracted from the row is the same type as in the object.
            Class classification = (Class)getPrimaryKeyClassifications().get(index);
            Object value = databaseRow.get(field);
            if (value != null) {
                primaryKeyValues.addElement(session.getPlatform(getDescriptor().getJavaClass()).convertObject(value, classification));
            } else {
                return null;
            }
        }

        return primaryKeyValues;
    }

    /**
     * Return the row with primary keys and their values from the given expression.
     */
    public AbstractRecord extractPrimaryKeyRowFromExpression(Expression expression, AbstractRecord translationRow, AbstractSession session) {
        AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size());

        //put the curent session onthe expression builder for use later store current session incase
        //it is required at a later stage
        AbstractSession oldSession = expression.getBuilder().getSession();
        expression.getBuilder().setSession(session.getRootSession(null));
        // Get all the field & values from expression
        boolean isValid = expression.extractPrimaryKeyValues(true, getDescriptor(), primaryKeyRow, translationRow);
        expression.getBuilder().setSession(session.getRootSession(null));
        if (!isValid) {
            return null;
        }

        // Check that the sizes match up
        if (primaryKeyRow.size() != getDescriptor().getPrimaryKeyFields().size()) {
            return null;
        }

        return primaryKeyRow;
    }

    /**
     * Extract primary key attribute values from the domainObject.
     */
    public AbstractRecord extractPrimaryKeyRowFromObject(Object domainObject, AbstractSession session) {
        AbstractRecord databaseRow = createRecord(getPrimaryKeyMappings().size());

        // PERF: use index not enumeration.
        for (int index = 0; index < getPrimaryKeyMappings().size(); index++) {
            ((DatabaseMapping)getPrimaryKeyMappings().get(index)).writeFromObjectIntoRow(domainObject, databaseRow, session);
        }

        // PERF: optimize simple primary key case, no need to remap.
        if (getDescriptor().hasSimplePrimaryKey()) {
            return databaseRow;
        }
        AbstractRecord primaryKeyRow = createRecord(getPrimaryKeyMappings().size());
        List primaryKeyFields = getDescriptor().getPrimaryKeyFields();
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            // Ensure that the type extracted from the object is the same type as in the descriptor,
            // the main reason for this is that 1-1 can optimize on vh by getting from the row as the row-type.
            Class classification = (Class)getPrimaryKeyClassifications().get(index);
            DatabaseField field = (DatabaseField)primaryKeyFields.get(index);
            Object value = databaseRow.get(field);
            primaryKeyRow.put(field, session.getPlatform(domainObject.getClass()).convertObject(value, classification));
        }

        return primaryKeyRow;
    }

    /**
     * Extract the value of the primary key attribute from the specified object.
     */
    public Object extractValueFromObjectForField(Object domainObject, DatabaseField field, AbstractSession session) throws DescriptorException {
        // Allow for inheritance, the concrete descriptor must always be used.
        ClassDescriptor descriptor = null;//this variable will be assigned in the final

        if (getDescriptor().hasInheritance() && (domainObject.getClass() != getDescriptor().getJavaClass()) && ((descriptor = session.getDescriptor(domainObject)).getJavaClass() != getDescriptor().getJavaClass())) {
            return descriptor.getObjectBuilder().extractValueFromObjectForField(domainObject, field, session);
        } else {
            DatabaseMapping mapping = getMappingForField(field);
            if (mapping == null) {
                throw DescriptorException.missingMappingForField(field, getDescriptor());
            }

            return mapping.valueFromObject(domainObject, field, session);
        }
    }

    /**
     * Return the base mapping for the given DatabaseField.
     */
    public DatabaseMapping getBaseMappingForField(DatabaseField databaseField) {
        DatabaseMapping mapping = getMappingForField(databaseField);

        // Drill down through the mappings until we get the direct mapping to the databaseField.
        while (mapping.isAggregateObjectMapping()) {
            mapping = ((AggregateObjectMapping)mapping).getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField);
        }
        return mapping;
    }

    /**
     * Return the base value that is mapped to for given field.
     */
    public Object getBaseValueForField(DatabaseField databaseField, Object domainObject) {
        Object valueIntoObject = domainObject;
        DatabaseMapping mapping = getMappingForField(databaseField);

        // Drill down through the aggregate mappings to get to the direct to field mapping.
        while (mapping.isAggregateObjectMapping()) {
            valueIntoObject = mapping.getAttributeValueFromObject(valueIntoObject);
            mapping = ((AggregateMapping)mapping).getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField);
        }
        return mapping.getAttributeValueFromObject(valueIntoObject);
    }

    /**
     * Return the descriptor
     */
    public ClassDescriptor getDescriptor() {
        return descriptor;
    }

    /**
     * INTERNAL:
     * Return the classifiction for the field contained in the mapping.
     * This is used to convert the row value to a consistent java value.
     */
    public Class getFieldClassification(DatabaseField fieldToClassify) throws DescriptorException {
        DatabaseMapping mapping = getMappingForField(fieldToClassify);
        if (mapping == null) {
            // Means that the mapping is read-only or the classification is unknown,
            // this is normally not an issue as the classification is only really used for primary keys
            // and only when the database type can be different and not polymorphic than the object type.
            return null;
        }

        return mapping.getFieldClassification(fieldToClassify);
    }

    /**
     * Return the field used for the query key name.
     */
    public DatabaseField getFieldForQueryKeyName(String name) {
        QueryKey key = getDescriptor().getQueryKeyNamed(name);
        if (key == null) {
            DatabaseMapping mapping = getMappingForAttributeName(name);
            if (mapping == null) {
                return null;
            }
            if (mapping.getFields().isEmpty()) {
                return null;
            }
            return (DatabaseField)mapping.getFields().firstElement();
        }
        if (key.isDirectQueryKey()) {
            return ((DirectQueryKey)key).getField();
        }
        return null;
    }

    /**
     * PERF:
     * Return all mappings that require cloning.
     * This allows for simple directs to be avoided when using clone copying.
     */
    public List<DatabaseMapping> getCloningMappings() {
        return cloningMappings;
    }

    /**
     * INTERNAL:
     * Answers the attributes which are always joined to the original query on reads.
     */
    public Vector<String> getJoinedAttributes() {
        return joinedAttributes;
    }

    /**
     * INTERNAL:
     * Answers if any attributes are to be joined / returned in the same select
     * statement.
     */
    public boolean hasJoinedAttributes() {
        return (joinedAttributes != null);
    }

    /**
     * Return the mapping for the specified attribute name.
     */
    public DatabaseMapping getMappingForAttributeName(String name) {
        return getMappingsByAttribute().get(name);
    }

    /**
     * Return al the mapping for the specified field.
     */
    public DatabaseMapping getMappingForField(DatabaseField field) {
        return getMappingsByField().get(field);
    }

    /**
     * Return all the read-only mapping for the specified field.
     */
    public Vector<DatabaseMapping> getReadOnlyMappingsForField(DatabaseField field) {
        return getReadOnlyMappingsByField().get(field);
    }

    /**
     * Return all the mapping to attribute associations
     */
    protected Map<String, DatabaseMapping> getMappingsByAttribute() {
        return mappingsByAttribute;
    }

    /**
     * INTERNAL:
     * Return all the mapping to field associations
     */
    public Map<DatabaseField, DatabaseMapping> getMappingsByField() {
        return mappingsByField;
    }

    /**
     * INTERNAL:
     * Return all the read-only mapping to field associations
     */
    public Map<DatabaseField, Vector<DatabaseMapping>> getReadOnlyMappingsByField() {
        return readOnlyMappingsByField;
    }

    /**
     * Return the non primary key mappings.
     */
    protected Vector<DatabaseMapping> getNonPrimaryKeyMappings() {
        return nonPrimaryKeyMappings;
    }

    /**
     * Return the base value that is mapped to for given field.
     */
    public Object getParentObjectForField(DatabaseField databaseField, Object domainObject) {
        Object valueIntoObject = domainObject;
        DatabaseMapping mapping = getMappingForField(databaseField);

        // Drill down through the aggregate mappings to get to the direct to field mapping.
        while (mapping.isAggregateObjectMapping()) {
            valueIntoObject = mapping.getAttributeValueFromObject(valueIntoObject);
            mapping = ((AggregateMapping)mapping).getReferenceDescriptor().getObjectBuilder().getMappingForField(databaseField);
        }
        return valueIntoObject;
    }

    /**
     * Return primary key classifications.
     * These are used to ensure a consistent type for the pk values.
     */
    public Vector<Class> getPrimaryKeyClassifications() {
        if (primaryKeyClassifications == null) {
            List primaryKeyFields = getDescriptor().getPrimaryKeyFields();
            Vector<Class> classifications = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(primaryKeyFields.size());

            for (int index = 0; index < primaryKeyFields.size(); index++) {
                DatabaseMapping mapping = (DatabaseMapping)getPrimaryKeyMappings().get(index);
                DatabaseField field = (DatabaseField)primaryKeyFields.get(index);
                if (mapping != null) {
                    classifications.add(Helper.getObjectClass(mapping.getFieldClassification(field)));
                } else {
                    classifications.add(null);
                }
                primaryKeyClassifications = classifications;
            }
        }
        return primaryKeyClassifications;
    }

    /**
     * Return the primary key expression
     */
    public Expression getPrimaryKeyExpression() {
        return primaryKeyExpression;
    }

    /**
     * Return primary key mappings.
     */
    public Vector<DatabaseMapping> getPrimaryKeyMappings() {
        return primaryKeyMappings;
    }

    /**
     * INTERNAL: return a database field based on a query key name
     */
    public DatabaseField getTargetFieldForQueryKeyName(String queryKeyName) {
        DatabaseMapping mapping = (DatabaseMapping)getMappingForAttributeName(queryKeyName);
        if ((mapping != null) && mapping.isDirectToFieldMapping()) {
            return ((AbstractDirectMapping)mapping).getField();
        }

        //mapping is either null or not direct to field.
        //check query keys
        QueryKey queryKey = getDescriptor().getQueryKeyNamed(queryKeyName);
        if ((queryKey != null) && queryKey.isDirectQueryKey()) {
            return ((DirectQueryKey)queryKey).getField();
        }

        //nothing found
        return null;
    }
    
    /**
     * Cache all the mappings by their attribute and fields.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        this.getMappingsByField().clear();
        this.getReadOnlyMappingsByField().clear();
        this.getMappingsByAttribute().clear();
        this.getCloningMappings().clear();

        for (Enumeration mappings = getDescriptor().getMappings().elements(); mappings.hasMoreElements();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement();

            // Add attribute to mapping association
            if (!mapping.isWriteOnly()) {
                getMappingsByAttribute().put(mapping.getAttributeName(), mapping);
            }
            
            if (mapping.isCloningRequired()) {
                getCloningMappings().add(mapping);
            }

            // Add field to mapping association
            for (Enumeration fields = mapping.getFields().elements(); fields.hasMoreElements(); ) {
                DatabaseField field = DatabaseField.class.cast(fields.nextElement());

                if (mapping.isReadOnly()) {
                    Vector mappingVector = (Vector) getReadOnlyMappingsByField().get(field);
    
                    if (mappingVector == null) {
                        mappingVector = NonSynchronizedVector.newInstance();
                        getReadOnlyMappingsByField().put(field, mappingVector);
                    }
    
                    mappingVector.add(mapping);
                } else {
                    if (mapping.isAggregateObjectMapping()) {
                        // For Embeddable class, we need to test read-only
                        // status of individual fields in the embeddable.
                        ObjectBuilder aggregateObjectBuilder = AggregateObjectMapping.class.cast(mapping).getReferenceDescriptor().getObjectBuilder();
                        
                        // Look in the non-read-only fields mapping
                        DatabaseMapping aggregatedFieldMapping = aggregateObjectBuilder.getMappingForField(field);
    
                        if (aggregatedFieldMapping == null) { // mapping must be read-only
                            Vector mappingVector = (Vector) getReadOnlyMappingsByField().get(field);
                            
                            if (mappingVector == null) {
                                mappingVector = NonSynchronizedVector.newInstance();
                                getReadOnlyMappingsByField().put(field, mappingVector);
                            }
    
                            mappingVector.add(mapping);
                        } else {
                            getMappingsByField().put(field, mapping);
                        }
                    } else { // Not an embeddable mapping
                        if (getMappingsByField().containsKey(field)) {
                            session.getIntegrityChecker().handleError(DescriptorException.multipleWriteMappingsForField(field.toString(), mapping));
                        } else {
                            getMappingsByField().put(field, mapping);
                        }
                    }
                }
            }
        }

        initializePrimaryKey(session);

        initializeJoinedAttributes();
    }
    
    /**
     * INTERNAL:
     * Iterates through all one to one mappings and checks if any of them use joining.
     * <p>
     * By caching the result query execution in the case where there are no joined
     * attributes can be improved.
     */
    public void initializeJoinedAttributes() {
        // For concurrency don't worry about doing this work twice, just make sure
        // if it happens don't add the same joined attributes twice.
        Vector<String> joinedAttributes = null;
        Vector mappings = getDescriptor().getMappings();
        for (int i = 0; i < mappings.size(); i++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(i);
            if (mapping.isOneToOneMapping() && (mapping.isRelationalMapping()) && ((OneToOneMapping)mapping).shouldUseJoining()) {
                if (joinedAttributes == null) {
                    joinedAttributes = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
                }
                joinedAttributes.add(mapping.getAttributeName());
            }
        }
        this.joinedAttributes = joinedAttributes;
    }

    /**
     * Initialize a cache key. Called by buildObject and now also by
     * buildWorkingCopyCloneFromRow.
     */
    protected void copyQueryInfoToCacheKey(CacheKey cacheKey, ObjectBuildingQuery query, AbstractRecord databaseRow, AbstractSession session, ClassDescriptor concreteDescriptor) {
        //CR #4365 - used to prevent infinit recursion on refresh object cascade all
        cacheKey.setLastUpdatedQueryId(query.getQueryId());

        if (concreteDescriptor.usesOptimisticLocking()) {
            OptimisticLockingPolicy policy = concreteDescriptor.getOptimisticLockingPolicy();
            Object cacheValue = policy.getValueToPutInCache(databaseRow, session);

            //register the object into the IM and set the write lock object
            cacheKey.setWriteLockValue(cacheValue);
        }
        cacheKey.setReadTime(query.getExecutionTime());
    }

    /**
     * Cache primary key and non primary key mappings.
     */
    public void initializePrimaryKey(AbstractSession session) throws DescriptorException {
        createPrimaryKeyExpression(session);

        List primaryKeyfields = getDescriptor().getPrimaryKeyFields();

        this.getPrimaryKeyMappings().clear();
        this.getNonPrimaryKeyMappings().clear();

        // This must be before because the scondary table primary key fields are registered after
        for (Iterator fields = getMappingsByField().keySet().iterator(); fields.hasNext();) {
            DatabaseField field = (DatabaseField)fields.next();
            if (!primaryKeyfields.contains(field)) {
                DatabaseMapping mapping = getMappingForField(field);
                if (!getNonPrimaryKeyMappings().contains(mapping)) {
                    getNonPrimaryKeyMappings().addElement(mapping);
                }
            }
        }
        List primaryKeyFields = getDescriptor().getPrimaryKeyFields();
        for (int index = 0; index < primaryKeyFields.size(); index++) {
            DatabaseField primaryKeyField = (DatabaseField)primaryKeyFields.get(index);
            DatabaseMapping mapping = getMappingForField(primaryKeyField);

            if ((mapping == null) && (!getDescriptor().isAggregateDescriptor()) && (!getDescriptor().isAggregateCollectionDescriptor())) {
                throw DescriptorException.noMappingForPrimaryKey(primaryKeyField, getDescriptor());
            }

            getPrimaryKeyMappings().addElement(mapping);
            if (mapping != null) {
                mapping.setIsPrimaryKeyMapping(true);
            }

            // Use the same mapping to map the additional table primary key fields.
            // This is required if someone trys to map to one of these fields.
            if (getDescriptor().hasMultipleTables() && (mapping != null)) {
                for (Map keyMapping : getDescriptor().getAdditionalTablePrimaryKeyFields().values()) {
                    DatabaseField secondaryField = (DatabaseField) keyMapping.get(primaryKeyField);

                    // This can be null in the custom multiple join case
                    if (secondaryField != null) {
                        getMappingsByField().put(secondaryField, mapping);

                        if (mapping.isAggregateObjectMapping()) {
                            // GF#1153,1391
                            // If AggregateObjectMapping contain primary keys and the descriptor has multiple tables
                            // AggregateObjectMapping should know the the primary key join columns (secondaryField here)
                            // to handle some cases properly
                            ((AggregateObjectMapping) mapping).addPrimaryKeyJoinField(primaryKeyField, secondaryField);
                        }
                    }
                }
            }
        }

        // PERF: compute if primary key is mapped through direct mappings,
        // to allow fast extraction.
        boolean hasSimplePrimaryKey = true;
        for (int index = 0; index < getPrimaryKeyMappings().size(); index++) {
            DatabaseMapping mapping = (DatabaseMapping)getPrimaryKeyMappings().get(index);

            // Primary key mapping may be null for aggregate collection.
            if ((mapping == null) || (!mapping.isDirectToFieldMapping())) {
                hasSimplePrimaryKey = false;
                break;
            }
        }
        getDescriptor().setHasSimplePrimaryKey(hasSimplePrimaryKey);
    }

    /**
     * Returns the clone of the specified object. This is called only from unit of work.
     * This only instatiates the clone instance, it does not clone the attributes,
     * this allows the stub of the clone to be registered before cloning its parts.
     */
    public Object instantiateClone(Object domainObject, AbstractSession session) {
        return getDescriptor().getCopyPolicy().buildClone(domainObject, session);
    }

    /**
     * Returns the clone of the specified object. This is called only from unit of work.
     * The domainObject sent as parameter is always a copy from the parent of unit of work.
     * bug 2612602 make a call to build a working clone. This will in turn call the copy policy
     * to make a working clone. This allows for lighter and heavier clones to
     * be created based on their use.
     * this allows the stub of the clone to be registered before cloning its parts.
     */
    public Object instantiateWorkingCopyClone(Object domainObject, AbstractSession session) {
        return getDescriptor().getCopyPolicy().buildWorkingCopyClone(domainObject, session);
    }

    /**
     * It is now possible to build working copy clones directly from rows.
     * <p>An intermediary original is no longer needed.
     * <p>This has ramifications to the copy policy and cmp, for clones are
     * no longer built via cloning.
     * <p>Instead the copy policy must in some cases not copy at all.
     * this allows the stub of the clone to be registered before cloning its parts.
     */
    public Object instantiateWorkingCopyCloneFromRow(AbstractRecord row, ObjectBuildingQuery query) {
        if (query.isObjectLevelReadQuery()){ //for backward compat reasons cast this
            return getDescriptor().getCopyPolicy().buildWorkingCopyCloneFromRow(row, ((ObjectLevelReadQuery)query));
        }else{
            return getDescriptor().getCopyPolicy().buildWorkingCopyCloneFromRow(row, query);
        }
    }

    public boolean isPrimaryKeyMapping(DatabaseMapping mapping) {
        return getPrimaryKeyMappings().contains(mapping);
    }

    /**
     * INTERNAL:
     * Perform the itteration opperation on the objects attributes through the mappings.
     */
    public void iterate(DescriptorIterator iterator) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        int mappingsSize = mappings.size();
        for (int index = 0; index < mappingsSize; index++) {
            ((DatabaseMapping)mappings.get(index)).iterate(iterator);
        }
    }

    /**
     * INTERNAL:
     * Merge changes between the objects, this merge algorthim is dependent on the merge manager.
     */
    public void mergeChangesIntoObject(Object target, ObjectChangeSet changeSet, Object source, MergeManager mergeManager) {
        for (Enumeration changes = changeSet.getChanges().elements(); changes.hasMoreElements();) {
            ChangeRecord record = (ChangeRecord)changes.nextElement();

            //cr 4236, use ObjectBuilder getMappingForAttributeName not the Descriptor one because the
            // ObjectBuilder method is much more efficient.
            DatabaseMapping mapping = getMappingForAttributeName(record.getAttribute());
            mapping.mergeChangesIntoObject(target, record, source, mergeManager);
        }

        // PERF: Avoid events if no listeners.
        if (getDescriptor().getEventManager().hasAnyEventListeners()) {
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(target);
            event.setSession(mergeManager.getSession());
            event.setOriginalObject(source);
            event.setChangeSet(changeSet);
            event.setEventCode(DescriptorEventManager.PostMergeEvent);
            getDescriptor().getEventManager().executeEvent(event);
        }
    }

    /**
     * INTERNAL:
     * Merge the contents of one object into another, this merge algorthim is dependent on the merge manager.
     * This merge also prevents the extra step of calculating the changes when it is not required.
     */
    public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager) {
        mergeIntoObject(target, isUnInitialized, source, mergeManager, false);
    }
    
    /**
     * INTERNAL:
     * Merge the contents of one object into another, this merge algorthim is dependent on the merge manager.
     * This merge also prevents the extra step of calculating the changes when it is not required.
     * If 'cascadeOnly' is true, only foreign reference mappings are merged.
     */
    public void mergeIntoObject(Object target, boolean isUnInitialized, Object source, MergeManager mergeManager, boolean cascadeOnly) {
        // cascadeOnly is introduced to optimize merge
        // for GF#1139 Cascade merge operations to relationship mappings even if already registered
        
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        Vector mappings = getDescriptor().getMappings();
        for (int index = 0; index < mappings.size(); index++) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.get(index);
            if(!cascadeOnly || mapping.isForeignReferenceMapping()){
                mapping.mergeIntoObject(target, isUnInitialized, source, mergeManager);
            }
        }

        // PERF: Avoid events if no listeners.
        if (getDescriptor().getEventManager().hasAnyEventListeners()) {
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(target);
            event.setSession(mergeManager.getSession());
            event.setOriginalObject(source);
            event.setEventCode(DescriptorEventManager.PostMergeEvent);
            getDescriptor().getEventManager().executeEvent(event);
        }
    }

    /**
     * Clones the attributes of the specified object. This is called only from unit of work.
     * The domainObject sent as parameter is always a copy from the parent of unit of work.
     */
    public void populateAttributesForClone(Object original, Object clone, UnitOfWorkImpl unitOfWork) {
        // PERF: Avoid synchronized enumerator as is concurrency bottleneck.
        List mappings = getCloningMappings();
        for (int index = 0; index < mappings.size(); index++) {
                ((DatabaseMapping)mappings.get(index)).buildClone(original, clone, unitOfWork);
        }

        // PERF: Avoid events if no listeners.
        if (getDescriptor().getEventManager().hasAnyEventListeners()) {
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(clone);
            event.setSession(unitOfWork);
            event.setOriginalObject(original);
            event.setEventCode(DescriptorEventManager.PostCloneEvent);
            getDescriptor().getEventManager().executeEvent(event);
        }
    }

    /**
     * Rehash any hashtables based on fields.
     * This is used to clone descriptors for aggregates, which hammer field names,
     * it is probably better not to hammer the field name and this should be refactored.
     */
    public void rehashFieldDependancies(AbstractSession session) {
        setMappingsByField(Helper.rehashMap(getMappingsByField()));
        setReadOnlyMappingsByField(Helper.rehashMap(getReadOnlyMappingsByField()));
        setPrimaryKeyMappings(oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2));
        setNonPrimaryKeyMappings(oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2));
        initializePrimaryKey(session);
    }

    /**
     * Set the descriptor.
     */
    public void setDescriptor(ClassDescriptor aDescriptor) {
        descriptor = aDescriptor;
    }

    /**
     * All the mappings and their respective attribute associations are cached for performance improvement.
     */
    protected void setMappingsByAttribute(Map<String, DatabaseMapping> theAttributeMappings) {
        mappingsByAttribute = theAttributeMappings;
    }

    /**
     * INTERNAL:
     * All the mappings and their respective field associations are cached for performance improvement.
     */
    public void setMappingsByField(Map<DatabaseField, DatabaseMapping> theFieldMappings) {
        mappingsByField = theFieldMappings;
    }

    /**
     * INTERNAL:
     * All the read-only mappings and their respective field associations are cached for performance improvement.
     */
    public void setReadOnlyMappingsByField(Map<DatabaseField, Vector<DatabaseMapping>> theReadOnlyFieldMappings) {
        readOnlyMappingsByField = theReadOnlyFieldMappings;
    }

    /**
     * The non primary key mappings are cached to improve performance.
     */
    protected void setNonPrimaryKeyMappings(Vector<DatabaseMapping> theNonPrimaryKeyMappings) {
        nonPrimaryKeyMappings = theNonPrimaryKeyMappings;
    }

    /**
     * Set primary key classifications.
     * These are used to ensure a consistent type for the pk values.
     */
    protected void setPrimaryKeyClassifications(Vector<Class> primaryKeyClassifications) {
        this.primaryKeyClassifications = primaryKeyClassifications;
    }

    /**
     * The primary key expression is cached to improve performance.
     */
    public void setPrimaryKeyExpression(Expression criteria) {
        primaryKeyExpression = criteria;
    }

    /**
     * The primary key mappings are cached to improve performance.
     */
    protected void setPrimaryKeyMappings(Vector<DatabaseMapping> thePrimaryKeyMappings) {
        primaryKeyMappings = thePrimaryKeyMappings;
    }

    public String toString() {
        return Helper.getShortClassName(getClass()) + "(" + getDescriptor().toString() + ")";
    }

    /**
     * Unwrap the object if required.
     * This is used for the wrapper policy support and EJB.
     */
    public Object unwrapObject(Object proxy, AbstractSession session) {
        if (proxy == null) {
            return null;
        }

        // Check if already unwrapped.
        if ((getDescriptor().getJavaClass() == proxy.getClass()) || !getDescriptor().hasWrapperPolicy() || !getDescriptor().getWrapperPolicy().isWrapped(proxy)) {
            return proxy;
        }

        // Allow for inheritance, the concrete wrapper must always be used.
        if (getDescriptor().hasInheritance() && (getDescriptor().getInheritancePolicy().hasChildren())) {
            ClassDescriptor descriptor = session.getDescriptor(proxy);
            if (descriptor != getDescriptor()) {
                return descriptor.getObjectBuilder().unwrapObject(proxy, session);
            }
        }

        if (getDescriptor().hasWrapperPolicy()) {
            return getDescriptor().getWrapperPolicy().unwrapObject(proxy, session);
        } else {
            return proxy;
        }
    }

    /**
     * Validates the object builder. This is done once the object builder initialized and descriptor
     * fires this validation.
     */
    public void validate(AbstractSession session) throws DescriptorException {
        if (getDescriptor().usesSequenceNumbers()) {
            if (getMappingForField(getDescriptor().getSequenceNumberField()) == null) {
                throw DescriptorException.mappingForSequenceNumberField(getDescriptor());
            }
        }
    }

    /**
     * Verify that an object has been deleted from the database.
     * An object can span multiple tables. A query is performed on each of
     * these tables using the primary key values of the object as the selection
     * criteria. If the query returns a result then the object has not been
     * deleted from the database. If no result is returned then each of the
     * mappings is asked to verify that the object has been deleted. If all mappings
     * answer true then the result is true.
     */
    public boolean verifyDelete(Object object, AbstractSession session) {
        AbstractRecord translationRow = buildRowForTranslation(object, session);

        // If a call is used generated SQL cannot be executed, the call must be used.
        if ((getDescriptor().getQueryManager().getReadObjectQuery() != null) && getDescriptor().getQueryManager().getReadObjectQuery().isCallQuery()) {
            Object result = session.readObject(object);
            if (result != null) {
                return false;
            }
        } else {
            for (Enumeration tables = getDescriptor().getTables().elements();
                     tables.hasMoreElements();) {
                DatabaseTable table = (DatabaseTable)tables.nextElement();

                SQLSelectStatement sqlStatement = new SQLSelectStatement();
                sqlStatement.addTable(table);
                if (table == getDescriptor().getTables().firstElement()) {
                    sqlStatement.setWhereClause((Expression)getPrimaryKeyExpression().clone());
                } else {
                    sqlStatement.setWhereClause(buildPrimaryKeyExpression(table));
                }
                DatabaseField all = new DatabaseField("*");
                all.setTable(table);
                sqlStatement.addField(all);
                sqlStatement.normalize(session, null);

                DataReadQuery dataReadQuery = new DataReadQuery();
                dataReadQuery.setSQLStatement(sqlStatement);
                dataReadQuery.setSessionName(getDescriptor().getSessionName());

                // execute the query and check if there is a valid result
                Vector queryResults = (Vector)session.executeQuery(dataReadQuery, translationRow);
                if (!queryResults.isEmpty()) {
                    return false;
                }
            }
        }

        // now ask each of the mappings to verify that the object has been deleted.
        for (Enumeration mappings = getDescriptor().getMappings().elements();
                 mappings.hasMoreElements();) {
            DatabaseMapping mapping = (DatabaseMapping)mappings.nextElement();

            if (!mapping.verifyDelete(object, session)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Wrap the object if required.
     * This is used for the wrapper policy support and EJB.
     */
    public Object wrapObject(Object implementation, AbstractSession session) {
        if (implementation == null) {
            return null;
        }

        // Check if already wrapped.
        if (!getDescriptor().hasWrapperPolicy() || getDescriptor().getWrapperPolicy().isWrapped(implementation)) {
            return implementation;
        }

        // Allow for inheritance, the concrete wrapper must always be used.
        if (getDescriptor().hasInheritance() && getDescriptor().getInheritancePolicy().hasChildren() && (implementation.getClass() != getDescriptor().getJavaClass())) {
            ClassDescriptor descriptor = session.getDescriptor(implementation);
            if(descriptor != getDescriptor()) {
                return descriptor.getObjectBuilder().wrapObject(implementation, session);
            }
        }
        if (getDescriptor().hasWrapperPolicy()) {
            return getDescriptor().getWrapperPolicy().wrapObject(implementation, session);
        } else {
            return implementation;
        }
    }
}



/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.descriptors.changetracking;

import java.io.Serializable;
import oracle.toplink.essentials.internal.sessions.ObjectChangeSet;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.helper.IdentityHashtable;

/**
 * INTERNAL:
 * Implementers of ObjectChangePolicy implement the code which computes changes sets
 * for TopLink's UnitOfWork commit process. An ObjectChangePolicy is stored on an
 * Object's descriptor.
 * @see DeferredChangeDetectionPolicy
 * @see ObjectChangeTrackingPolicy
 * @see AttributeChangeTrackingPolicy
 * @author Tom Ware
 */
public interface ObjectChangePolicy extends Serializable {

    /**
     * INTERNAL:
     * calculateChanges creates a change set for a particular object
     * @return oracle.toplink.essentials.changesets.ObjectChangeSet an object change set describing
     * the changes to this object
     * @param clone the Object to compute a change set for
     * @param backUp the old version of the object to use for comparison
     * @param changes the change set to add changes to
     * @param session the current session
     * @param descriptor the descriptor for this object
     * @param shouldRiseEvent indicates whether PreUpdate event should be risen (usually true)
     */
    public ObjectChangeSet calculateChanges(Object clone, Object backUp, oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet changes, AbstractSession session, ClassDescriptor descriptor, boolean shouldRiseEvent);

    /**
     * INTERNAL:
     * Create ObjectChangeSet through comparison. Used in cases where we need to force change calculation (ie aggregates)
     */
    public ObjectChangeSet createObjectChangeSetThroughComparison(Object clone, Object backUp, oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet changeSet, boolean isNew, AbstractSession session, ClassDescriptor descriptor);

    /**
     * INTERNAL:
     * This method is used to dissable changetracking temporarily
     */
    public void dissableEventProcessing(Object changeTracker);

    /**
     * INTERNAL:
     * This method is used to enable changetracking temporarily
     */
    public void enableEventProcessing(Object changeTracker);
    
    /**
     * INTERNAL:
     * This may cause a property change event to be raised to a listner in the case that a listener exists.
     * If there is no listener then this call is a no-op
     */
    public void raiseInternalPropertyChangeEvent(Object source, String propertyName, Object oldValue, Object newValue);
    
    /**
     * INTERNAL:
     * This method is used to revert an object within the unit of work
     */
    public void revertChanges(Object clone, ClassDescriptor descriptor, UnitOfWorkImpl uow, IdentityHashtable cloneMapping);

    /**
     * INTERNAL:
     * This is a place holder for reseting the listener on one of the subclasses
     */
    public void clearChanges(Object object, UnitOfWorkImpl uow, ClassDescriptor descriptor);
    
    /**
     * INTERNAL:
     * This method is used internally to rest the policies back to original state
     * This is used when the clones are to be reused.
     */
    public void updateWithChanges(Object clone, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow, ClassDescriptor descriptor);

    /**
     * INTERNAL:
     * Return true if the Object should be compared, false otherwise. This method is implemented to allow
     * run time determination of whether a change set should be computed for an object. In general, calculateChanges()
     * will only be executed in a UnitOfWork if this method returns true.
     * @param object the object that will be compared
     * @param unitOfWork the active unitOfWork
     * @param descriptor the descriptor for the current object
     */
    public boolean shouldCompareForChange(Object object, UnitOfWorkImpl unitOfWork, ClassDescriptor descriptor);

    /**
     * INTERNAL:
     * Assign Changelistner to an aggregate object
     */
    public void setAggregateChangeListener(Object parent, Object aggregate, UnitOfWorkImpl uow, ClassDescriptor descriptor, String mappingAttribute);

    /**
     * INTERNAL:
     * Assign appropriate ChangeListener to PropertyChangeListener based on the policy.
     */
    public void setChangeListener(Object clone, UnitOfWorkImpl uow, ClassDescriptor descriptor);

    /**
     * INTERNAL:
     * Set the ObjectChangeSet on the Listener, initially used for aggregate support
     */
    public void setChangeSetOnListener(ObjectChangeSet objectChangeSet, Object clone);
    
    /**
     * INTERNAL:
     * Build back up clone.
     */
    public Object buildBackupClone(Object clone, ObjectBuilder builder, UnitOfWorkImpl uow);

    /**
     * INTERNAL:
     * initialize the Policy
     */
    public void initialize(AbstractSession session, ClassDescriptor descriptor);

    /**
     * Used to track instances of the change policies without doing an instance of check
     */
    public boolean isDeferredChangeDetectionPolicy();

    /**
     * Used to track instances of the change policies without doing an instance of check
     */
    public boolean isObjectChangeTrackingPolicy();

    /**
     * Used to track instances of the change policies without doing an instance of check
     */
    public boolean isAttributeChangeTrackingPolicy();
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.sessions;

import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.localization.*;

/**
 * <b>Purpose</b>: Define how an object is to be copied.<p>
 * <b>Description</b>: This is for usage with the object copying feature, not the unit of work.
 * This is useful for copying an entire object graph as part of the
 * host application's logic.<p>
 * <b>Responsibilities</b>:<ul>
 * <li> Inidcate through CASCADE levels the depth relationships will copied.
 * <li> Indicate if PK attributes should be copied with existing value or should be reset.
 * </ul>
 * @since TOPLink/Java 3.0
 * @see Session#copyObject(Object, ObjectCopyingPolicy)
 */
public class ObjectCopyingPolicy {
    protected boolean shouldResetPrimaryKey;
    protected oracle.toplink.essentials.internal.sessions.AbstractSession session;
    protected HardIdentityHashtable copies;

    /** Policy depth that determines how the copy will cascade to the object's
        related parts */
    protected int depth;

    /** Depth level indicating that NO relationships should be included in the copy.
        Relationships that are not copied will include the default value of the object's
        instantiation policy */
    public static final int NO_CASCADE = 1;

    /** Depth level indicating that only relationships with mapping indicated privately-
        owned should be copied */
    public static final int CASCADE_PRIVATE_PARTS = 2;

    /** Depth level indicating that all relationships with mappings should be used when
        building the copied object graph */
    public static final int CASCADE_ALL_PARTS = 3;

    /**
     * PUBLIC:
     * Return a new copying policy.
     * By default the policy cascades privately owned parts and nulls primary keys.
     */
    public ObjectCopyingPolicy() {
        this.shouldResetPrimaryKey = true;
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        this.copies = new HardIdentityHashtable();
        this.depth = CASCADE_PRIVATE_PARTS;
    }

    /**
     * PUBLIC:
     * Set if the copy should cascade all relationships when copying the object(s).
     */
    public void cascadeAllParts() {
        setDepth(CASCADE_ALL_PARTS);
    }

    /**
     * PUBLIC:
     * Set if the copy should cascade only those relationships that are configured
     * as privately-owned.
     */
    public void cascadePrivateParts() {
        setDepth(CASCADE_PRIVATE_PARTS);
    }

    /**
     * PUBLIC:
     * Set if the copy should not cascade relationships when copying the object(s)
     */
    public void dontCascade() {
        setDepth(NO_CASCADE);
    }

    /**
     * INTERNAL: Get the session.
     */
    public HardIdentityHashtable getCopies() {
        return copies;
    }

    /**
     * INTERNAL: Return the cascade depth.
     */
    public int getDepth() {
        return depth;
    }

    /**
     * INTERNAL: Return the session.
     */
    public oracle.toplink.essentials.internal.sessions.AbstractSession getSession() {
        return session;
    }

    /**
     * INTERNAL: Set the copies.
     */
    public void setCopies(HardIdentityHashtable newCopies) {
        copies = newCopies;
    }

    /**
     * INTERNAL: Set the cascade depth.
     */
    public void setDepth(int newDepth) {
        depth = newDepth;
    }

    /**
     * INTERNAL: Set the session.
     */
    public void setSession(oracle.toplink.essentials.internal.sessions.AbstractSession newSession) {
        session = newSession;
    }

    /**
     * PUBLIC:
     * Set if the primary key should be reset to null.
     */
    public void setShouldResetPrimaryKey(boolean newShouldResetPrimaryKey) {
        shouldResetPrimaryKey = newShouldResetPrimaryKey;
    }

    /**
     * PUBLIC:
     * Return true if the policy has been configured to CASCADE_ALL_PARTS or CASCADE_PRIVATE_PARTS.
     */
    public boolean shouldCascade() {
        return getDepth() != NO_CASCADE;
    }

    /**
     * PUBLIC:
     * Return true if the policy should CASCADE_ALL_PARTS
     */
    public boolean shouldCascadeAllParts() {
        return getDepth() == CASCADE_ALL_PARTS;
    }

    /**
     * PUBLIC:
     * Return true if the policy should CASCADE_PRIVATE_PARTS
     */
    public boolean shouldCascadePrivateParts() {
        return getDepth() == CASCADE_PRIVATE_PARTS;
    }

    /**
     * PUBLIC:
     * Return if the primary key should be reset to null.
     */
    public boolean shouldResetPrimaryKey() {
        return shouldResetPrimaryKey;
    }

    /**
     * INTERNAL:
     */
    public String toString() {
        String depthString = "";
        if (shouldCascadeAllParts()) {
            depthString = "CASCADE_ALL_PARTS";
        } else if (shouldCascadePrivateParts()) {
            depthString = "CASCADE_PRIVATE_PARTS";
        } else {
            depthString = "NO_CASCADING";
        }
        Object[] args = { depthString, new Boolean(shouldResetPrimaryKey()) };
        return Helper.getShortClassName(this) + ToStringLocalization.buildMessage("depth_reset_key", args);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.queryframework;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.expressions.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.querykeys.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;


/**
 * <p><b>Purpose</b>:
 * Abstract class for all read queries using objects.
 *
 * <p><b>Description</b>:
 * Contains common behavior for all read queries using objects.
 *
 * @author Yvon Lavoie
 * @since TOPLink/Java 1.0
 */
public abstract class ObjectLevelReadQuery extends ObjectBuildingQuery {

    /** Provide a default builder so that it's easier to be consistent */
    protected ExpressionBuilder defaultBuilder;

    /** Allows for the resulting objects to be refresh with the data from the database. */
    protected boolean shouldRefreshIdentityMapResult;

    /** Allow for the cache usage to be specified to enable in-memory querying. */
    protected int cacheUsage;

    // Note: UseDescriptorSetting will result in CheckCacheByPrimaryKey for most cases
    // it simply allows the Descriptor's disable cache hits to be used
    public static final int UseDescriptorSetting = -1;
    public static final int DoNotCheckCache = 0;
    public static final int CheckCacheByExactPrimaryKey = 1;
    public static final int CheckCacheByPrimaryKey = 2;
    public static final int CheckCacheThenDatabase = 3;
    public static final int CheckCacheOnly = 4;
    public static final int ConformResultsInUnitOfWork = 5;

    /** INTERNAL: for bug 2612601 allow ability not to register results in UOW. */
    protected boolean shouldRegisterResultsInUnitOfWork = true;

    /** Allow for additional fields to be selected, used for m-m batch reading. */
    protected Vector additionalFields;

    /** Allow for a complex result to be return including the rows and objects, used for m-m batch reading. */
    protected boolean shouldIncludeData;

    /** CMP only. Allow users to configure whether finder should be executed in a uow or not. */
    protected boolean shouldProcessResultsInUnitOfWork = true;

    /** Indicates if distinct should be used or not. */
    protected short distinctState;
    public static final short UNCOMPUTED_DISTINCT = 0;
    public static final short USE_DISTINCT = 1;
    public static final short DONT_USE_DISTINCT = 2;

    /**
     * CR 3677
     * Used to determine behaviour of indirection in InMemoryQuerying
     * This should have been just a constant similar to distinct locking, etc. instead of an object that just has the state and no behavoir,
     * the object instantiation adds un-needed overhead, but too late now.
     */
    protected InMemoryQueryIndirectionPolicy inMemoryQueryIndirectionPolicy;

    /**
     * Used to set the read time on objects that use this query.
     * Should be set to the time the query returned from the database.
     */
    protected long executionTime = 0;

    /**
     * INTERNAL: This is the key for accessing unregistered and locked result in the query's properties.
     * The uow and QueryBaseValueHolder use this property to record amd to retreive the result respectively.
     */
    public static final String LOCK_RESULT_PROPERTY = "LOCK_RESULT";

    /** Allow for a query level fetch group to be set. */
    protected FetchGroup fetchGroup;

    /** The pre-defined fetch group name. */
    protected String fetchGroupName;

    /** Flag to turn on/off the use of the default fetch group. */
    protected boolean shouldUseDefaultFetchGroup = true;

    /** PERF: Store if the query originally used the default lock mode. */
    protected boolean wasDefaultLockMode = false;
    
    /** Stores the non fetchjoin attributes, these are joins that will be
     * represented in the where clause but not in the select */
    protected Vector nonFetchJoinAttributeExpressions;

    /** Stores the helper object for dealing with joined attributes */
    protected JoinedAttributeManager joinedAttributeManager;
    
    /**
     * INTERNAL:
     * Initialize the state of the query
     */
    public ObjectLevelReadQuery() {
        this.shouldRefreshIdentityMapResult = false;
        this.distinctState = UNCOMPUTED_DISTINCT;
        this.joinedAttributeManager = new JoinedAttributeManager(getDescriptor(), getExpressionBuilder(), this);
        this.additionalFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.cacheUsage = UseDescriptorSetting;
        this.shouldIncludeData = false;
        this.inMemoryQueryIndirectionPolicy = new InMemoryQueryIndirectionPolicy();
    }

    /**
     * INTERNAL:
     * Return if this query originally used the default lock mode.
     */
    protected boolean wasDefaultLockMode() {
        return wasDefaultLockMode;
    }
    
    /**
     * INTERNAL:
     * Set if this query originally used the default lock mode.
     */
    protected void setWasDefaultLockMode(boolean wasDefaultLockMode) {
        this.wasDefaultLockMode = wasDefaultLockMode;
    }
    
    /**
     * INTERNAL:
     * Clone the query
     */
    public Object clone() {
        ObjectLevelReadQuery cloneQuery = (ObjectLevelReadQuery)super.clone();

        //CR#... must also clone the joined expressions as always joined attribute will be added
        // don't use setters as this will trigger unprepare.
        cloneQuery.joinedAttributeManager = (JoinedAttributeManager)cloneQuery.getJoinedAttributeManager().clone();
        if (hasNonFetchJoinedAttributeExpressions()){
            cloneQuery.setNonFetchJoinAttributeExpressions((Vector)this.nonFetchJoinAttributeExpressions.clone());
        }
        cloneQuery.joinedAttributeManager.setBaseQuery(cloneQuery);
        return cloneQuery;
    }

    /**
     * INTERNAL:
     * Clone the query, including its selection criteria.
     * <p>
     * Normally selection criteria are not cloned here as they are cloned
     * later on during prepare.
     */
    public Object deepClone() {
        ObjectLevelReadQuery clone = (ObjectLevelReadQuery)clone();
        if (getSelectionCriteria() != null) {
            clone.setSelectionCriteria((Expression)getSelectionCriteria().clone());
        } else if (defaultBuilder != null) {
            clone.defaultBuilder = (ExpressionBuilder)defaultBuilder.clone();
        }
        return clone;
    }

    /**
     * PUBLIC:
     * Set the query to lock, this will also turn refreshCache on.
     */
    public void acquireLocks() {
        setLockMode(LOCK);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * PUBLIC:
     * Set the query to lock without waiting (blocking), this will also turn refreshCache on.
     */
    public void acquireLocksWithoutWaiting() {
        setLockMode(LOCK_NOWAIT);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Additional fields can be added to a query. This is used in m-m bacth reading to bring back the key from the join table.
     */
    public void addAdditionalField(DatabaseField field) {
        getAdditionalFields().addElement(field);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Additional fields can be added to a query. This is used in m-m bacth reading to bring back the key from the join table.
     */
    public void addAdditionalField(Expression fieldExpression) {
        getAdditionalFields().addElement(fieldExpression);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * PUBLIC:
     * Specify the one-to-one mapped attribute to be optimized in this query.
     * The query will join the object(s) being read with the one-to-one attribute,
     * this allows all of the data required for the object(s) to be read in a single query instead of (n) queries.
     * This should be used when the application knows that it requires the part for all of the objects being read.
     * This can be used only for one-to-one mappings where the target is not the same class as the source,
     * either directly or through inheritance. Also two joins cannot be done to the same class.
     *
     * <p>Note: This cannot be used for objects where it is possible not to have a part,
     * as these objects will be ommited from the result set,
     * unless an outer join is used through passing and expression using "getAllowingNull".
     *
     * <p>Example: query.addJoinedAttribute("address")
     *
     * @see #addJoinedAttribute(Expression)
     */
    public void addJoinedAttribute(String attributeName) {
        addJoinedAttribute(getExpressionBuilder().get(attributeName));
    }

    /**
     * PUBLIC:
     * Specify the to-one or to-many mapped attribute to be optimized in this query.
     * The query will join the object(s) being read with the specified attribute,
     * this allows all of the data required for the object(s) to be read in a single query instead of (n) queries.
     * This should be used when the application knows that it requires the part for all of the objects being read.
     *
     * <p>Note: This cannot be used for objects where it is possible not to have a part,
     * as these objects will be ommited from the result set,
     * unless an outer join is used through passing and expression using "getAllowingNull".
     *
     * <p>Example:
     * The following will fetch along with Employee(s) "Jones" all projects they participate in
     * along with teamLeaders and their addresses, teamMembers and their phones.
     *
     * query.setSelectionCriteria(query.getExpressionBuilder().get("lastName").equal("Jones"));
     * Expression projects = query.getExpressionBuilder().anyOf("projects");
     * query.addJoinedAttribute(projects);
     * Expression teamLeader = projects.get("teamLeader");
     * query.addJoinedAttribute(teamLeader);
     * Expression teamLeaderAddress = teamLeader.getAllowingNull("address");
     * query.addJoinedAttribute(teamLeaderAddress);
     * Expression teamMembers = projects.anyOf("teamMembers");
     * query.addJoinedAttribute(teamMembers);
     * Expression teamMembersPhones = teamMembers.anyOfAllowingNone("phoneNumbers");
     * query.addJoinedAttribute(teamMembersPhones);
     *
     * Note that:
     * the order is essential: an expression should be added before any expression derived from it;
     * the object is built once - it won't be rebuilt if it to be read again as a joined attribute:
     * in the example the query won't get phones for "Jones" -
     * even though they are among teamMembers (for whom phones are read).
     *
     */
    public void addJoinedAttribute(Expression attributeExpression) {
        getJoinedAttributeManager().addJoinedAttributeExpression(attributeExpression);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        // Joined attributes are now calculated in prePrepare.
        setIsPrePrepared(false);
    }

    /**
     * PUBLIC:
     * Specify the one-to-one mapped attribute to be optimized in this query.
     * The query will join the object(s) being read with the one-to-one
     * attribute. The difference between this and a joined attribute is that
     * it allows data to be retrieved based on a join, but will not populate
     * the joined attribute. It also allows all of the data required for the
     * object(s) to be read in a single query instead of (n) queries. This
     * should be used when the application knows that it requires the part for
     * all of the objects being read. This can be used only for one-to-one
     * mappings where the target is not the same class as the source, either
     * directly or through inheritance. Also two joins cannot be done to the
     * same class.
     *
     * <p>Note: This cannot be used for objects where it is possible not to have
     * a part, as these objects will be ommited from the result set, unless an
     * outer join is used through passing and expression using "getAllowingNull".
     *
     * <p>Example: query.addNonFetchJoinedAttribute("address")
     *
     * @see #addNonFetchJoinedAttribute(Expression)
     */
    public void addNonFetchJoinedAttribute(String attributeName) {
        addNonFetchJoinedAttribute(getExpressionBuilder().get(attributeName));
    }

    /**
     * PUBLIC:
     * Specify the one-to-one mapped attribute to be optimized in this query.
     * The query will join the object(s) being read with the one-to-one
     * attribute. The difference between this and a joined attribute is that
     * it allows data to be retrieved based on a join, but will not populate
     * the joined attribute. It also allows all of the data required for the
     * object(s) to be read in a single query instead of (n) queries. This
     * should be used when the application knows that it requires the part for
     * all of the objects being read. This can be used only for one-to-one
     * mappings where the target is not the same class as the source, either
     * directly or through inheritance. Also two joins cannot be done to the
     * same class.
     *
     * <p>Note: This cannot be used for objects where it is possible not to have
     * a part, as these objects will be ommited from the result set, unless an
     * outer join is used through passing and expression using "getAllowingNull".
     *
     * <p>Example: query.addNonFetchJoinedAttribute(query.getExpressionBuilder().get("teamLeader").get("address"))
     *
     * @see #addNonFetchJoinedAttribute(Expression)
     */
    public void addNonFetchJoinedAttribute(Expression attributeExpression) {
        getNonFetchJoinAttributeExpressions().add(attributeExpression);
        
        // Bug 2804042 Must un-prepare if prepared as the SQL may change.
        // Joined attributes are now calculated in prePrepare.
        setIsPrePrepared(false);
    }

    /**
     * INTERNAL:
     * Iterate through a list of joined expressions and add the fields they represent to a list
     * of fields.
     */
    protected void addSelectionFieldsForJoinedExpressions(List fields, List joinedExpressions) {
        for (int index = 0; index < joinedExpressions.size(); index++) {
            ObjectExpression objectExpression = (ObjectExpression)joinedExpressions.get(index);

            // Expression may not have been initialized.
            objectExpression.getBuilder().setSession(getSession().getRootSession(null));
            objectExpression.getBuilder().setQueryClass(getReferenceClass());
            ClassDescriptor descriptor = objectExpression.getMapping().getReferenceDescriptor();
            fields.addAll(descriptor.getFields());
        }
    }

    /**
     * INTERNAL:
     * Called by CursoredStream to construct objects from rows.
     * Subclasses which build other results objects (ReportQuery, & PartialObjects) may override
     */
    public Object buildObject(AbstractRecord row) {
        return getDescriptor().getObjectBuilder().buildObject(this, row, this.getJoinedAttributeManager());
    }

    /**
     * PUBLIC:
     * The cache will checked completely, if the object is not found null will be returned or an error if the query is too complex.
     * Queries can be configured to use the cache at several levels.
     * Other caching option are available.
     * @see #setCacheUsage(int)
     */
    public void checkCacheOnly() {
        setCacheUsage(CheckCacheOnly);
    }

    /**
     * INTERNAL:
     * Ensure that the descriptor has been set.
     */
    public void checkDescriptor(AbstractSession session) throws QueryException {
        if (getReferenceClass() == null) {
            throw QueryException.referenceClassMissing(this);
        }

        if (getDescriptor() == null) {
            ClassDescriptor referenceDescriptor = session.getDescriptor(getReferenceClass());
            if (referenceDescriptor == null) {
                throw QueryException.descriptorIsMissing(getReferenceClass(), this);
            }
            setDescriptor(referenceDescriptor);
        }
    }

    /**
     * INTERNAL:
     * Contains the body of the check early return call, implemented by subclasses.
     */
    protected abstract Object checkEarlyReturnImpl(AbstractSession session, AbstractRecord translationRow);

    /**
     * INTERNAL:
     * Check to see if this query already knows the return vale without preforming any further work.
     */
    public Object checkEarlyReturn(AbstractSession session, AbstractRecord translationRow) {
        // For bug 3136413/2610803 building the selection criteria from an EJBQL string or
        // an example object is done just in time.
        // Also calls checkDescriptor here.
        //buildSelectionCriteria(session);
        checkPrePrepare(session);

        if (!session.isUnitOfWork()) {
            return checkEarlyReturnImpl(session, translationRow);
        }
        UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)session;

        // The cache check must happen on the UnitOfWork in these cases either
        // to access transient state or for pessimistic locking, as only the
        // UOW knows which objects it has locked.
        if (shouldCheckCacheOnly() || shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork() || (getLockMode() != ObjectBuildingQuery.NO_LOCK)) {
            Object result = checkEarlyReturnImpl(unitOfWork, translationRow);
            if (result != null) {
                return result;
            }
        }

        // don't bother trying to get a cache hit on the parent session
        // as if not in UnitOfWork it is not yet pessimistically locked
        // on the database for sure.
        // Note for ReadObjectQueries we totally ignore shouldCheckCacheOnly.
        if (isReadObjectQuery() && isLockQuery()) {
            return null;
        }

        // follow the execution path in looking for the object.
        AbstractSession parentSession = unitOfWork.getParentIdentityMapSession(this);

        // assert parentSession != unitOfWork;
        Object result = checkEarlyReturn(parentSession, translationRow);

        if (result != null) {
            // Optimization: If find deleted object by exact primary key
            // treat this as cache hit but return null. Bug 2782991.
            if (result == InvalidObject.instance) {
                return result;
            }
            return registerResultInUnitOfWork(result, unitOfWork, translationRow, false);
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Check to see if this query needs to be prepare and prepare it.
     * The prepare is done on the original query to ensure that the work is not repeated.
     */
    public void checkPrepare(AbstractSession session, AbstractRecord translationRow) {
        // CR#3823735 For custom queries the prePrepare may not have been called yet.
        checkPrePrepare(session);
        super.checkPrepare(session, translationRow);
    }

    /**
     * INTERNAL:
     * ObjectLevelReadQueries now have an explicit pre-prepare stage, which
     * is for checking for pessimistic locking, and computing any joined
     * attributes declared on the descriptor.
     */
    protected void checkPrePrepare(AbstractSession session) {
        checkDescriptor(session);
        // This query is first prepared for global common state, this must be synced.
        if (!isPrePrepared()) {// Avoid the monitor is already prePrepare, must check again for concurrency.
            synchronized (this) {
                if (!isPrePrepared()) {
                    AbstractSession alreadySetSession = getSession();
                    setSession(session);// Session is required for some init stuff.
                    prePrepare();
                    setSession(alreadySetSession);
                    setIsPrePrepared(true);// MUST not set prepare until done as other thread may hit before finishing the prePrepare.
                }
            }
        }
    }

    /**
     * INTERNAL:
     * The reference class has been changed, need to reset the
     * descriptor. Null out the current descriptor and call
     * checkDescriptor
     * Added Feb 27, 2001 JED for EJBQL feature
     */
    public void changeDescriptor(AbstractSession theSession) {
        setDescriptor(null);
        checkDescriptor(theSession);
    }

    /**
     * INTERNAL:
     * Conforms and registers an individual result. This instance could be one
     * of the elements returned from a read all query, the result of a Read Object
     * query, or an element read from a cursor.
     * <p>
     * A result needs to be registered before it can be conformed, so
     * registerIndividualResult is called here.
     * <p>
     * Conforming on a result from the database is lenient. Since the object
     * matched the query on the database we assume it matches here unless we can
     * determine for sure that it was changed in this UnitOfWork not to conform.
     * @param result may be an original, or a raw database row
     * @param arguments the parameters this query was executed with
     * @param selectionCriteriaClone the expression to conform to. If was a
     * selection object or key, null (which all conform to) is used
     * @param alreadyReturned a hashtable of objects already found by scanning
     * the UnitOfWork cache for conforming instances. Prevents duplicates.
     * @param buildDirectlyFromRows whether result is an original or a raw database
     * row
     * @return a clone, or null if result does not conform.
     */
    protected Object conformIndividualResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, Expression selectionCriteriaClone, HardIdentityHashtable alreadyReturned, boolean buildDirectlyFromRows) {
        // First register the object. Since object is presently unwrapped the
        // exact objects stored in the cache will be returned.
        // The object is known to exist.
        Object clone = registerIndividualResult(result, unitOfWork, buildDirectlyFromRows, null);

        if (getDescriptor().hasWrapperPolicy() && getDescriptor().getWrapperPolicy().isWrapped(clone)) {
            // The only time the clone could be wrapped is if we are not registering
            // results in the unitOfWork and we are ready to return a final
            // (unregistered) result now. Any further processing may accidently
            // cause it to get registered.
            return clone;
        }
        //bug 4459976 in order to maintain backward compatibility on ordering
        // lets use the result as a guild for the final result not the hashtable
        // of found objects.
        if (unitOfWork.isObjectDeleted(clone) ) {
            return null;
        }
        if (!isExpressionQuery() || (selectionCriteriaClone == null)) {
            if (alreadyReturned != null) {
                alreadyReturned.remove(clone);
            }
            return clone;
        }
        try {
            // pass in the policy to assume that the object conforms if indirection is not triggered. This
            // is valid because the query returned the object and we should trust the query that the object
            // matches the selection criteria, and because the indirection is not triggered then the customer
            //has not changed the value.
            // bug 2637555
            // unless for bug 3568141 use the painstaking shouldTriggerIndirection if set
            InMemoryQueryIndirectionPolicy policy = getInMemoryQueryIndirectionPolicy();
            if (!policy.shouldTriggerIndirection()) {
                policy = new InMemoryQueryIndirectionPolicy(InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_CONFORMED);
            }
            if (selectionCriteriaClone.doesConform(clone, unitOfWork, arguments, policy)) {
                 if (alreadyReturned != null) {
                    alreadyReturned.remove(clone);
                }
               return clone;
            }
        } catch (QueryException exception) {
            // bug 3570561: mask all-pervasive valueholder exceptions while conforming
            if ((unitOfWork.getShouldThrowConformExceptions() == UnitOfWorkImpl.THROW_ALL_CONFORM_EXCEPTIONS) && (exception.getErrorCode() != QueryException.MUST_INSTANTIATE_VALUEHOLDERS)) {
                throw exception;
            }
            if (alreadyReturned != null) {
                alreadyReturned.remove(clone);
            }
            return clone;
        }
        return null;
    }

    /**
     * PUBLIC:
     * The cache will checked completely, if the object is not found the database will be queried,
     * and the database result will be verified with what is in the cache and/or unit of work including new objects.
     * This can lead to poor performance so it is recomended that only the database be queried in most cases.
     * Queries can be configured to use the cache at several levels.
     * Other caching option are available.
     * @see #setCacheUsage(int)
     */
    public void conformResultsInUnitOfWork() {
        setCacheUsage(ConformResultsInUnitOfWork);
    }

    /**
     * PUBLIC:
     * Set the query not to lock.
     */
    public void dontAcquireLocks() {
        setLockMode(NO_LOCK);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * PUBLIC:
     * This can be used to explicitly disable the cache hit.
     * The cache hit may not be desired in some cases, such as
     * stored procedures that accept the primary key but do not query on it.
     */
    public void dontCheckCache() {
        setCacheUsage(DoNotCheckCache);
    }

    /**
     * PUBLIC:
     * When unset means perform read normally and dont do refresh.
     */
    public void dontRefreshIdentityMapResult() {
        setShouldRefreshIdentityMapResult(false);
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void dontUseDistinct() {
        setDistinctState(DONT_USE_DISTINCT);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * There is a very special case where a query may be a bean-level
     * pessimistic locking query.
     * <p>
     * If that is so, only queries executed inside of a UnitOfWork should
     * have a locking clause. In the extremely rare case that we execute
     * a locking query outside of a UnitOfWork, must disable locking so that
     * we do not get a fetch out of sequence error.
     */
    public DatabaseQuery prepareOutsideUnitOfWork(AbstractSession session) {
        // Implementation is complicated because: if locking refresh will be
        // auto set to true preventing cache hit.
        // Must prepare this query from scratch if outside uow but locking
        // Must not reprepare this query as a NO_LOCK, but create a clone first
        // Must not cloneAndUnPrepare unless really have to
        if (isLockQuery(session) && getLockingClause().isForUpdateOfClause()) {
            ObjectLevelReadQuery clone = (ObjectLevelReadQuery)clone();
            clone.dontAcquireLocks();
            clone.setIsPrepared(false);
            clone.checkPrePrepare(session);
            return clone;
        }
        return this;
    }

    /**
     * INTERNAL:
     * Execute the query. If there are objects in the cache return the results
     * of the cache lookup.
     *
     * @param session - the session in which the receiver will be executed.
     * @exception DatabaseException - an error has occurred on the database.
     * @exception OptimisticLockException - an error has occurred using the optimistic lock feature.
     * @return An object, the result of executing the query.
     */
    public Object execute(AbstractSession session, AbstractRecord translationRow) throws DatabaseException, OptimisticLockException {
        //Bug#2839852 Refreshing is not possible if the query uses checkCacheOnly.
        if (shouldRefreshIdentityMapResult() && shouldCheckCacheOnly()) {
            throw QueryException.refreshNotPossibleWithCheckCacheOnly(this);
        }
        return super.execute(session, translationRow);
    }

    /*
     * Executes the prepared query on the datastore.
     */
    public Object executeDatabaseQuery() throws DatabaseException {
        if (getSession().isUnitOfWork()) {
            UnitOfWorkImpl unitOfWork = (UnitOfWorkImpl)getSession();

            // Note if a nested unit of work this will recursively start a
            // transaction early on the parent also.
            if (isLockQuery()) {
                if ((!unitOfWork.getCommitManager().isActive()) && (!unitOfWork.wasTransactionBegunPrematurely())) {
                    unitOfWork.beginTransaction();
                    unitOfWork.setWasTransactionBegunPrematurely(true);
                }
            }
            if (unitOfWork.isNestedUnitOfWork()) {
                UnitOfWorkImpl nestedUnitOfWork = (UnitOfWorkImpl)getSession();
                setSession(nestedUnitOfWork.getParent());
                Object result = executeDatabaseQuery();
                setSession(nestedUnitOfWork);
                return registerResultInUnitOfWork(result, nestedUnitOfWork, getTranslationRow(), false);
            }
        }
        session.validateQuery(this);// this will update the query with any settings

        if (getQueryId() == 0) {
            setQueryId(getSession().getNextQueryId());
        }

        return executeObjectLevelReadQuery();
    }

    /*
     * Executes the prepared query on the datastore.
     */
    protected abstract Object executeObjectLevelReadQuery() throws DatabaseException;

    /**
     * INTERNAL:
     * At this point only the code has been copied over from UnitOfWork
     * internalExecuteQuery. No work has been done.
     * @param unitOfWork
     * @param translationRow
     * @return
     * @throws oracle.toplink.essentials.exceptions.DatabaseException
     * @throws oracle.toplink.essentials.exceptions.OptimisticLockException
     */
    public Object executeInUnitOfWork(UnitOfWorkImpl unitOfWork, AbstractRecord translationRow) throws DatabaseException, OptimisticLockException {
        if (!shouldMaintainCache()) {
            return unitOfWork.getParent().executeQuery(this, translationRow);
        }
        Object result = execute(unitOfWork, translationRow);

        // Optimization: If find deleted object on uow by exact primary key
        // treat this as cache hit but return null. Bug 2782991.
        if (result == InvalidObject.instance) {
            return null;
        }
        return result;
    }

    /**
     * INTERNAL:
     * Additional fields can be added to a query. This is used in m-m bacth reading to bring back the key from the join table.
     */
    public Vector getAdditionalFields() {
        return additionalFields;
    }

    /**
     * PUBLIC:
     * Return the cache usage.
     * By default only primary key read object queries will first check the cache before accessing the database.
     * Any query can be configure to query against the cache completely, by key or ignore the cache check.
     * <p>Valid values are:
     * <ul>
     * <li> DoNotCheckCache
     * <li> CheckCacheByExactPrimaryKey
     * <li> CheckCacheByPrimaryKey
     * <li> CheckCacheThenDatabase
     * <li> CheckCacheOnly
     * <li> ConformResultsInUnitOfWork
     * <li> UseDescriptorSetting
     * Note: UseDescriptorSetting functions like CheckCacheByPrimaryKey, except checks the appropriate descriptor's
     * shouldDisableCacheHits setting when querying on the cache.
     * </lu>
     */
    public int getCacheUsage() {
        return cacheUsage;
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public short getDistinctState() {
        return distinctState;
    }

    /**
     * REQUIRED:
     * Get the expression builder which should be used for this query.
     * This expression builder should be used to build all expressions used by this query.
     */
    public ExpressionBuilder getExpressionBuilder() {
        if (defaultBuilder == null) {
            initializeDefaultBuilder();
        }

        return defaultBuilder;
    }

    /**
     * INTERNAL
     * Sets the default expression builder for this query.
     */
    public void setExpressionBuilder(ExpressionBuilder builder) {
        this.defaultBuilder = builder;
    }

    /**
     * PUBLIC:
     * Returns the InMemoryQueryIndirectionPolicy for this query
     */
    public InMemoryQueryIndirectionPolicy getInMemoryQueryIndirectionPolicy() {
        return this.inMemoryQueryIndirectionPolicy;
    }

    /**
     * INTERNAL:
     * Set the list of expressions that represent elements that are joined because of their
     * mapping for this query.
     */
    public JoinedAttributeManager getJoinedAttributeManager() {
        return this.joinedAttributeManager;
    }

    /**
     * INTERNAL:
     * Return the attributes that must be joined, but not fetched, that is,
     * do not trigger the value holder.
     */
    public Vector getNonFetchJoinAttributeExpressions() {
        if (this.nonFetchJoinAttributeExpressions == null){
            this.nonFetchJoinAttributeExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
        }
        return nonFetchJoinAttributeExpressions;
    }

    /**
     * INTERNAL:
     * Lookup the mapping for this item by traversing its expression recursively.
     * If an aggregate of foreign mapping is found it is traversed.
     */
    public DatabaseMapping getLeafMappingFor(Expression expression, ClassDescriptor rootDescriptor) throws QueryException {
        // Check for database field expressions or place holder
        if ((expression == null) || (expression.isFieldExpression())) {
            return null;
        }

        if (!(expression.isQueryKeyExpression())) {
            return null;
        }

        QueryKeyExpression qkExpression = (QueryKeyExpression)expression;
        Expression baseExpression = qkExpression.getBaseExpression();

        ClassDescriptor descriptor = getLeafDescriptorFor(baseExpression, rootDescriptor);
        return descriptor.getMappingForAttributeName(qkExpression.getName());
    }

    /**
     * INTERNAL:
     * Lookup the descriptor for this item by traversing its expression recursively.
     * @param expression
     * @param rootDescriptor
     * @return
     * @throws oracle.toplink.essentials.exceptions.QueryException
     */
    public ClassDescriptor getLeafDescriptorFor(Expression expression, ClassDescriptor rootDescriptor) throws QueryException {
        // The base case
        if (expression.isExpressionBuilder()) {
            // The following special case is where there is a parallel builder
            // which has a different reference class as the primary builder.
            Class queryClass = ((ExpressionBuilder)expression).getQueryClass();
            if ((queryClass != null) && (queryClass != getReferenceClass())) {
                return getSession().getDescriptor(queryClass);
            }
            return rootDescriptor;
        }
        Expression baseExpression = ((QueryKeyExpression)expression).getBaseExpression();
        ClassDescriptor baseDescriptor = getLeafDescriptorFor(baseExpression, rootDescriptor);
        ClassDescriptor descriptor = null;
        String attributeName = expression.getName();

        DatabaseMapping mapping = baseDescriptor.getMappingForAttributeName(attributeName);

        if (mapping == null) {
            QueryKey queryKey = baseDescriptor.getQueryKeyNamed(attributeName);
            if (queryKey != null) {
                if (queryKey.isForeignReferenceQueryKey()) {
                    descriptor = getSession().getDescriptor(((ForeignReferenceQueryKey)queryKey).getReferenceClass());
                } else// if (queryKey.isDirectQueryKey())
                 {
                    descriptor = queryKey.getDescriptor();
                }
            }
            if (descriptor == null) {
                throw QueryException.invalidExpressionForQueryItem(expression, this);
            }
        } else if (mapping.isAggregateObjectMapping()) {
            descriptor = ((AggregateObjectMapping)mapping).getReferenceDescriptor();
        } else if (mapping.isForeignReferenceMapping()) {
            descriptor = ((ForeignReferenceMapping)mapping).getReferenceDescriptor();
        }
        return descriptor;
    }

    /**
     * PUBLIC:
     * Return the current locking mode.
     */
    public short getLockMode() {
        if (lockingClause == null) {
            return DEFAULT_LOCK_MODE;
        } else {
            return lockingClause.getLockMode();
        }
    }

    /**
     * INTERNAL:
     * It is not exactly as simple as a query being either locking or not.
     * Any combination of the reference class object and joined attributes
     * may be locked.
     */
    public ForUpdateClause getLockingClause() {
        return lockingClause;
    }

    /**
     * INTERNAL:
     * Return the time this query actually went to the database
     */
    public long getExecutionTime() {
        return executionTime;
    }

    /**
     * PUBLIC:
     * Return the reference class of the query.
     */
    // TODO This method is left in so the javadoc remains, but is actually implemented on
    // super. We should reconsider this.
    public Class getReferenceClass() {
        return super.getReferenceClass();
    }

    /**
     * INTERNAL:
     * Return the reference class of the query.
     */
     // TODO This method is left in so the javadoc remains, but is actually implemented on
     // super. We should reconsider this.
    public String getReferenceClassName() {
        return super.getReferenceClassName();
    }

    /**
     * PUBLIC:
     * Answers if the domain objects are to be read as of a past time.
     * @see #getAsOfClause()
     */
    public boolean hasAsOfClause() {
        return ((defaultBuilder != null) && defaultBuilder.hasAsOfClause());
    }

    /**
     * INTERNAL:
     * Return the attributes that must be joined.
     */
    public boolean hasNonFetchJoinedAttributeExpressions() {
        return this.nonFetchJoinAttributeExpressions != null && !this.nonFetchJoinAttributeExpressions.isEmpty();
    }

    /**
     * INTERNAL:
     * Return if partial attributes.
     */
    public boolean hasPartialAttributeExpressions() {
        return false;
    }

    /**
     * INTERNAL:
     * Return the fields selected by the query.
     * This includes the partial or joined fields.
     * This is used for custom SQL executions.
     */
    public Vector getSelectionFields() {
        if ((!hasPartialAttributeExpressions()) && (!getJoinedAttributeManager().hasJoinedAttributes()) && (!hasFetchGroupAttributeExpressions())) {
            return getDescriptor().getAllFields();
        }
        Vector fields;
        if (hasFetchGroupAttributeExpressions()) {//fetch group support
            List fetchGroupAttrExps = getFetchGroup().getFetchGroupAttributeExpressions();
            fields = new Vector(fetchGroupAttrExps.size());
            for (int index = 0; index < fetchGroupAttrExps.size(); index++) {
                Expression expression = (Expression)fetchGroupAttrExps.get(index);

                // Expression may not have been initialized.
                expression.getBuilder().setSession(getSession().getRootSession(null));
                expression.getBuilder().setQueryClass(getReferenceClass());
                Helper.addAllToVector(fields, expression.getFields());
            }
        } else {
            fields = new Vector(getDescriptor().getAllFields().size() + getJoinedAttributeManager().getJoinedAttributeExpressions().size() + getJoinedAttributeManager().getJoinedMappingExpressions().size());
            Helper.addAllToVector(fields, getDescriptor().getAllFields());
            addSelectionFieldsForJoinedExpressions(fields, getJoinedAttributeManager().getJoinedAttributeExpressions());
            addSelectionFieldsForJoinedExpressions(fields, getJoinedAttributeManager().getJoinedMappingExpressions());
        }
        return fields;
    }

    /**
     * Initialize the expression builder which should be used for this query. If
     * there is a where clause, use its expression builder, otherwise
     * generate one and cache it. This helps avoid unnecessary rebuilds.
     */
    protected void initializeDefaultBuilder() {
        DatabaseQueryMechanism mech = getQueryMechanism();
        if (mech.isExpressionQueryMechanism() && ((ExpressionQueryMechanism)mech).getExpressionBuilder() != null) {
            this.defaultBuilder = ((ExpressionQueryMechanism)mech).getExpressionBuilder();
            return;
        }
        this.defaultBuilder = new ExpressionBuilder();
    }

    /**
     * INTERNAL:
     * return true if this query has computed its distinct value already
     */
    public boolean isDistinctComputed() {
        return getDistinctState() != UNCOMPUTED_DISTINCT;
    }

    /**
     * PUBLIC:
     * Answers if the query lock mode is known to be LOCK or LOCK_NOWAIT.
     *
     * In the case of DEFAULT_LOCK_MODE and the query reference class being a CMP entity bean,
     * at execution time LOCK, LOCK_NOWAIT, or NO_LOCK will be decided.
     * <p>
     * If a single joined attribute was configured for pessimistic locking then
     * this will return true (after first execution) as the SQL contained a
     * FOR UPDATE OF clause.
     */
    public boolean isLockQuery() {
        return getLockMode() > NO_LOCK;
    }

    /**
     * ADVANCED:
     * Answers if this query will issue any pessimistic locks.
     * <p>
     * If the lock mode is not known (DEFAULT_LOCK_MODE / descriptor specified
     * fine-grained locking) the lock mode will be determined now, to be either
     * LOCK, LOCK_NOWAIT, or NO_LOCK.
     * @see #isLockQuery()
     */
    public boolean isLockQuery(oracle.toplink.essentials.sessions.Session session) {
        checkPrePrepare((AbstractSession)session);
        return isLockQuery();
    }

    /**
     * PUBLIC:
     * Return if this is an object level read query.
     */
    public boolean isObjectLevelReadQuery() {
        return true;
    }

    /**
     * PUBLIC:
     * Queries prepare common stated in themselves.
     */
    protected boolean isPrePrepared() {
        return isPrePrepared;
    }

    /**
     * INTERNAL:
     * Answers if we are executing through a UnitOfWork and registering results.
     * This is only ever false if using the conforming without registering
     * feature.
     */
    protected boolean isRegisteringResults() {
        return ((shouldRegisterResultsInUnitOfWork() && getDescriptor().shouldRegisterResultsInUnitOfWork()) || isLockQuery());
    }

    /**
     * INTERNAL:
     * If changes are made to the query that affect the derived SQL or Call
     * parameters the query needs to be prepared again.
     * <p>
     * Automatically called internally.
     * <p>
     * The early phase of preparation is to check if this is a pessimistic
     * locking query.
     */
    protected void setIsPrePrepared(boolean isPrePrepared) {
        // Only unprepare if was prepared to begin with, prevent unpreparing during prepare.
        if (this.isPrePrepared && !isPrePrepared) {
            setIsPrepared(false);
            this.getJoinedAttributeManager().reset();
        }
        this.isPrePrepared = isPrePrepared;
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    protected void prepare() throws QueryException {
        super.prepare();
        prepareQuery();
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    protected void prePrepare() throws QueryException {
        // For bug 3136413/2610803 building the selection criteria from an EJBQL string or
        // an example object is done just in time.
        buildSelectionCriteria(session);
        checkDescriptor(session);

        // Add mapping joined attributes.
        if (getQueryMechanism().isExpressionQueryMechanism()) {
            getJoinedAttributeManager().processJoinedMappings();
        }

        // modify query for locking only if locking has not been configured
        if (isDefaultLock()) {
            setWasDefaultLockMode(true);
            ForUpdateOfClause lockingClause = null;
            if (getJoinedAttributeManager().hasJoinedExpressions()) {
                lockingClause = getJoinedAttributeManager().setupLockingClauseForJoinedExpressions(lockingClause, getSession());
            }
            if (lockingClause == null) {
                this.lockingClause = ForUpdateClause.newInstance(NO_LOCK);
            } else {
                this.lockingClause = lockingClause;
                // SPECJ: Locking not compatible with distinct for batch reading.
                dontUseDistinct();
            }
        } else if (getLockMode() == NO_LOCK) {
            setWasDefaultLockMode(true);
        }
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    protected void prepareQuery() throws QueryException {
        if ((!shouldMaintainCache()) && shouldRefreshIdentityMapResult() && (!descriptor.isAggregateCollectionDescriptor())) {
            throw QueryException.refreshNotPossibleWithoutCache(this);
        }
        if (shouldMaintainCache() && hasPartialAttributeExpressions()) {
            throw QueryException.cannotCachePartialObjects(this);
        }

        if (descriptor.isAggregateDescriptor()) {
            // Not allowed
            throw QueryException.aggregateObjectCannotBeDeletedOrWritten(descriptor, this);
        }

        // If fetch group manager is not set in the descriptor and the user attempts to use fetch group in the query dynamiclly, throw exception here.
        if ((!getDescriptor().hasFetchGroupManager()) && ((getFetchGroup() != null) || (getFetchGroupName() != null))) {
            throw QueryException.fetchGroupValidOnlyIfFetchGroupManagerInDescriptor(getDescriptor().getJavaClassName(), getName());
        }

        // Prepare fetch group if applied.
        if (getDescriptor().hasFetchGroupManager()) {
            getDescriptor().getFetchGroupManager().prepareQueryWithFetchGroup(this);
        }

        // Validate and prepare join expressions.
        if (getJoinedAttributeManager().hasJoinedExpressions()) {
            getJoinedAttributeManager().prepareJoinExpressions(getSession());
        } else {
            // If the query is being re-prepared must clear possible old cached data.
            getJoinedAttributeManager().reset();
        }
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object(s) resulting from the query.
     * If cascading is used the private parts of the objects will also be refreshed.
     */
    public void refreshIdentityMapResult() {
        setShouldRefreshIdentityMapResult(true);
    }

    /**
     * INTERNAL:
     * All objects queried via a UnitOfWork get registered here. If the query
     * went to the database.
     * <p>
     * Involves registering the query result individually and in totality, and
     * hence refreshing / conforming is done here.
     *
     * @param result may be collection (read all) or an object (read one),
     * or even a cursor. If in transaction the shared cache will
     * be bypassed, meaning the result may not be originals from the parent
     * but raw database rows.
     * @param unitOfWork the unitOfWork the result is being registered in.
     * @param arguments the original arguments/parameters passed to the query
     * execution. Used by conforming
     * @param buildDirectlyFromRows If in transaction must construct
     * a registered result from raw database rows.
     *
     * @return the final (conformed, refreshed, wrapped) UnitOfWork query result
     */
    public abstract Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows);

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void resetDistinct() {
        setDistinctState(UNCOMPUTED_DISTINCT);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Additional fields can be added to a query. This is used in m-m bacth reading to bring back the key from the join table.
     */
    public void setAdditionalFields(Vector additionalFields) {
        this.additionalFields = additionalFields;
    }

    /**
     * PUBLIC:
     * Set the cache usage.
     * By default only primary key read object queries will first check the cache before accessing the database.
     * Any query can be configure to query against the cache completely, by key or ignore the cache check.
     * <p>Valid values are:
     * <ul>
     * <li> DoNotCheckCache - The query does not check the cache but accesses the database, the cache will still be maintain.
     * <li> CheckCacheByExactPrimaryKey - If the query is exactly and only on the object's primary key the cache will be checked.
     * <li> CheckCacheByPrimaryKey - If the query contains the primary key and possible other values the cache will be checked.
     * <li> CheckCacheThenDatabase - The whole cache will be checked to see if there is any object matching the query, if not the database will be accessed.
     * <li> CheckCacheOnly - The whole cache will be checked to see if there is any object matching the query, if not null or an empty collection is returned.
     * <li> ConformResultsAgainstUnitOfWork - The results will be checked againtst the changes within the unit of work and object no longer matching or deleted will be remove, matching new objects will also be added.
     * <li> shouldCheckDescriptorForCacheUsage - This setting functions like CheckCacheByPrimaryKey, except checks the appropriate descriptor's
     * shouldDisableCacheHits setting when querying on the cache.
      * </lu>
     */
    public void setCacheUsage(int cacheUsage) {
        this.cacheUsage = cacheUsage;
    }

    /**
     * INTERNAL:
     * Set the descriptor for the query.
     */
    public void setDescriptor(ClassDescriptor descriptor) {
        super.setDescriptor(descriptor);
        if (this.joinedAttributeManager != null){
            this.joinedAttributeManager.setDescriptor(descriptor);
        }
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void setDistinctState(short distinctState) {
        this.distinctState = distinctState;
    }

    /**
     * INTERNAL:
     * Set the the time this query went to the database.
     */
    public void setExecutionTime(long executionTime) {
        this.executionTime = executionTime;
    }

    /**
     * PUBLIC:
     * Set the InMemoryQueryIndirectionPolicy for this query
     */
    //Feature 2297
    public void setInMemoryQueryIndirectionPolicy(InMemoryQueryIndirectionPolicy inMemoryQueryIndirectionPolicy) {
        //Bug2862302 Backwards compatibility. This makes sure 9.0.3 and any older version project xml don't break
        if (inMemoryQueryIndirectionPolicy != null) {
            this.inMemoryQueryIndirectionPolicy = inMemoryQueryIndirectionPolicy;
        }
    }

    /**
     * PUBLIC:
     * Sets whether this is a pessimistically locking query.
     * <ul>
     * <li>ObjectBuildingQuery.LOCK: SELECT .... FOR UPDATE issued.
     * <li>ObjectBuildingQuery.LOCK_NOWAIT: SELECT .... FOR UPDATE NO WAIT issued.
     * <li>ObjectBuildingQuery.NO_LOCK: no pessimistic locking.
     * <li>ObjectBuildingQuery.DEFAULT_LOCK_MODE (default) and you have a CMP descriptor:
     * fine grained locking will occur.
     * </ul>
     * <p>Fine Grained Locking: On execution the reference class
     * and those of all joined attributes will be checked. If any of these have a
     * PessimisticLockingPolicy set on their descriptor, they will be locked in a
     * SELECT ... FOR UPDATE OF ... {NO WAIT}. Issues fewer locks
     * and avoids setting the lock mode on each query.
     * <p>Example:<code>readAllQuery.setSelectionCriteria(employee.get("address").equal("Ottawa"));</code>
     * <ul><li>LOCK: all employees in Ottawa and all referenced Ottawa addresses will be locked.
     * <li>DEFAULT_LOCK_MODE: if address is a joined attribute, and only address has a pessimistic
     * locking policy, only referenced Ottawa addresses will be locked.
     * </ul>
     * @see oracle.toplink.essentials.descriptors.PessimisticLockingPolicy
     */
    public void setLockMode(short lockMode) {
        if ((lockMode == LOCK) || (lockMode == LOCK_NOWAIT)) {
            lockingClause = ForUpdateClause.newInstance(lockMode);
            setShouldRefreshIdentityMapResult(true);
        } else if (lockMode == NO_LOCK) {
            lockingClause = ForUpdateClause.newInstance(lockMode);
        } else {
            lockingClause = null;
            setIsPrePrepared(false);
        }
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Return the attributes that must be joined, but not fetched, that is,
     * do not trigger the value holder.
     */
    protected void setNonFetchJoinAttributeExpressions(Vector nonFetchJoinExpressions) {
        this.nonFetchJoinAttributeExpressions = nonFetchJoinExpressions;
    }

    /**
     * INTERNAL:
     * The locking clause contains a list of expressions representing which
     * objects are to be locked by the query.
     * <p>
     * Use for even finer grained control over what is and is not locked by
     * a particular query.
     */
    public void setLockingClause(ForUpdateClause clause) {
        if (clause.isForUpdateOfClause()) {
            this.lockingClause = clause;
            setIsPrePrepared(false);
        } else {
            setLockMode(clause.getLockMode());
        }
    }

    public void setEJBQLString(String ejbqlString) {
        super.setEJBQLString(ejbqlString);
        setIsPrePrepared(false);
    }

    /**
     * REQUIRED:
     * Set the reference class for the query.
     */
    // TODO This method is left in so the javadoc remains, but is actually implemented on
    // super. We should reconsider this.
    public void setReferenceClass(Class aClass) {
        super.setReferenceClass(aClass);
    }

    /**
     * INTERNAL:
     * Set the reference class for the query.
     */
    // TODO This method is left in so the javadoc remains, but is actually implemented on
    // super. We should reconsider this.
    public void setReferenceClassName(String aClass) {
        super.setReferenceClassName(aClass);
    }

    public void setSelectionCriteria(Expression expression) {
        super.setSelectionCriteria(expression);
        if ((expression != null) && (defaultBuilder != null)) {
            // For flashback: Must make sure expression and defaultBuilder always in sync.
            ExpressionBuilder newBuilder = expression.getBuilder();
            if ( (newBuilder != defaultBuilder) && (newBuilder.getQueryClass() == null || newBuilder.getQueryClass().equals(defaultBuilder.getQueryClass()) )){
                defaultBuilder = newBuilder;
            }
        }
    }

    /**
     * INTERNAL:
     * Set if the rows for the result of the query should also be returned using a complex query result.
     * @see ComplexQueryResult
     */
    public void setShouldIncludeData(boolean shouldIncludeData) {
        this.shouldIncludeData = shouldIncludeData;
    }

    /**
     * PUBLIC:
     * Set if the attributes of the object(s) resulting from the query should be refreshed.
     * If cascading is used the private parts of the objects will also be refreshed.
     */
    public void setShouldRefreshIdentityMapResult(boolean shouldRefreshIdentityMapResult) {
        this.shouldRefreshIdentityMapResult = shouldRefreshIdentityMapResult;
    }

    /**
     * INTERNAL:
     * Set to false to have queries conform to a UnitOfWork without registering
     * any additional objects not already in that UnitOfWork.
     * @see #shouldRegisterResultsInUnitOfWork
     * @bug 2612601
     */
    public void setShouldRegisterResultsInUnitOfWork(boolean shouldRegisterResultsInUnitOfWork) {
        this.shouldRegisterResultsInUnitOfWork = shouldRegisterResultsInUnitOfWork;
    }

    /**
     * PUBLIC:
     * Return if cache should be checked.
     */
    public boolean shouldCheckCacheOnly() {
        return getCacheUsage() == CheckCacheOnly;
    }

    /**
     * PUBLIC:
     * Return whether the descriptor's disableCacheHits setting should be checked prior
     * to querying the cache.
     */
    public boolean shouldCheckDescriptorForCacheUsage() {
        return getCacheUsage() == UseDescriptorSetting;
    }

    /**
     * PUBLIC:
     * Should the results will be checked against the changes within the unit of work and object no longer matching or deleted will be remove, matching new objects will also be added..
     */
    public boolean shouldConformResultsInUnitOfWork() {
        return getCacheUsage() == ConformResultsInUnitOfWork;
    }

    /**
     * INTERNAL:
     * return true if this query should use a distinct
     */
    public boolean shouldDistinctBeUsed() {
        return getDistinctState() == USE_DISTINCT;
    }

    /**
     * INTERNAL:
     * Return if the rows for the result of the query should also be returned using a complex query result.
     * @see ComplexQueryResult
     */
    public boolean shouldIncludeData() {
        return shouldIncludeData;
    }

    /**
     * INTERNAL:
     * Allows one to do conforming in a UnitOfWork without registering.
     * Queries executed on a UnitOfWork will only return working copies for objects
     * that have already been registered.
     * <p>Extreme care should be taken in using this feature, for a user will
     * get back a mix of registered and original (unregistered) objects.
     * <p>Best used with a WrapperPolicy where invoking on an object will trigger
     * its registration (CMP). Without a WrapperPolicy {_at_link oracle.toplink.essentials.sessions.UnitOfWork#registerExistingObject registerExistingObject}
     * should be called on any object that you intend to change.
     * @return true by default.
     * @see #setShouldRegisterResultsInUnitOfWork(boolean)
     * @see oracle.toplink.essentials.publicinterface.Descriptor#shouldRegisterResultsInUnitOfWork()
     * @bug 2612601
     */
    public boolean shouldRegisterResultsInUnitOfWork() {
        return shouldRegisterResultsInUnitOfWork;
    }

    /**
     * INTERNAL:
     * Return if this is a full object query, not partial nor fetch group.
     */
    public boolean shouldReadAllMappings() {
        return (!hasPartialAttributeExpressions()) && (!hasFetchGroupAttributeExpressions());
    }
    
    /**
     * INTERNAL:
     * Check if the mapping is part of the partial attributes.
     */
    public boolean shouldReadMapping(DatabaseMapping mapping) {
        if ((!hasPartialAttributeExpressions()) && (!hasFetchGroupAttributeExpressions())) {
            return true;
        }

        // bug 3659145
        if (hasFetchGroupAttributeExpressions()) {
            return isFetchGroupAttribute(mapping.getAttributeName());
        }

        return true;
    }

    /**
     * PUBLIC:
     * Set to a boolean. When set means refresh the instance
     * variables of referenceObject from the database.
     */
    public boolean shouldRefreshIdentityMapResult() {
        return shouldRefreshIdentityMapResult;
    }

    public String toString() {
        if (getReferenceClass() == null) {
            return super.toString();
        }
        return Helper.getShortClassName(getClass()) + "(" + getReferenceClass().getName() + ")";
    }

    /**
     * ADVANCED:
     * Used for CMP only. This allows users to indicate whether cmp finders executed
     * at the beginning of a transaction should always be run against a UnitOfWork.
     * Defaults to true.
     * <p>
     * If set to false, then UnitOfWork allocation will be deferred until a business
     * method (including creates/removes) or finder with shouldProcessResultsInUnitOfWork == true
     * is invoked. Any finder executed before such a time, will do so against the
     * underlying ServerSession. Forcing finder execution to always go through a
     * UnitOfWork means the results will be cloned and cached in the UnitOfWork up
     * front. This is desired when the results will be accessed in the same transaction.
     * <p>
     * Note that finders executed with an unspecified transaction context will never
     * be executed against a UnitOfWork, even if this setting is true. This case may happen
     * with the NotSupported, Never, and Supports attributes.
     */
    public void setShouldProcessResultsInUnitOfWork(boolean processResultsInUnitOfWork) {
        this.shouldProcessResultsInUnitOfWork = processResultsInUnitOfWork;
    }

    /**
     * ADVANCED:
     * Used for CMP only. Indicates whether cmp finders executed at the beginning
     * of a transaction should always be run against a UnitOfWork.
     * Defaults to true.
     * <p>
     * If set to false, then UnitOfWork allocation will be deferred until a business
     * method (including creates/removes) or finder with shouldProcessResultsInUnitOfWork == true
     * is invoked. Any finder executed before such a time, will do so against the
     * underlying ServerSession. Forcing finder execution to always go through a
     * UnitOfWork means the results will be cloned and cached in the UnitOfWork up
     * front. This is desired when the results will be accessed in the same transaction.
     * <p>
     * Note that finders executed with an unspecified transaction context will never
     * be executed against a UnitOfWork, even if this setting is true. This case may happen
     * with the NotSupported, Never, and Supports attributes.
     */
    public boolean shouldProcessResultsInUnitOfWork() {
        return this.shouldProcessResultsInUnitOfWork;
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void useDistinct() {
        setDistinctState(USE_DISTINCT);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
    * INTERNAL:
    * Helper method that checks if clone has been locked with uow.
    */
    public boolean isClonePessimisticLocked(Object clone, UnitOfWorkImpl uow) {
        return false;
    }

    /**
     * INTERNAL:
     * Helper method that records clone with uow if query is pessimistic locking.
     */
    public void recordCloneForPessimisticLocking(Object clone, UnitOfWorkImpl uow) {
        if ((isLockQuery()) && lockingClause.isReferenceClassLocked()) {
            uow.addPessimisticLockedClone(clone);
        }
    }

    /**
    * INTERNAL: Helper method to determine the default mode. If true and quey has a pessimistic locking policy,
    * locking will be configured according to the pessimistic locking policy.
    */
    public boolean isDefaultLock() {
        return (lockingClause == null);
    }

    /**
     * Return the fetch group set in the query.
     * If a fetch group is not explicitly set in the query, default fetch group optionally defined in the decsiptor
     * would be used, unless the user explicitly calls query.setShouldUseDefaultFetchGroup(false).
     */
    public FetchGroup getFetchGroup() {
        return fetchGroup;
    }

    /**
     * INTERNAL:
     * Initialize fetch group
     */
    public void initializeFetchGroup() {
        if (fetchGroup != null) {
            //fetch group already set.
            return;
        }

        //not explicitly set dynamically fetch group
        if (fetchGroupName != null) {//set pre-defined named group
            fetchGroup = getDescriptor().getFetchGroupManager().getFetchGroup(fetchGroupName);
            if (fetchGroup == null) {
                //named fetch group is not defined in the descriptor
                throw QueryException.fetchGroupNotDefinedInDescriptor(fetchGroupName);
            }
        } else {//not set fecth group at all
            //use the default fetch group if not explicitly turned off
            if (shouldUseDefaultFetchGroup()) {
                fetchGroup = getDescriptor().getDefaultFetchGroup();
            }
        }
    }

    /**
     * Set a dynamic (use case) fetch group to the query.
     */
    public void setFetchGroup(FetchGroup newFetchGroup) {
        fetchGroup = newFetchGroup;
    }

    /**
     * Set a descriptor-level pre-defined named fetch group to the query.
     */
    public void setFetchGroupName(String groupName) {
        //nullify the fecth group refernce as one query can only has one fetch group.
        fetchGroup = null;
        fetchGroupName = groupName;
    }

    /**
     * Return the fetch group name set in the query.
     */
    public String getFetchGroupName() {
        return fetchGroupName;
    }

    /**
     * Return false if the query does not use the default fetch group defined in the descriptor level.
     */
    public boolean shouldUseDefaultFetchGroup() {
        return shouldUseDefaultFetchGroup;
    }

    /**
     * Set false if the user does not want to use the default fetch group defined in the descriptor level.
     */
    public void setShouldUseDefaultFetchGroup(boolean shouldUseDefaultFetchGroup) {
        this.shouldUseDefaultFetchGroup = shouldUseDefaultFetchGroup;
    }

    /**
     * INTERNAL:
     * Return if fetch group attributes.
     */
    public boolean hasFetchGroupAttributeExpressions() {
        return (getFetchGroup() != null) && (getFetchGroup().hasFetchGroupAttributeExpressions());
    }

    /**
     * INTERNAL:
     * Return if fetch group attribute.
     */
    public boolean isFetchGroupAttribute(String attributeName) {
        if (getFetchGroup() == null) {
            //every attribute is fetched already
            return true;
        }
        return getFetchGroup().getAttributes().contains(attributeName);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.indirection.ValueHolderInterface;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.identitymaps.CacheKey;
import oracle.toplink.essentials.internal.indirection.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: Abstract class for 1:1, varibale 1:1 and reference mappings
 */
public abstract class ObjectReferenceMapping extends ForeignReferenceMapping {

    /** Keeps track if any of the fields are foreign keys. */
    protected boolean isForeignKeyRelationship;

    /** Keeps track of which fields are foreign keys on a per field basis (can have mixed foreign key relationships). */
    protected Vector<DatabaseField> foreignKeyFields;

    protected ObjectReferenceMapping() {
        super();
    }

    /**
     * INTERNAL:
     * Used during building the backup shallow copy to copy the vector without re-registering the target objects.
     * For 1-1 or ref the reference is from the clone so it is already registered.
     */
    public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
        return attributeValue;
    }

    /**
     * INTERNAL:
     * Require for cloning, the part must be cloned.
     * Ignore the objects, use the attribute value.
     */
    public Object buildCloneForPartObject(Object attributeValue, Object original, Object clone, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        // Optimize registration to knowledge of existence.
        if (isExisting) {
            return unitOfWork.registerExistingObject(attributeValue);
        } else {
            // Not known wether existing or not.
            return unitOfWork.registerObject(attributeValue);
        }
    }

    /**
     * INTERNAL:
     * Copy of the attribute of the object.
     * This is NOT used for unit of work but for templatizing an object.
     */
    public void buildCopy(Object copy, Object original, ObjectCopyingPolicy policy) {
        Object attributeValue = getRealAttributeValueFromObject(original, policy.getSession());
        if ((attributeValue != null) && (policy.shouldCascadeAllParts() || (policy.shouldCascadePrivateParts() && isPrivateOwned()))) {
            attributeValue = policy.getSession().copyObject(attributeValue, policy);
        } else if (attributeValue != null) {
            // Check for copy of part, i.e. back reference.
            Object copyValue = policy.getCopies().get(attributeValue);
            if (copyValue != null) {
                attributeValue = copyValue;
            }
        }
        setRealAttributeValueInObject(copy, attributeValue);
    }

    /**
     * INTERNAL:
     * This method was created in VisualAge.
     * @return prototype.changeset.ChangeRecord
     */
    public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
        Object cloneAttribute = null;
        Object backUpAttribute = null;

        cloneAttribute = getAttributeValueFromObject(clone);

        if (!owner.isNew()) {
            backUpAttribute = getAttributeValueFromObject(backUp);
            if ((backUpAttribute == null) && (cloneAttribute == null)) {
                return null;
            }
        }

        if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            //the clone's valueholder was never triggered so there will be no change
            return null;
        }
        Object cloneAttributeValue = null;
        Object backUpAttributeValue = null;

        if (cloneAttribute != null) {
            cloneAttributeValue = getRealAttributeValueFromObject(clone, session);
        }
        if (backUpAttribute != null) {
            backUpAttributeValue = getRealAttributeValueFromObject(backUp, session);
        }

        if ((cloneAttributeValue == backUpAttributeValue) && (!owner.isNew())) {// if it is new record the value
            return null;
        }

        ObjectReferenceChangeRecord record = internalBuildChangeRecord(cloneAttributeValue, owner, session);
        if (!owner.isNew()) {
            record.setOldValue(backUpAttributeValue);
        }
        return record;
    }

    /**
     * INTERNAL:
     * Directly build a change record based on the newValue without comparison
     */
    public ObjectReferenceChangeRecord internalBuildChangeRecord(Object newValue, ObjectChangeSet owner, AbstractSession session) {
        ObjectReferenceChangeRecord changeRecord = new ObjectReferenceChangeRecord(owner);
        changeRecord.setAttribute(getAttributeName());
        changeRecord.setMapping(this);
        setNewValueInChangeRecord(newValue, changeRecord, owner, session);
        return changeRecord;
    }

    /**
     * INTERNAL:
     * Set the newValue in the change record
     */
    public void setNewValueInChangeRecord(Object newValue, ObjectReferenceChangeRecord changeRecord, ObjectChangeSet owner, AbstractSession session) {
        if (newValue != null) {
            // Bug 2612571 - added more flexible manner of getting descriptor
            ObjectChangeSet newSet = getDescriptorForTarget(newValue, session).getObjectBuilder().createObjectChangeSet(newValue, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session);
            changeRecord.setNewValue(newSet);
        } else {
            changeRecord.setNewValue(null);
        }
    }

    /**
     * INTERNAL:
     * Compare the references of the two objects are the same, not the objects themselves.
     * Used for independent relationships.
     * This is used for testing and validation purposes.
     */
    protected boolean compareObjectsWithoutPrivateOwned(Object firstObject, Object secondObject, AbstractSession session) {
        Object firstReferencedObject = getRealAttributeValueFromObject(firstObject, session);
        Object secondReferencedObject = getRealAttributeValueFromObject(secondObject, session);

        if ((firstReferencedObject == null) && (secondReferencedObject == null)) {
            return true;
        }

        if ((firstReferencedObject == null) || (secondReferencedObject == null)) {
            return false;
        }

        Vector firstKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(firstReferencedObject, session);
        Vector secondKey = getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(secondReferencedObject, session);

        for (int index = 0; index < firstKey.size(); index++) {
            Object firstValue = firstKey.elementAt(index);
            Object secondValue = secondKey.elementAt(index);

            if (!((firstValue == null) && (secondValue == null))) {
                if ((firstValue == null) || (secondValue == null)) {
                    return false;
                }
                if (!firstValue.equals(secondValue)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * INTERNAL:
     * Compare the references of the two objects are the same, and the objects themselves are the same.
     * Used for private relationships.
     * This is used for testing and validation purposes.
     */
    protected boolean compareObjectsWithPrivateOwned(Object firstObject, Object secondObject, AbstractSession session) {
        Object firstPrivateObject = getRealAttributeValueFromObject(firstObject, session);
        Object secondPrivateObject = getRealAttributeValueFromObject(secondObject, session);

        return session.compareObjects(firstPrivateObject, secondPrivateObject);
    }

    /**
     * INTERNAL:
     * Return a descriptor for the target of this mapping
     * @see oracle.toplink.essentials.mappings.VariableOneToOneMapping
     * Bug 2612571
     */
    public ClassDescriptor getDescriptorForTarget(Object object, AbstractSession session) {
        return session.getDescriptor(object);
    }

    /**
     * INTERNAL:
     * Object reference must unwrap the reference object if required.
     */
    public Object getRealAttributeValueFromObject(Object object, AbstractSession session) {
        Object value = super.getRealAttributeValueFromObject(object, session);
        value = getReferenceDescriptor().getObjectBuilder().unwrapObject(value, session);

        return value;
    }

    /**
     * INTERNAL:
     * Related mapping should implement this method to return true.
     */
    public boolean isObjectReferenceMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Iterate on the attribute value.
     * The value holder has already been processed.
     */
    public void iterateOnRealAttributeValue(DescriptorIterator iterator, Object realAttributeValue) {
        // This may be wrapped as the caller in iterate on foreign reference does not unwrap as the type is generic.
        Object unwrappedAttributeValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(realAttributeValue, iterator.getSession());
        iterator.iterateReferenceObjectForMapping(unwrappedAttributeValue, this);
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object. Which is the original from the parent UnitOfWork
     */
    public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager) {
        Object targetValueOfSource = null;

        // The target object must be completely merged before setting it otherwise
        // another thread can pick up the partial object.
        if (shouldMergeCascadeParts(mergeManager)) {
            ObjectChangeSet set = (ObjectChangeSet)((ObjectReferenceChangeRecord)changeRecord).getNewValue();
            if (set != null) {
                if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
                    //Let's try and find it first. We may have merged it allready. In which case merge
                    //changes will stop the recursion
                    targetValueOfSource = set.getTargetVersionOfSourceObject(mergeManager.getSession(), false);
                    if ((targetValueOfSource == null) && (set.isNew() || set.isAggregate()) && set.containsChangesFromSynchronization()) {
                        if (!mergeManager.getObjectsAlreadyMerged().containsKey(set)) {
                            // if we haven't merged this object allready then build a new object
                            // otherwise leave it as null which will stop the recursion
                            // CR 2855
                            // CR 3424 Need to build the right instance based on class type instead of refernceDescriptor
                            Class objectClass = set.getClassType(mergeManager.getSession());
                            targetValueOfSource = mergeManager.getSession().getDescriptor(objectClass).getObjectBuilder().buildNewInstance();
                            //Store the changeset to prevent us from creating this new object again
                            mergeManager.getObjectsAlreadyMerged().put(set, targetValueOfSource);
                        } else {
                            //CR 4012
                            //we have all ready created the object, must be in a cyclic
                            //merge on a new object so get it out of the allreadymerged collection
                            targetValueOfSource = mergeManager.getObjectsAlreadyMerged().get(set);
                        }
                    } else {
                        // If We have not found it anywhere else load it from the database
                        targetValueOfSource = set.getTargetVersionOfSourceObject(mergeManager.getSession(), true);
                    }
                    if (set.containsChangesFromSynchronization()) {
                        mergeManager.mergeChanges(targetValueOfSource, set);
                    }
                    //bug:3604593 - ensure reference not changed source is invalidated if target object not found
                    if (targetValueOfSource ==null)
                    {
                      mergeManager.getSession().getIdentityMapAccessorInstance().invalidateObject(target);
                      return;
                    }
                } else {
                    mergeManager.mergeChanges(set.getUnitOfWorkClone(), set);
                }
            }
        }
        if ((targetValueOfSource == null) && (((ObjectReferenceChangeRecord)changeRecord).getNewValue() != null)) {
            targetValueOfSource = ((ObjectChangeSet)((ObjectReferenceChangeRecord)changeRecord).getNewValue()).getTargetVersionOfSourceObject(mergeManager.getSession());
        }

        // Register new object in nested units of work must not be registered into the parent,
        // so this records them in the merge to parent case.
        if (isPrivateOwned() && (source != null)) {
            mergeManager.registerRemovedNewObjectIfRequired(getRealAttributeValueFromObject(source, mergeManager.getSession()));
        }

        targetValueOfSource = getReferenceDescriptor().getObjectBuilder().wrapObject(targetValueOfSource, mergeManager.getSession());
        setRealAttributeValueInObject(target, targetValueOfSource);
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     */
    public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) {
        if (isTargetUnInitialized) {
            // This will happen if the target object was removed from the cache before the commit was attempted
            if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) {
                setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), mergeManager.getSession()));
                return;
            }
        }
        if (!shouldMergeCascadeReference(mergeManager)) {
            // This is only going to happen on mergeClone, and we should not attempt to merge the reference
            return;
        }
        if (mergeManager.shouldMergeOriginalIntoWorkingCopy()) {
            if (!isAttributeValueInstantiated(target)) {
                // This will occur when the clone's value has not been instantiated yet and we do not need
                // the refresh that attribute
                return;
            }
        } else if (!isAttributeValueInstantiated(source)) {
            // I am merging from a clone into an original. No need to do merge if the attribute was never
            // modified
            return;
        }

        Object valueOfSource = getRealAttributeValueFromObject(source, mergeManager.getSession());

        Object targetValueOfSource = null;

        // The target object must be completely merged before setting it otherwise
        // another thread can pick up the partial object.
        if (shouldMergeCascadeParts(mergeManager) && (valueOfSource != null)) {
            if ((mergeManager.getSession().isUnitOfWork()) && (((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet() != null)) {
                // If it is a unit of work, we have to check if I have a change Set fot this object
                mergeManager.mergeChanges(mergeManager.getObjectToMerge(valueOfSource), (ObjectChangeSet)((UnitOfWorkChangeSet)((UnitOfWorkImpl)mergeManager.getSession()).getUnitOfWorkChangeSet()).getObjectChangeSetForClone(valueOfSource));
            } else {
                mergeManager.mergeChanges(mergeManager.getObjectToMerge(valueOfSource), null);
            }
        }

        if (valueOfSource != null) {
            // Need to do this after merge so that an object exists in the database
            targetValueOfSource = mergeManager.getTargetVersionOfSourceObject(valueOfSource);
        }

        if (this.getDescriptor().getObjectChangePolicy().isObjectChangeTrackingPolicy()) {
            // Object level or attribute level so lets see if we need to raise the event?
            Object valueOfTarget = getRealAttributeValueFromObject(target, mergeManager.getSession());
            if ( valueOfTarget != targetValueOfSource ) { //equality comparison cause both are uow clones
                this.getDescriptor().getObjectChangePolicy().raiseInternalPropertyChangeEvent(target, getAttributeName(), valueOfTarget, targetValueOfSource);
            }
        }
 
        targetValueOfSource = getReferenceDescriptor().getObjectBuilder().wrapObject(targetValueOfSource, mergeManager.getSession());
        setRealAttributeValueInObject(target, targetValueOfSource);
    }

    /**
     * INTERNAL:
     * Return all the fields populated by this mapping, these are foreign keys only.
     */
    protected Vector<DatabaseField> collectFields() {
        return getForeignKeyFields();
    }

    /**
     * INTERNAL:
     * Returns the foreign key names associated with the mapping.
     * These are the fields that will be populated by the 1-1 mapping when writting.
     */
    public Vector<DatabaseField> getForeignKeyFields() {
        return foreignKeyFields;
    }

    /**
    * INTERNAL:
    * Set the foreign key fields associated with the mapping.
    * These are the fields that will be populated by the 1-1 mapping when writting.
    */
    protected void setForeignKeyFields(Vector<DatabaseField> foreignKeyFields) {
        this.foreignKeyFields = foreignKeyFields;
        if (!foreignKeyFields.isEmpty()) {
            setIsForeignKeyRelationship(true);
        }
    }

    /**
     * INTERNAL:
     * Return if the 1-1 mapping has a foreign key dependency to its target.
     * This is true if any of the foreign key fields are true foreign keys,
     * i.e. populated on write from the targets primary key.
     */
    public boolean isForeignKeyRelationship() {
        return isForeignKeyRelationship;
    }

    /**
     * INTERNAL:
     * Set if the 1-1 mapping has a foreign key dependency to its target.
     * This is true if any of the foreign key fields are true foreign keys,
     * i.e. populated on write from the targets primary key.
     */
    public void setIsForeignKeyRelationship(boolean isForeignKeyRelationship) {
        this.isForeignKeyRelationship = isForeignKeyRelationship;
    }

    /**
     * INTERNAL:
     * Insert privately owned parts
     */
    public void preInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (isForeignKeyRelationship()) {
            insert(query);
        }
    }

    /**
     * INTERNAL:
     * Reads the private owned object.
     */
    protected Object readPrivateOwnedForObject(ObjectLevelModifyQuery modifyQuery) throws DatabaseException {
        if (modifyQuery.getSession().isUnitOfWork()) {
            if (modifyQuery.getObjectChangeSet() != null) {
                ObjectReferenceChangeRecord record = (ObjectReferenceChangeRecord) modifyQuery.getObjectChangeSet().getChangesForAttributeNamed(getAttributeName());
                if (record != null) {
                    return record.getOldValue();
                }
            } else { // Old commit.
                return getRealAttributeValueFromObject(modifyQuery.getBackupClone(), modifyQuery.getSession());
            }
        }
        
        return null;
    }

    /**
     * INTERNAL:
     * Update privately owned parts
     */
    public void preUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (!isAttributeValueInstantiated(query.getObject())) {
            return;
        }

        if (isPrivateOwned()) {
            Object objectInDatabase = readPrivateOwnedForObject(query);
            if (objectInDatabase != null) {
                query.setProperty(this, objectInDatabase);
            }
        }

        if (!isForeignKeyRelationship()) {
            return;
        }

        update(query);
    }

    /**
     * INTERNAL:
     * Delete the referenced objects.
     */
    public void postDelete(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        // Deletion takes place according the the cascading policy
        if (!shouldObjectModifyCascadeToParts(query)) {
            return;
        }

        // There are no dependencies after this object has been deleted successfully!
        if (query.shouldCascadeOnlyDependentParts() && !isPrivateOwned()) {
            return;
        }

        Object object = query.getProperty(this);

        // The object is stored in the query by preDeleteForObjectUsing(...).
        if (isForeignKeyRelationship()) {
            if (object != null) {
                query.removeProperty(this);

                //if the query is being passed from an aggregate collection descriptor then
                // The delete will have been cascaded at update time. This will cause sub objects
                // to be ignored, and real only classes to throw exceptions.
                // If it is an aggregate Collection then delay deletes until they should be deleted
                //CR 2811
                if (query.isCascadeOfAggregateDelete()) {
                    query.getSession().getCommitManager().addObjectToDelete(object);
                } else {
                    DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
                    deleteQuery.setObject(object);
                    deleteQuery.setCascadePolicy(query.getCascadePolicy());
                    query.getSession().executeQuery(deleteQuery);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Insert privately owned parts
     */
    public void postInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (!isForeignKeyRelationship()) {
            insert(query);
        }
    }

    /**
     * INTERNAL:
     * Update privately owned parts
     */
    public void postUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (!isAttributeValueInstantiated(query.getObject())) {
            return;
        }

        if (!isForeignKeyRelationship()) {
            update(query);
        }

        // If a private owned reference was changed the old value will be set on the query as a property.
        Object objectInDatabase = query.getProperty(this);
        if (objectInDatabase != null) {
            query.removeProperty(this);
        } else {
            return;
        }

        // If there is no change (old commit), it must be determined if the value changed.
        if (query.getObjectChangeSet() == null) {
            Object objectInMemory = getRealAttributeValueFromObject(query.getObject(), query.getSession());
    
            // delete the object in the database if it is no more a referenced object.
            if (objectInDatabase != objectInMemory) {
                CacheKey cacheKeyForObjectInDatabase = null;
                CacheKey cacheKeyForObjectInMemory = new CacheKey(new Vector());
    
                cacheKeyForObjectInDatabase = new CacheKey(getPrimaryKeyForObject(objectInDatabase, query.getSession()));
    
                if (objectInMemory != null) {
                    cacheKeyForObjectInMemory = new CacheKey(getPrimaryKeyForObject(objectInMemory, query.getSession()));
                }
    
                if (cacheKeysAreEqual(cacheKeyForObjectInDatabase, cacheKeyForObjectInMemory)) {
                    return;
                }
            } else {
                return;
            }
        }
            
        if (query.shouldCascadeOnlyDependentParts()) {
            query.getSession().getCommitManager().addObjectToDelete(objectInDatabase);
        } else {
            query.getSession().deleteObject(objectInDatabase);
        }
    }

    /**
     * INTERNAL:
     * Delete the referenced objects.
     */
    public void preDelete(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        // Deletion takes place according the the cascading policy
        if (!shouldObjectModifyCascadeToPartsForPreDelete(query)) {
            return;
        }

        // Get the referenced objects.
        Object objectInMemory = getRealAttributeValueFromObject(query.getObject(), query.getSession());
        Object objectFromDatabase = null;

        // Because the value in memory may have been changed we check the previous value or database value.
        objectFromDatabase = readPrivateOwnedForObject(query);

        // If the value was changed, both values must be deleted (uow will have inserted the new one).
        if ((objectFromDatabase != null) && (objectFromDatabase != objectInMemory)) {
            // Also check pk as may not be maintaining identity.
            CacheKey cacheKeyForObjectInDatabase = null;
            CacheKey cacheKeyForObjectInMemory = new CacheKey(new Vector());

            cacheKeyForObjectInDatabase = new CacheKey(getPrimaryKeyForObject(objectFromDatabase, query.getSession()));

            if (objectInMemory != null) {
                cacheKeyForObjectInMemory = new CacheKey(getPrimaryKeyForObject(objectInMemory, query.getSession()));
            }
            if (!cacheKeysAreEqual(cacheKeyForObjectInMemory, cacheKeyForObjectInDatabase)) {
                if (objectFromDatabase != null) {
                    // Make sure only objects sheduled for deletion are deleted
                    if (shouldObjectDeleteCascadeToPart(query, objectFromDatabase)) {
                        DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
                        deleteQuery.setObject(objectFromDatabase);
                        deleteQuery.setCascadePolicy(query.getCascadePolicy());
                        query.getSession().executeQuery(deleteQuery);
                    }
                }
            }
        }

        if (!isForeignKeyRelationship()) {
            if (objectInMemory != null) {
                // Make sure only objects sheduled for deletion are deleted
                if (shouldObjectDeleteCascadeToPart(query, objectInMemory)) {
                    DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
                    deleteQuery.setObject(objectInMemory);
                    deleteQuery.setCascadePolicy(query.getCascadePolicy());
                    query.getSession().executeQuery(deleteQuery);
                }
            }
        } else {
            // The actual deletion of part takes place in postDelete(...).
            if (objectInMemory != null) {
                query.setProperty(this, objectInMemory);
            }
        }
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){
        Object attributeValue = getAttributeValueFromObject(object);
        if (attributeValue != null && this.isCascadeRemove() ){
            Object reference = getIndirectionPolicy().getRealAttributeValueFromObject(object, attributeValue);
            if (reference != null && (! visitedObjects.contains(reference)) ){
                visitedObjects.put(reference, reference);
                uow.performRemove(reference, visitedObjects);
            }
        }
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects){
        Object attributeValue = getAttributeValueFromObject(object);
        if (attributeValue != null && this.isCascadePersist() && getIndirectionPolicy().objectIsInstantiated(attributeValue)){
            Object reference = getIndirectionPolicy().getRealAttributeValueFromObject(object, attributeValue);
            uow.registerNewObjectForPersist(reference, visitedObjects);
        }
    }

    /**
     * INTERNAL:
     */
    protected boolean cacheKeysAreEqual(CacheKey cacheKey1, CacheKey cacheKey2) {
        return cacheKey1.equals(cacheKey2);
    }

    /**
     * INTERNAL:
     */
    protected Vector getPrimaryKeyForObject(Object object, AbstractSession session) {
        return getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, session);
    }

    /**
     * INTERNAL:
     * The returns if the mapping has any constraint dependencies, such as foreign keys and join tables.
     */
    public boolean hasConstraintDependency() {
        return isForeignKeyRelationship();
    }

    /**
     * INTERNAL:
     * Builder the unit of work value holder.
     * @param buildDirectlyFromRow indicates that we are building the clone directly
     * from a row as opposed to building the original from the row, putting it in
     * the shared cache, and then cloning the original.
     */
    public UnitOfWorkValueHolder createUnitOfWorkValueHolder(ValueHolderInterface attributeValue, Object original, Object clone, AbstractRecord row, UnitOfWorkImpl unitOfWork, boolean buildDirectlyFromRow) {
        UnitOfWorkQueryValueHolder valueHolder = null;
        if ((row == null) && (getDescriptor().getObjectBuilder().isPrimaryKeyMapping(this))) {
            // The row must be built if a primary key mapping for remote case.
            AbstractRecord rowFromTargetObject = extractPrimaryKeyRowForSourceObject(original, unitOfWork);
            valueHolder = new UnitOfWorkQueryValueHolder(attributeValue, clone, this, rowFromTargetObject, unitOfWork);
        } else {
            valueHolder = new UnitOfWorkQueryValueHolder(attributeValue, clone, this, row, unitOfWork);
        }

        // In case of joined attributes it so happens that the attributeValue
        // contains a registered clone, as valueFromRow was called with a
        // UnitOfWork. So switch the values.
        // Note that this UOW valueholder starts off as instantiated but that
        // is fine, for the reality is that it is.
        if (buildDirectlyFromRow && attributeValue.isInstantiated()) {
            Object cloneAttributeValue = attributeValue.getValue();
            valueHolder.privilegedSetValue(cloneAttributeValue);
            valueHolder.setInstantiated();

            // PERF: Do not modify the original value-holder, it is never used.
        }
        return valueHolder;
    }

    /**
     * INTERNAL:
     * Extract the reference pk for rvh usage in remote model.
     */
    public AbstractRecord extractPrimaryKeyRowForSourceObject(Object domainObject, AbstractSession session) {
        AbstractRecord databaseRow = getDescriptor().getObjectBuilder().createRecord();
        writeFromObjectIntoRow(domainObject, databaseRow, session);
        return databaseRow;
    }

    /**
     * INTERNAL:
     * Extract the reference pk for rvh usage in remote model.
     */
    public Vector extractPrimaryKeysForReferenceObject(Object domainObject, AbstractSession session) {
        return getIndirectionPolicy().extractPrimaryKeyForReferenceObject(getAttributeValueFromObject(domainObject), session);
    }

    /**
     * INTERNAL:
     * Return the primary key for the reference object (i.e. the object
     * object referenced by domainObject and specified by mapping).
     * This key will be used by a RemoteValueHolder.
     */
    public Vector extractPrimaryKeysForReferenceObjectFromRow(AbstractRecord row) {
        return new Vector(1);
    }

    /**
     * INTERNAL:
     * Extract the reference pk for rvh usage in remote model.
     */
    public Vector extractPrimaryKeysFromRealReferenceObject(Object object, AbstractSession session) {
        if (object == null) {
            return new Vector(1);
        } else {
            Object implementation = getReferenceDescriptor().getObjectBuilder().unwrapObject(object, session);
            return getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(implementation, session);
        }
    }

    /**
     * INTERNAL:
     * Insert the referenced objects.
     */
    protected void insert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        // Insertion takes place according the the cascading policy
        if (!shouldObjectModifyCascadeToParts(query)) {
            return;
        }

        // Get the privately owned parts
        Object object = getRealAttributeValueFromObject(query.getObject(), query.getSession());

        if (object == null) {
            return;
        }
        ObjectChangeSet changeSet = query.getObjectChangeSet();
        if (changeSet != null) {
            ObjectReferenceChangeRecord changeRecord = (ObjectReferenceChangeRecord)query.getObjectChangeSet().getChangesForAttributeNamed(getAttributeName());
            if (changeRecord != null) {
                changeSet = (ObjectChangeSet)changeRecord.getNewValue();
            } else {
                // no changeRecord no reference.
                return;
            }
        } else {
            UnitOfWorkChangeSet uowChangeSet = null;

            // get changeSet for referenced object. Could get it from the changeRecord but that would as much work
            if (query.getSession().isUnitOfWork() && (((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet() != null)) {
                uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet();
                changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(object);
            }
        }

        WriteObjectQuery writeQuery = null;
        if (isPrivateOwned()) {
            // no identity check needed for private owned
            writeQuery = new InsertObjectQuery();
        } else {
            writeQuery = new WriteObjectQuery();
        }
        writeQuery.setObject(object);
        writeQuery.setObjectChangeSet(changeSet);
        writeQuery.setCascadePolicy(query.getCascadePolicy());
        query.getSession().executeQuery(writeQuery);
    }

    /**
     * INTERNAL:
     * Update the private owned part.
     */
    protected void update(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (!shouldObjectModifyCascadeToParts(query)) {
            return;
        }

        // If objects are not instantiated that means they are not changed.
        if (!isAttributeValueInstantiated(query.getObject())) {
            return;
        }

        // Get the privately owned parts in the memory
        Object object = getRealAttributeValueFromObject(query.getObject(), query.getSession());
        if (object != null) {
            ObjectChangeSet changeSet = query.getObjectChangeSet();
            if (changeSet != null) {
                ObjectReferenceChangeRecord changeRecord = (ObjectReferenceChangeRecord)query.getObjectChangeSet().getChangesForAttributeNamed(getAttributeName());
                if (changeRecord != null) {
                    changeSet = (ObjectChangeSet)changeRecord.getNewValue();
                } else {
                    // no changeRecord no change to reference.
                    return;
                }
            } else {
                UnitOfWorkChangeSet uowChangeSet = null;

                // get changeSet for referenced object. Could get it from the changeRecord but that would as much work
                if (query.getSession().isUnitOfWork() && (((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet() != null)) {
                    uowChangeSet = (UnitOfWorkChangeSet)((UnitOfWorkImpl)query.getSession()).getUnitOfWorkChangeSet();
                    changeSet = (ObjectChangeSet)uowChangeSet.getObjectChangeSetForClone(object);
                }
            }
            WriteObjectQuery writeQuery = new WriteObjectQuery();
            writeQuery.setObject(object);
            writeQuery.setObjectChangeSet(changeSet);
            writeQuery.setCascadePolicy(query.getCascadePolicy());
            query.getSession().executeQuery(writeQuery);
        }
    }

    /**
     * INTERNAL:
     * To verify if the specified object is deleted or not.
     */
    public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException {
        if (isPrivateOwned()) {
            Object attributeValue = getRealAttributeValueFromObject(object, session);

            if (attributeValue != null) {
                return session.verifyDelete(attributeValue);
            }
        }

        return true;
    }

    /**
     * INTERNAL:
     * Get a value from the object and set that in the respective field of the row.
     * But before that check if the reference object is instantiated or not.
     */
    public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord databaseRow) {
        Object object = query.getObject();
        AbstractSession session = query.getSession();

        if (!isAttributeValueInstantiated(object)) {
            return;
        }

        if (session.isUnitOfWork()) {
            if (compareObjectsWithoutPrivateOwned(query.getBackupClone(), object, session)) {
                return;
            }
        }

        writeFromObjectIntoRow(object, databaseRow, session);
    }

    /**
     * INTERNAL:
     * Get a value from the object and set that in the respective field of the row.
     */
    public void writeFromObjectIntoRowForWhereClause(ObjectLevelModifyQuery query, AbstractRecord databaseRow) {
        if (isReadOnly()) {
            return;
        }

        if (query.isDeleteObjectQuery()) {
            writeFromObjectIntoRow(query.getObject(), databaseRow, query.getSession());
        } else {
            // If the original was never instantiated the backup clone has a ValueHolder of null
            // so for this case we must extract from the original object.
            if (isAttributeValueInstantiated(query.getObject())) {
                writeFromObjectIntoRow(query.getBackupClone(), databaseRow, query.getSession());
            } else {
                writeFromObjectIntoRow(query.getObject(), databaseRow, query.getSession());
            }
        }
    }
    
    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return true;
    }
    
    /**
     * INTERNAL:
     * Either create a new change record or update the change record with the new value.
     * This is used by attribute change tracking.
     */
    public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        // Must ensure values are unwrapped.
        Object unwrappedNewValue = newValue;
        Object unwrappedOldValue = oldValue;
        if (newValue != null) {
            unwrappedNewValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(newValue, uow);
        }
        if (oldValue != null) {
            unwrappedOldValue = getReferenceDescriptor().getObjectBuilder().unwrapObject(oldValue, uow);
        }
        ObjectReferenceChangeRecord changeRecord = (ObjectReferenceChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (changeRecord == null) {
            changeRecord = internalBuildChangeRecord(unwrappedNewValue, objectChangeSet, uow);
            changeRecord.setOldValue(unwrappedOldValue);
            objectChangeSet.addChange(changeRecord);
            
        } else {
            setNewValueInChangeRecord(unwrappedNewValue, changeRecord, objectChangeSet, uow);
        }
    }

    /**
     * INTERNAL:
     * Directly build a change record without comparison
     */
    public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) {
        return internalBuildChangeRecord(getRealAttributeValueFromObject(clone, session), owner, session);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.queryframework;

import java.util.List;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Collections;
import java.util.Enumeration;
import java.util.IdentityHashMap;
import java.util.ListIterator;
import oracle.toplink.essentials.exceptions.QueryException;
import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;
import oracle.toplink.essentials.internal.sessions.MergeManager;
import oracle.toplink.essentials.internal.sessions.ObjectChangeSet;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet;
import oracle.toplink.essentials.internal.sessions.CollectionChangeRecord;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: A OrderedListContainerPolicy is ContainerPolicy whose
 * container class implements the List interface and is ordered by an @OrderBy.
 * <p>
 * <p><b>Responsibilities</b>:
 * Provide the functionality to operate on an instance of a List.
 *
 * @see ContainerPolicy
 * @see CollectionContainerPolicy
 * @see ListContainerPolicy
 */
public class OrderedListContainerPolicy extends ListContainerPolicy {
    /**
     * INTERNAL:
     * Construct a new policy.
     */
    public OrderedListContainerPolicy() {
        super();
    }
    
    /**
     * INTERNAL:
     * Construct a new policy for the specified class.
     */
    public OrderedListContainerPolicy(Class containerClass) {
        super(containerClass);
    }
    
    /**
     * INTERNAL:
     * Construct a new policy for the specified class name.
     */
    public OrderedListContainerPolicy(String containerClassName) {
        super(containerClassName);
    }
    
    /**
     * INTERNAL:
     * Add element into a container which implements the List interface.
     * Add at a particular index.
     */
    protected void addIntoAtIndex(Integer index, Object object, Object container, AbstractSession session) {
        if (hasElementDescriptor()) {
            object = getElementDescriptor().getObjectBuilder().wrapObject(object, session);
        }
        
        try {
            if (index == null || (index.intValue() > sizeFor(container))) {
                // The index can be larger than the size on a merge,
                // so should be added to the end, it may also be null if the
                // index was unknown, such as an event using the add API.
                ((List)container).add(object);
            } else {
                ((List)container).add(index.intValue(), object);
            }
        } catch (ClassCastException ex1) {
            throw QueryException.cannotAddElement(object, container, ex1);
        } catch (IllegalArgumentException ex2) {
            throw QueryException.cannotAddElement(object, container, ex2);
        } catch (UnsupportedOperationException ex3) {
            throw QueryException.cannotAddElement(object, container, ex3);
        }
    }
    
    /**
     * INTERNAL:
     * This method is used to calculate the differences between two collections.
     * This algorithm is a work in progress. It works great and all, but like
     * anything, you can always make it better.
     */
    public void compareCollectionsForChange(Object oldList, Object newList, CollectionChangeRecord changeRecord, AbstractSession session, ClassDescriptor referenceDescriptor) {
        Vector orderedObjectsToAdd = new Vector();
        Hashtable indicesToRemove = new Hashtable();
        Hashtable oldListIndexValue = new Hashtable();
        IdentityHashMap oldListValueIndex = new IdentityHashMap();
        IdentityHashMap objectsToAdd = new IdentityHashMap();
        HardIdentityHashtable newListValueIndex = new HardIdentityHashtable();
        
        // Step 1 - Go through the old list.
        if (oldList != null) {
            ListIterator iterator = iteratorFor(oldList);
        
            while (iterator.hasNext()) {
                Integer index = new Integer(iterator.nextIndex());
                Object value = iterator.next();
                oldListValueIndex.put(value, index);
                oldListIndexValue.put(index, value);
                indicesToRemove.put(index, index);
            }
        }
            
        // Step 2 - Go though the new list.
        if (newList != null) {
            // Step i - Gather the list info.
            ListIterator iterator = iteratorFor(newList);
            while (iterator.hasNext()) {
                newListValueIndex.put(iterator.next(), new Integer(iterator.previousIndex()));
            }
        
            // Step ii - Go through the new list again.
            int index = 0;
            int offset = 0;
            iterator = iteratorFor(newList);
            while (iterator.hasNext()) {
                index = iterator.nextIndex();
                Object currentObject = iterator.next();
            
                // If value is null then nothing can be done with it.
                if (currentObject != null) {
                    if (oldListValueIndex.containsKey(currentObject)) {
                        int oldIndex = ((Integer) oldListValueIndex.get(currentObject)).intValue();
                        oldListValueIndex.remove(currentObject);
                    
                        if (index == oldIndex) {
                            indicesToRemove.remove(new Integer(oldIndex));
                            offset = 0; // Reset the offset, assume we're back on track.
                        } else if (index == (oldIndex + offset)) {
                            // We're in the right spot according to the offset.
                            indicesToRemove.remove(new Integer(oldIndex));
                        } else {
                            // Time to be clever and figure out why we're not in the right spot!
                            int movedObjects = 0;
                            int deletedObjects = 0;
                            boolean moved = true;
                        
                            if (oldIndex < index) {
                                ++offset;
                            } else {
                                for (int i = oldIndex - 1; i >= index; i--) {
                                    Object oldObject = oldListIndexValue.get(new Integer(i));
                                    if (newListValueIndex.containsKey(oldObject)) {
                                        ++movedObjects;
                                    } else {
                                        ++deletedObjects;
                                    }
                                }
                            
                                if (index == ((oldIndex + offset) - deletedObjects)) {
                                    // We fell into place because of deleted objects.
                                    offset = offset - deletedObjects;
                                    moved = false;
                                } else if (movedObjects > 1) {
                                    // Assume we moved down, bumping everyone by one.
                                    ++offset;
                                } else {
                                    // Assume we moved down unless the object that was
                                    // here before is directly beside us.
                                    Object oldObject = oldListIndexValue.get(new Integer(index));
                                
                                    if (newListValueIndex.containsKey(oldObject)) {
                                        if ((((Integer) newListValueIndex.get(oldObject)).intValue() - index) > 1) {
                                            moved = false; // Assume the old object moved up.
                                            --offset;
                                        }
                                    }
                                }
                            }
                        
                            if (moved) {
                                // Add ourselves to the ordered add list.
                                orderedObjectsToAdd.add(currentObject);
                            } else {
                                // Take us off the removed list.
                                indicesToRemove.remove(new Integer(oldIndex));
                            }
                        }
                    } else {
                        ++offset;
                        objectsToAdd.put(currentObject, currentObject);
                        orderedObjectsToAdd.add(currentObject);
                    }
                } else {
                    // If we find nulls we need decrease our offset.
                    offset--;
                }
            }
        }
        
        // Sort the remove indices that are left and set the data on the collection change
        // record to be processed on the merge.
        Vector orderedIndicesToRemove = new Vector(indicesToRemove.values());
        Collections.sort(orderedIndicesToRemove);
        changeRecord.addAdditionChange(objectsToAdd, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
        changeRecord.addRemoveChange(oldListValueIndex, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
        changeRecord.addOrderedAdditionChange(orderedObjectsToAdd, newListValueIndex, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
        changeRecord.addOrderedRemoveChange(orderedIndicesToRemove, oldListIndexValue, (UnitOfWorkChangeSet) changeRecord.getOwner().getUOWChangeSet(), session);
    }
    
    /**
     * INTERNAL:
     * Return an list iterator for the given container.
     */
    public ListIterator iteratorFor(Object container) {
        return ((List)container).listIterator();
    }
    
    /**
     * INTERNAL:
     * Merge changes from the source to the target object. Because this is a
     * collection mapping, values are added to or removed from the collection
     * based on the change set.
     */
    public void mergeChanges(CollectionChangeRecord changeRecord, Object valueOfTarget, boolean shouldMergeCascadeParts, MergeManager mergeManager, AbstractSession parentSession) {
        ObjectChangeSet objectChanges;
        
        synchronized (valueOfTarget) {
            // Step 1 - iterate over the removed changes and remove them from the container.
            Vector removedIndices = changeRecord.getOrderedRemoveObjectIndices();

            if (removedIndices.isEmpty()) {
                // Check if we have removed objects via a
                // simpleRemoveFromCollectionChangeRecord API call.
                Enumeration removedObjects = changeRecord.getRemoveObjectList().keys();
            
                while (removedObjects.hasMoreElements()) {
                    objectChanges = (ObjectChangeSet) removedObjects.nextElement();
                    removeFrom(objectChanges.getOldKey(), objectChanges.getTargetVersionOfSourceObject(mergeManager.getSession()), valueOfTarget, parentSession);
                    registerRemoveNewObjectIfRequired(objectChanges, mergeManager);
                }
            } else {
                for (int i = removedIndices.size() - 1; i >= 0; i--) {
                    Integer index = ((Integer) removedIndices.elementAt(i)).intValue();
                    objectChanges = (ObjectChangeSet) changeRecord.getOrderedRemoveObject(index);;
                    removeFromAtIndex(index, valueOfTarget);
                
                    // The object was actually removed and not moved.
                    if (changeRecord.getRemoveObjectList().containsKey(objectChanges)) {
                        registerRemoveNewObjectIfRequired(objectChanges, mergeManager);
                    }
                }
            }
            
            // Step 2 - iterate over the added changes and add them to the container.
            Enumeration addObjects = changeRecord.getOrderedAddObjects().elements();
            while (addObjects.hasMoreElements()) {
                objectChanges = (ObjectChangeSet) addObjects.nextElement();
                boolean objectAdded = changeRecord.getAddObjectList().containsKey(objectChanges);
                Object object = null;
                
                // The object was actually added and not moved.
                if (objectAdded && shouldMergeCascadeParts) {
                    object = mergeCascadeParts(objectChanges, mergeManager, parentSession);
                }
                
                if (object == null) {
                    // Retrieve the object to be added to the collection.
                    object = objectChanges.getTargetVersionOfSourceObject(mergeManager.getSession());
                }

                // Assume at this point the above merge will have created a new
                // object if required and that the object was actually added and
                // not moved.
                if (objectAdded && mergeManager.shouldMergeChangesIntoDistributedCache()) {
                    // Bugs 4458089 & 4454532 - check if collection contains new item before adding
                    // during merge into distributed cache
                    if (! contains(object, valueOfTarget, mergeManager.getSession())) {
                        addIntoAtIndex(changeRecord.getOrderedAddObjectIndex(objectChanges), object, valueOfTarget, mergeManager.getSession());
                    }
                } else {
                    addIntoAtIndex(changeRecord.getOrderedAddObjectIndex(objectChanges), object, valueOfTarget, mergeManager.getSession());
                }
            }
        }
    }
    
    /**
     * INTERNAL:
     */
    protected void registerRemoveNewObjectIfRequired(ObjectChangeSet objectChanges, MergeManager mergeManager) {
        if (! mergeManager.shouldMergeChangesIntoDistributedCache()) {
            mergeManager.registerRemovedNewObjectIfRequired(objectChanges.getUnitOfWorkClone());
        }
    }
    
    /**
     * INTERNAL:
     * Remove the element at the specified index.
     */
    protected void removeFromAtIndex(int index, Object container) {
        try {
            ((List) container).remove(index);
        } catch (ClassCastException ex1) {
            throw QueryException.cannotRemoveFromContainer(new Integer(index), container, this);
        } catch (IllegalArgumentException ex2) {
            throw QueryException.cannotRemoveFromContainer(new Integer(index), container, this);
        } catch (UnsupportedOperationException ex3) {
            throw QueryException.cannotRemoveFromContainer(new Integer(index), container, this);
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.ejb.cmp3.base;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import java.util.logging.Level;
import java.security.AccessController;
import java.security.PrivilegedAction;
import oracle.toplink.essentials.config.*;
import oracle.toplink.essentials.internal.helper.ReferenceMode;
import oracle.toplink.essentials.internal.localization.ExceptionLocalization;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.logging.SessionLog;

/**
 *
 * The class processes some of TopLink properties.
 * The class may be used for any properties, but it only makes sense
 * to use it in the following two cases:
 * 1. There is a set of legal values defined
 * either requiring translation (like CacheTypeProp);
 * or not (like LoggingLevelProp).
 * 2. Property is really a prefix from which the property obtained by
 * appending entity or class name (like DescriptorCustomizerProp -
 * it corresponds to "toplink.descriptor.customizer." prefix that allows to
 * define propereties like "toplink.descriptor.customizer.myPackage.MyClass").
 *
 * TopLink properties and their values defined in oracle.toplink.essentials.config package.
 *
 * To add a new property:
 * Define a new property in TopLinkProperties;
 * Add a class containing property's values if required to config package (like CacheType);
 * Alternatively values may be already defined elsewhere (like in LoggingLevelProp).
 * Add an inner class to this class extending Prop corresponding to the new property (like CacheTypeProp);
 * The first constructor parameter is property name; the second is default value;
 * In constructor
 * provide 2-dimensional value array in case the values should be translated (like CacheTypeProp);
 * in case translation is not required provide a single-dimension array (like LoggingLevelProp).
 * In inner class Prop static initializer addProp an instance of the new prop class (like addProp(new CacheTypeProp())).
 *
 * @see TopLinkProperties
 * @see CacheType
 * @see TargetDatabase
 * @see TargetServer
 *
 */
public class PropertiesHandler {
    
    /**
     * INTERNAL:
     * Gets property value from the map, if none found looks in System properties.
     * Use this to get a value for a non-prefixed property.
     * Could be used on prefixes (like "oracle.toplink.cache-type.") too,
     * but will always return null
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static String getPropertyValue(String name, Map m) {
        return Prop.getPropertyValueToApply(name, m, null, true);
    }

    public static String getPropertyValueLogDebug(String name, Map m, AbstractSession session) {
        return Prop.getPropertyValueToApply(name, m, session, true);
    }
    
    /**
     * INTERNAL:
     * Gets property value from the map, if none found looks in System properties.
     * Use this to get a value for a prefixed property:
     * for "oracle.toplink.cache-type.Employee"
     * pass "oracle.toplink.cache-type.", "Employee".
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static String getPrefixedPropertyValue(String prefix, String suffix, Map m) {
        return (String)getPrefixValues(prefix, m).get(suffix);
    }

    /**
     * INTERNAL:
     * Gets properties' values from the map, if none found looks in System properties.
     * In the returned map values keyed by suffixes.
     * Use it with prefixes (like "oracle.toplink.cache-type.").
     * Could be used on simple properties (not prefixes, too),
     * but will always return either an empty map or a map containing a single
     * value keyed by an empty String.
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static Map getPrefixValues(String prefix, Map m) {
        return Prop.getPrefixValuesToApply(prefix, m, null, true);
    }
    
    public static Map getPrefixValuesLogDebug(String prefix, Map m, AbstractSession session) {
        return Prop.getPrefixValuesToApply(prefix, m, session, true);
    }
    
    /**
     * INTERNAL:
     * Returns the default property value that should be applied.
     * Throws IllegalArgumentException in case the name doesn't correspond
     * to any property.
     */
    public static String getDefaultPropertyValue(String name) {
        return Prop.getDefaultPropertyValueToApply(name, null);
    }
    
    public static String getDefaultPropertyValueLogDebug(String name, AbstractSession session) {
        return Prop.getDefaultPropertyValueToApply(name, session);
    }
    
    /**
     * INTERNAL:
     * Gets property value from AbstractSession.getProperties() map,
     * if none found looks in its parent recursively;
     * if none is found looks in System properties.
     * Use this to get a value for a non-prefixed property.
     * Throws IllegalArgumentException in case the property value is illegal.
     */
    public static String getSessionPropertyValue(String name, AbstractSession session) {
        String value = null;
        while(value == null && session != null) {
            AbstractSession parent = session.getParent();
            // Don't use System properties as default unless session has no parent.
            // Motivation: look in all the recursive parents' properties before looking in System properties.
            value = Prop.getPropertyValueToApply(name, session.getProperties(), null, parent == null);
            session = parent;
        }
        return value;
    }

    public static String getSessionPropertyValueLogDebug(String name, AbstractSession session) {
        String value = null;
        while(value == null && session != null) {
            AbstractSession parent = session.getParent();
            // Don't use System properties as default unless session has no parent.
            // Motivation: look in all the recursive parents' properties before looking in System properties.
            value = Prop.getPropertyValueToApply(name, session.getProperties(), session, parent == null);
            session = parent;
        }
        return value;
    }
    
    /**
     * INTERNAL:
     * Empty String value indicates that the default property value
     * should be used.
     */
    protected static boolean shouldUseDefault(String value) {
        return value != null && value.length() == 0;
    }
    
    protected static abstract class Prop {
        static HashMap mainMap = new HashMap();
        Object[] valueArray;
        HashMap valueMap;
        String name;
        String defaultValue;
        String defaultValueToApply;
        boolean valueToApplyMayBeNull;
        boolean shouldReturnOriginalValueIfValueToApplyNotFound;
        
        static {
            addProp(new LoggerTypeProp());
            addProp(new LoggingLevelProp());
            addProp(new CategoryLoggingLevelProp());
            addProp(new TargetDatabaseProp());
            addProp(new TargetServerProp());
            addProp(new CacheSizeProp());
            addProp(new CacheTypeProp());
            addProp(new CacheSharedProp());
            addProp(new DescriptorCustomizerProp());
            addProp(new FlushClearCacheProp());
            addProp(new TransactionalCacheReferenceModeProp());
        }
        
        Prop(String name) {
            this.name = name;
        }
        
        Prop(String name, String defaultValue) {
            this(name);
            this.defaultValue = defaultValue;
        }

        static String getPropertyValueFromMap(String name, Map m, boolean useSystemAsDefault) {
            String value = (String)m.get(name);
            return value == null && useSystemAsDefault ? System.getProperty(name) : value;
        }
    
        // Collect all entries corresponding to the prefix name.
        // Note that entries from Map m override those from System properties.
        static Map getPrefixValuesFromMap(String name, Map m, boolean useSystemAsDefault) {
            Map mapOut = new HashMap();
            
            Iterator it;
            if(useSystemAsDefault) {
                it = (Iterator)AccessController.doPrivileged(
                    new PrivilegedAction() {
                        public Object run() {
                            return System.getProperties().entrySet().iterator();
                        }
                    }
                );
    
                while(it.hasNext()) {
                    Map.Entry entry = (Map.Entry)it.next();
                    String str = (String)entry.getKey();
                    if(str.startsWith(name)) {
                        String entityName = str.substring(name.length(), str.length());
                        mapOut.put(entityName, entry.getValue());
                    }
                }
            }
            
            it = m.entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String str = (String)entry.getKey();
                if(str.startsWith(name)) {
                    String entityName = str.substring(name.length(), str.length());
                    mapOut.put(entityName, entry.getValue());
                }
            }
            
            return mapOut;
        }
    
        static String getPropertyValue(String name, boolean shouldUseDefault, Map m, AbstractSession session, boolean useSystemAsDefault) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                // it's not our property
                return null;
            }
            String value = (String)getPropertyValueFromMap(name, m, useSystemAsDefault);
            if(value == null) {
                return null;
            }
            return prop.getValueToApply(value, shouldUseDefault, session);
        }
                
        static String getPropertyValueToApply(String name, Map m, AbstractSession session, boolean useSystemAsDefault) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                return null;
            }
            String value = (String)getPropertyValueFromMap(name, m, useSystemAsDefault);
            if(value == null) {
                return null;
            }
            return prop.getValueToApply(value, shouldUseDefault(value), session);
        }
                
        static Map getPrefixValuesToApply(String prefix, Map m, AbstractSession session, boolean useSystemAsDefault) {
            Prop prop = (Prop)mainMap.get(prefix);
            if(prop == null) {
                // prefix doesn't correspond to a Prop object - it's not our property.
                return new HashMap(0);
            }
            
            // mapps suffixes to property values
            Map mapIn = (Map)getPrefixValuesFromMap(prefix, m, useSystemAsDefault);
            if(mapIn.isEmpty()) {
                return mapIn;
            }
            
            HashMap mapOut = new HashMap(mapIn.size());
            Iterator it = mapIn.entrySet().iterator();
            while(it.hasNext()) {
                Map.Entry entry = (Map.Entry)it.next();
                String suffix = (String)entry.getKey();
                String value = (String)entry.getValue();
                mapOut.put(suffix, prop.getValueToApply(value, shouldUseDefault(value), suffix, session));
            }
            // mapps suffixes to valuesToApply
            return mapOut;
        }
        
        static String getDefaultPropertyValueToApply(String name, AbstractSession session) {
            Prop prop = (Prop)mainMap.get(name);
            if(prop == null) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("ejb30-default-for-unknown-property", new Object[]{name}));
            }
            prop.logDefault(session);
            return prop.defaultValueToApply;
        }
                
        String getValueToApply(String value, boolean shouldUseDefault, AbstractSession session) {
            return getValueToApply(value, shouldUseDefault, null, session);
        }
        
        // suffix is used only for properties-prefixes (like CacheType)
        String getValueToApply(String value, boolean shouldUseDefault, String suffix, AbstractSession session) {
            if(shouldUseDefault) {
                logDefault(session, suffix);
                return defaultValueToApply;
            }
            String valueToApply = value;
            if(valueMap != null) {
                String key = getUpperCaseString(value);
                valueToApply = (String)valueMap.get(key);
                if(valueToApply == null) {
                    boolean notFound = true;
                    if(valueToApplyMayBeNull) {
                        notFound = !valueMap.containsKey(key);
                    }
                    if(notFound) {
                        if(shouldReturnOriginalValueIfValueToApplyNotFound) {
                            valueToApply = value;
                        } else {
                            String propertyName = name;
                            if(suffix != null) {
                                propertyName = propertyName + suffix;
                            }
                            throw new IllegalArgumentException(ExceptionLocalization.buildMessage("ejb30-illegal-property-value", new Object[]{propertyName, getPrintValue(value)}));
                        }
                    }
                }
            }
            String logValue = TopLinkProperties.getOverriddenLogStringForProperty(name);
            if (logValue != null){
                log(session, logValue, logValue, suffix);
            } else {
                log(session, value, valueToApply, suffix);
            }
            return valueToApply;
        }

        static String getUpperCaseString(String value) {
            if(value != null) {
                return value.toUpperCase();
            } else {
                return null;
            }
        }
        static String getPrintValue(String value) {
            if(value != null) {
                return value;
            } else {
                return "null";
            }
        }
        void initialize() {
            if(valueArray != null) {
                valueMap = new HashMap(valueArray.length);
                if(valueArray instanceof Object[][]) {
                    Object[][] valueArray2 = (Object[][])valueArray;
                    for(int i=0; i<valueArray2.length; i++) {
                        valueMap.put(getUpperCaseString((String)valueArray2[i][0]), valueArray2[i][1]);
                        if(valueArray2[i][1] == null) {
                            valueToApplyMayBeNull = true;
                        }
                    }
                } else {
                    for(int i=0; i<valueArray.length; i++) {
                        valueMap.put(getUpperCaseString((String)valueArray[i]), valueArray[i]);
                        if(valueArray[i] == null) {
                            valueToApplyMayBeNull = true;
                        }
                    }
                }
                defaultValueToApply = (String)valueMap.get(getUpperCaseString(defaultValue));
            } else {
                defaultValueToApply = defaultValue;
            }
        }

        void logDefault(AbstractSession session) {
            logDefault(session, null);
        }
        
        void logDefault(AbstractSession session, String suffix) {
            if(session != null) {
                String propertyName = name;
                if(suffix != null) {
                    propertyName = propertyName + suffix;
                }
                if(defaultValue != defaultValueToApply) {
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "handler_property_value_default", new Object[]{propertyName, defaultValue, defaultValueToApply});
                } else {
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "property_value_default", new Object[]{propertyName, defaultValue});
                }
            }
        }
        
        void log(AbstractSession session, String value, String valueToApply, String suffix) {
            if(session != null) {
                String propertyName = name;
                if(suffix != null) {
                    propertyName = propertyName + suffix;
                }
                if(value != valueToApply) {
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "handler_property_value_specified", new Object[]{propertyName, value, valueToApply});
                } else {
                    session.log(SessionLog.FINEST, SessionLog.PROPERTIES, "property_value_specified", new Object[]{propertyName, value});
                }
            }
        }
        
        static void addProp(Prop prop) {
            prop.initialize();
            mainMap.put(prop.name, prop);
        }
    }

    protected static class LoggerTypeProp extends Prop {
        LoggerTypeProp() {
            super(TopLinkProperties.LOGGING_LOGGER, LoggerType.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            String pcg = "oracle.toplink.essentials.logging.";
            valueArray = new Object[][] {
                {LoggerType.DefaultLogger, pcg + "DefaultSessionLog"},
                {LoggerType.JavaLogger, pcg + "JavaLog"}
            };
        }
    }

    protected static class LoggingLevelProp extends Prop {
        LoggingLevelProp() {
            super(TopLinkProperties.LOGGING_LEVEL, Level.INFO.getName());
            valueArray = new Object[] {
                Level.OFF.getName(),
                Level.SEVERE.getName(),
                Level.OFF.getName(),
                Level.WARNING.getName(),
                Level.INFO.getName(),
                Level.CONFIG.getName(),
                Level.FINE.getName(),
                Level.FINER.getName(),
                Level.FINEST.getName(),
                Level.ALL.getName()
            };
        }
    }

    protected static class CategoryLoggingLevelProp extends Prop {
        CategoryLoggingLevelProp() {
            super(TopLinkProperties.CATEGORY_LOGGING_LEVEL_);
            valueArray = new Object[] {
                Level.OFF.getName(),
                Level.SEVERE.getName(),
                Level.OFF.getName(),
                Level.WARNING.getName(),
                Level.INFO.getName(),
                Level.CONFIG.getName(),
                Level.FINE.getName(),
                Level.FINER.getName(),
                Level.FINEST.getName(),
                Level.ALL.getName()
            };
        }
    }

    protected static class TargetDatabaseProp extends Prop {
        TargetDatabaseProp() {
            super(TopLinkProperties.TARGET_DATABASE, TargetDatabase.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            String pcg = "oracle.toplink.essentials.platform.database.";
            valueArray = new Object[][] {
                {TargetDatabase.Auto, pcg + "DatabasePlatform"},
                {TargetDatabase.Oracle, pcg + "oracle.OraclePlatform"},
                {TargetDatabase.Attunity, pcg + "AttunityPlatform"},
                {TargetDatabase.Cloudscape, pcg + "CloudscapePlatform"},
                {TargetDatabase.Database, pcg + "DatabasePlatform"},
                {TargetDatabase.DB2Mainframe, pcg + "DB2MainframePlatform"},
                {TargetDatabase.DB2, pcg + "DB2Platform"},
                {TargetDatabase.DBase, pcg + "DBasePlatform"},
                {TargetDatabase.Derby, pcg + "DerbyPlatform"},
                {TargetDatabase.HSQL, pcg + "HSQLPlatform"},
                {TargetDatabase.Informix, pcg + "InformixPlatform"},
                {TargetDatabase.JavaDB, pcg + "JavaDBPlatform"},
                {TargetDatabase.MySQL4, pcg + "MySQL4Platform"},
                {TargetDatabase.PointBase, pcg + "PointBasePlatform"},
                {TargetDatabase.PostgreSQL, pcg + "PostgreSQLPlatform"},
                {TargetDatabase.SQLAnyWhere, pcg + "SQLAnyWherePlatform"},
                {TargetDatabase.SQLServer, pcg + "SQLServerPlatform"},
                {TargetDatabase.Sybase, pcg + "SybasePlatform"},
                {TargetDatabase.TimesTen, pcg + "TimesTenPlatform"}
            };
        }
    }

    protected static class TargetServerProp extends Prop {
        TargetServerProp() {
            super(TopLinkProperties.TARGET_SERVER, TargetServer.DEFAULT);
            this.shouldReturnOriginalValueIfValueToApplyNotFound = true;
            String pcg = "oracle.toplink.essentials.platform.server.";
            valueArray = new Object[][] {
                {TargetServer.None, pcg + "NoServerPlatform"},
                {TargetServer.OC4J_10_1_3, pcg + "oc4j.Oc4jPlatform"},
                {TargetServer.SunAS9, pcg + "sunas.SunAS9ServerPlatform"}
            };
        }
    }

    protected static class CacheSizeProp extends Prop {
        CacheSizeProp() {
            super(TopLinkProperties.CACHE_SIZE_, Integer.toString(1000));
        }
    }

    protected static class TransactionalCacheReferenceModeProp extends Prop{
        TransactionalCacheReferenceModeProp() {
            super(TopLinkProperties.PERSISTENCE_CONTEXT_REFERENCE_MODE,ReferenceMode.HARD.name());
            valueArray = new Object[]{
                ReferenceMode.HARD.name(),
                ReferenceMode.WEAK.name()
            };
        }
        
    }
    protected static class CacheTypeProp extends Prop {
        CacheTypeProp() {
            super(TopLinkProperties.CACHE_TYPE_, CacheType.DEFAULT);
            String pcg = "oracle.toplink.essentials.internal.identitymaps.";
            valueArray = new Object[][] {
                {CacheType.Weak, pcg + "WeakIdentityMap"},
                {CacheType.SoftWeak, pcg + "SoftCacheWeakIdentityMap"},
                {CacheType.HardWeak, pcg + "HardCacheWeakIdentityMap"},
                {CacheType.Full, pcg + "FullIdentityMap"},
                {CacheType.NONE, pcg + "NoIdentityMap"}
            };
        }
    }

    protected static class CacheSharedProp extends Prop {
        CacheSharedProp() {
            super(TopLinkProperties.CACHE_SHARED_, "false");
            valueArray = new Object[] {
                "true",
                "false"
            };
        }
    }

    protected static class DescriptorCustomizerProp extends Prop {
        DescriptorCustomizerProp() {
            super(TopLinkProperties.DESCRIPTOR_CUSTOMIZER_);
        }
    }

    protected static class FlushClearCacheProp extends Prop {
        FlushClearCacheProp() {
            super(TopLinkProperties.FLUSH_CLEAR_CACHE, FlushClearCache.DEFAULT);
            valueArray = new Object[] {
                FlushClearCache.Merge,
                FlushClearCache.Drop,
                FlushClearCache.DropInvalidate
            };
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package oracle.toplink.essentials.queryframework;

import java.util.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;

/**set
 * <p><b>Purpose</b>:
 * Concrete class for all read queries involving a collection of objects.
 * <p>
 * <p><b>Responsibilities</b>:
 * Return a container of the objects generated by the query.
 * Implements the inheritance feature when dealing with abstract descriptors
 *
 * @author Yvon Lavoie
 * @since TOPLink/Java 1.0
 */
public class ReadAllQuery extends ObjectLevelReadQuery {

    /** Used for ordering support. */
    protected Vector orderByExpressions;

    /** Used for collection and stream support. */
    protected ContainerPolicy containerPolicy;

    /**
     * PUBLIC:
     * Return a new read all query.
     * A reference class must be specified before execution.
     * It is better to provide the class and expression builder on construction to esnure a single expression builder is used.
     * If no selection criteria is specified this will read all objects of the class from the database.
     */
    public ReadAllQuery() {
        super();
        this.useCollectionClass(ClassConstants.Vector_class);
    }

    /**
     * PUBLIC:
     * Return a new read all query.
     * It is better to provide the class and expression builder on construction to esnure a single expression builder is used.
     * If no selection criteria is specified this will read all objects of the class from the database.
     */
    public ReadAllQuery(Class classToRead) {
        this();
        setReferenceClass(classToRead);
    }

    /**
     * PUBLIC:
     * Return a new read all query for the class and the selection criteria.
     */
    public ReadAllQuery(Class classToRead, Expression selectionCriteria) {
        this();
        setReferenceClass(classToRead);
        setSelectionCriteria(selectionCriteria);
    }

    /**
     * PUBLIC:
     * Return a new read all query for the class.
     * The expression builder must be used for all associated expressions used with the query.
     */
    public ReadAllQuery(Class classToRead, ExpressionBuilder builder) {
        this();
        this.defaultBuilder = builder;
        setReferenceClass(classToRead);
    }

    /**
     * PUBLIC:
     * Return a new read all query.
     * The call represents a database interaction such as SQL, Stored Procedure.
     */
    public ReadAllQuery(Class classToRead, Call call) {
        this();
        setReferenceClass(classToRead);
        setCall(call);
    }

    /**
     * PUBLIC:
     * The expression builder should be provide on creation to ensure only one is used.
     */
    public ReadAllQuery(ExpressionBuilder builder) {
        this();
        this.defaultBuilder = builder;
    }

    /**
     * PUBLIC:
     * Create a read all query with the database call.
     */
    public ReadAllQuery(Call call) {
        this();
        setCall(call);
    }

    /**
     * PUBLIC:
     * Order the query results by the object's attribute or query key name.
     */
    public void addAscendingOrdering(String queryKeyName) {
        addOrdering(getExpressionBuilder().get(queryKeyName).ascending());
    }

    /**
     * PUBLIC:
     * Order the query results by the object's attribute or query key name.
     */
    public void addDescendingOrdering(String queryKeyName) {
        addOrdering(getExpressionBuilder().get(queryKeyName).descending());
    }

    /**
     * PUBLIC:
     * Add the ordering expression. This allows for ordering across relationships or functions.
     * Example: readAllQuery.addOrdering(expBuilder.get("address").get("city").toUpperCase().descending())
     */
    public void addOrdering(Expression orderingExpression) {
        getOrderByExpressions().addElement(orderingExpression);
        //Bug2804042 Must un-prepare if prepared as the SQL may change.
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * The cache check is done before the prepare as a hit will not require the work to be done.
     */
    protected Object checkEarlyReturnImpl(AbstractSession session, AbstractRecord translationRow) {
        // Check for in-memory only query.
        if (shouldCheckCacheOnly()) {
            // assert !isReportQuery();
            if (shouldUseWrapperPolicy()) {
                getContainerPolicy().setElementDescriptor(getDescriptor());
            }

            // PERF: Fixed to not query each unit of work cache (is not conforming),
            // avoid hashtable and primary key indexing.
            // At some point we may need to support some kind of in-memory with conforming option,
            // but we do not currently allow this.
            AbstractSession rootSession = session;
            while (rootSession.isUnitOfWork()) {
                rootSession = ((UnitOfWorkImpl)rootSession).getParent();
            }
            Vector allCachedVector = rootSession.getIdentityMapAccessor().getAllFromIdentityMap(getSelectionCriteria(), getReferenceClass(), translationRow, getInMemoryQueryIndirectionPolicy(), false);

            // Must ensure that all of the objects returned are correctly registered in the unit of work.
            if (session.isUnitOfWork()) {
                allCachedVector = ((UnitOfWorkImpl)session).registerAllObjects(allCachedVector);
            }

            return getContainerPolicy().buildContainerFromVector(allCachedVector, session);
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Check to see if a custom query should be used for this query.
     * This is done before the query is copied and prepared/executed.
     * null means there is none.
     */
    protected DatabaseQuery checkForCustomQuery(AbstractSession session, AbstractRecord translationRow) {
        checkDescriptor(session);

        // check if user defined a custom query
        if ((!isUserDefined()) && isExpressionQuery() && (getSelectionCriteria() == null) && (!hasOrderByExpressions()) && (getDescriptor().getQueryManager().hasReadAllQuery())) {
            return getDescriptor().getQueryManager().getReadAllQuery();
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Clone the query.
     */
    public Object clone() {
        ReadAllQuery cloneQuery = (ReadAllQuery)super.clone();

        // Don't use setters as that will trigger unprepare
        if (hasOrderByExpressions()) {
            cloneQuery.orderByExpressions = (Vector)getOrderByExpressions().clone();
        }
        cloneQuery.containerPolicy = getContainerPolicy().clone(cloneQuery);

        return cloneQuery;
    }

    /**
     * INTERNAL:
     * Conform the result if specified.
     */
    protected Object conformResult(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
        ContainerPolicy cp;
        HardIdentityHashtable indexedInterimResult = null;
        Expression selectionCriteriaClone = null;

        if (getSelectionCriteria() != null) {
            selectionCriteriaClone = (Expression)getSelectionCriteria().clone();
            selectionCriteriaClone.getBuilder().setSession(unitOfWork.getRootSession(null));
            selectionCriteriaClone.getBuilder().setQueryClass(getReferenceClass());
        }
        cp = getContainerPolicy();

        // This code is now a great deal different... For one, registration is done
        // as part of conforming. Also, this should only be called if one actually
        // is conforming.
        // First scan the UnitOfWork for conforming instances.
        // This will walk through the entire cache of registered objects.
        // Let p be objects from result not in the cache.
        // Let c be objects from cache.
        // Presently p intersect c = empty set, but later p subset c.
        // By checking cache now doesConform will be called p fewer times.
        indexedInterimResult = unitOfWork.scanForConformingInstances(selectionCriteriaClone, getReferenceClass(), arguments, this);

        // Now conform the result from the database.
        // Remove any deleted or changed objects that no longer conform.
        // Deletes will only work for simple queries, queries with or's or anyof's may not return
        // correct results when untriggered indirection is in the model.
        Vector fromDatabase = null;

        // When building directly from rows, one of the performance benefits
        // is that we no longer have to wrap and then unwrap the originals.
        // result is just a vector, not a container of wrapped originals.
        if (buildDirectlyFromRows) {
            Vector rows = (Vector)result;
            Set identitySet = null;
            fromDatabase = new Vector(rows.size());
            for (int i = 0; i < rows.size(); i++) {
                Object object = rows.elementAt(i);

                // null is placed in the row collection for 1-m joining to filter duplicate rows.
                if (object != null) {
                    Object clone = conformIndividualResult(object, unitOfWork, arguments, selectionCriteriaClone, indexedInterimResult, buildDirectlyFromRows);
                    if (clone != null) {
                        // Avoid duplicates if -m joining was used and a cache hit occured.
                        if (getJoinedAttributeManager().isToManyJoin()) {
                            if (identitySet == null) {
                                identitySet = new TopLinkIdentityHashSet(rows.size());
                            }
                            if (!identitySet.contains(clone)) {
                                identitySet.add(clone);
                                fromDatabase.addElement(clone);
                            }
                        } else {
                            fromDatabase.addElement(clone);
                        }
                    }
                }
            }
        } else {
            fromDatabase = new Vector(cp.sizeFor(result));
            AbstractSession sessionToUse = unitOfWork.getParent();
            for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) {
                Object object = cp.next(iter, sessionToUse);
                Object clone = conformIndividualResult(object, unitOfWork, arguments, selectionCriteriaClone, indexedInterimResult, buildDirectlyFromRows);
                if (clone != null) {
                    fromDatabase.addElement(clone);
                }
            }
        }

        // Now add the unwrapped conforming instances into an appropriate container.
        // Wrapping is done automatically.
        // Make sure a vector of exactly the right size is returned.
        //
        Object conformedResult = cp.containerInstance(indexedInterimResult.size() + fromDatabase.size());
        Object eachClone;
        for (Enumeration enumtr = indexedInterimResult.elements(); enumtr.hasMoreElements();) {
            eachClone = enumtr.nextElement();
            cp.addInto(eachClone, conformedResult, unitOfWork);
        }
        for (Enumeration enumtr = fromDatabase.elements(); enumtr.hasMoreElements();) {
            eachClone = enumtr.nextElement();
            cp.addInto(eachClone, conformedResult, unitOfWork);
        }
        return conformedResult;
    }

    /**
     * INTERNAL:
     * Execute the query.
     * Get the rows and build the object from the rows.
     * @exception DatabaseException - an error has occurred on the database
     * @return java.lang.Object collection of objects resulting from execution of query.
     */
    protected Object executeObjectLevelReadQuery() throws DatabaseException {
        Object result = null;
        if (getContainerPolicy().overridesRead()) {
            return getContainerPolicy().execute();
        }

        Vector rows = getQueryMechanism().selectAllRows();
        setExecutionTime(System.currentTimeMillis());
        // If using -m joins, must set all rows.
        if (getJoinedAttributeManager().isToManyJoin()) {
            getJoinedAttributeManager().setDataResults(rows, getSession());
        }

        if (getSession().isUnitOfWork()) {
            result = registerResultInUnitOfWork(rows, (UnitOfWorkImpl)getSession(), getTranslationRow(), true);//
        } else {
            result = getQueryMechanism().buildObjectsFromRows(rows);
        }

        if (shouldIncludeData()) {
            ComplexQueryResult complexResult = new ComplexQueryResult();
            complexResult.setResult(result);
            complexResult.setData(rows);
            return complexResult;
        }

        return result;
    }

    /**
     * INTERNAL:
     * Return the query's container policy.
     * @return oracle.toplink.essentials.internal.queryframework.ContainerPolicy
     */
    public ContainerPolicy getContainerPolicy() {
        return containerPolicy;
    }

    /**
     * INTERNAL:
     * Return the order expressions for the query.
     */
    public Vector getOrderByExpressions() {
        if (orderByExpressions == null) {
            orderByExpressions = new Vector();
        }
        return orderByExpressions;
    }

    /**
     * INTERNAL:
     * The order bys are lazy initialized to conserv space.
     */
    public boolean hasOrderByExpressions() {
        return orderByExpressions != null;
    }

    /**
     * INTERNAL:
     * Verify that we have hierarchical query expressions
     */
    public boolean hasHierarchicalExpressions() {
        return false;
    }

    /**
     * INTERNAL:
     * Return true is this query has batching
     */
    public boolean hasBatchReadAttributes() {
        return false;
    }

    /**
     * INTERNAL:
     * Return if the attribute is specified for batch reading.
     */
    public boolean isAttributeBatchRead(String attributeName) {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this is a read all query.
     */
    public boolean isReadAllQuery() {
        return true;
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    protected void prepare() throws QueryException {
        super.prepare();

        getContainerPolicy().prepare(this, getSession());

        if (getContainerPolicy().overridesRead()) {
            return;
        }

        prepareSelectAllRows();
    }

    /**
     * INTERNAL:
     * Set the properties needed to be cascaded into the custom query.
     */
    protected void prepareCustomQuery(DatabaseQuery customQuery) {
        ReadAllQuery customReadQuery = (ReadAllQuery)customQuery;
        customReadQuery.setContainerPolicy(getContainerPolicy());
        customReadQuery.setCascadePolicy(getCascadePolicy());
        customReadQuery.setShouldRefreshIdentityMapResult(shouldRefreshIdentityMapResult());
        customReadQuery.setShouldMaintainCache(shouldMaintainCache());
        customReadQuery.setShouldUseWrapperPolicy(shouldUseWrapperPolicy());
    }

    /**
     * INTERNAL:
     * Prepare the receiver for execution in a session.
     */
    public void prepareForExecution() throws QueryException {
        super.prepareForExecution();

        getContainerPolicy().prepareForExecution();

    }

    /**
     * INTERNAL:
     * Prepare the mechanism.
     */
    protected void prepareSelectAllRows() {
        getQueryMechanism().prepareSelectAllRows();
    }

    /**
     * INTERNAL:
     * All objects queried via a UnitOfWork get registered here. If the query
     * went to the database.
     * <p>
     * Involves registering the query result individually and in totality, and
     * hence refreshing / conforming is done here.
     *
     * @param result may be collection (read all) or an object (read one),
     * or even a cursor. If in transaction the shared cache will
     * be bypassed, meaning the result may not be originals from the parent
     * but raw database rows.
     * @param unitOfWork the unitOfWork the result is being registered in.
     * @param arguments the original arguments/parameters passed to the query
     * execution. Used by conforming
     * @param buildDirectlyFromRows If in transaction must construct
     * a registered result from raw database rows.
     *
     * @return the final (conformed, refreshed, wrapped) UnitOfWork query result
     */
    public Object registerResultInUnitOfWork(Object result, UnitOfWorkImpl unitOfWork, AbstractRecord arguments, boolean buildDirectlyFromRows) {
        // For bug 2612366: Conforming results in UOW extremely slow.
        // Replacing results with registered versions in the UOW is a part of
        // conforming and is now done while conforming to maximize performance.
        if (shouldConformResultsInUnitOfWork() || getDescriptor().shouldAlwaysConformResultsInUnitOfWork()) {
            return conformResult(result, unitOfWork, arguments, buildDirectlyFromRows);
        }

        // When building directly from rows, one of the performance benefits
        // is that we no longer have to wrap and then unwrap the originals.
        // result is just a vector, not a collection of wrapped originals.
        // Also for cursors the initial connection is automatically registered.
        if (buildDirectlyFromRows) {
            Vector rows = (Vector)result;
            Set identitySet = null;
            ContainerPolicy cp = getContainerPolicy();
            Object clones = cp.containerInstance(rows.size());
            for (Enumeration enumtr = rows.elements(); enumtr.hasMoreElements();) {
                Object row = enumtr.nextElement();

                // null is placed in the row collection for 1-m joining to filter duplicate rows.
                if (row != null) {
                    Object clone = registerIndividualResult(row, unitOfWork, buildDirectlyFromRows, null);

                    // Avoid duplicates if -m joining was used and a cache hit occured.
                    if (getJoinedAttributeManager().isToManyJoin()) {
                        if (identitySet == null) {
                            identitySet = new TopLinkIdentityHashSet(rows.size());
                        }
                        if (!identitySet.contains(clone)) {
                            identitySet.add(clone);
                            cp.addInto(clone, clones, unitOfWork);
                        }
                    } else {
                        cp.addInto(clone, clones, unitOfWork);
                    }
                }
            }
            return clones;
        }

        ContainerPolicy cp;
        cp = getContainerPolicy();

        Object clones = cp.containerInstance(cp.sizeFor(result));
        AbstractSession sessionToUse = unitOfWork.getParent();
        for (Object iter = cp.iteratorFor(result); cp.hasNext(iter);) {
            Object object = cp.next(iter, sessionToUse);
            Object clone = registerIndividualResult(object, unitOfWork, buildDirectlyFromRows, null);
            cp.addInto(clone, clones, unitOfWork);
        }
        return clones;
    }

    /**
     * PUBLIC:
     * Set the container policy. Used to support different containers
     * (e.g. Collections, Maps).
     */
    public void setContainerPolicy(ContainerPolicy containerPolicy) {
        // CR#... a container policy is always required, default is vector,
        // required for deployment XML.
        if (containerPolicy == null) {
            return;
        }
        this.containerPolicy = containerPolicy;
        setIsPrepared(false);
    }

    /**
     * INTERNAL:
     * Set the order expressions for the query.
     */
    public void setOrderByExpressions(Vector orderByExpressions) {
        this.orderByExpressions = orderByExpressions;
    }

    /**
     * PUBLIC:
     * Configure the mapping to use an instance of the specified container class
     * to hold the target objects.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Collection interface.
     * <p>jdk1.1.x: The container class must be a subclass of Vector.
     */
    public void useCollectionClass(Class concreteClass) {
        // Set container policy.
        setContainerPolicy(ContainerPolicy.buildPolicyFor(concreteClass));

    }

    /**
     * PUBLIC:
     * Configure the query to use an instance of the specified container class
     * to hold the result objects. The key used to index the value in the Map
     * is the value returned by a call to the specified zero-argument method.
     * The method must be implemented by the class (or a superclass) of the
     * value to be inserted into the Map.
     * <p>jdk1.2.x: The container class must implement (directly or indirectly) the Map interface.
     * <p>jdk1.1.x: The container class must be a subclass of Hashtable.
     * <p>The referenceClass must set before calling this method.
     */
    public void useMapClass(Class concreteClass, String methodName) {
        // the reference class has to be specified before coming here
        if (getReferenceClass() == null) {
            throw QueryException.referenceClassMissing(this);
        }
        ContainerPolicy policy = ContainerPolicy.buildPolicyFor(concreteClass);
        policy.setKeyName(methodName, getReferenceClass().getName());
        setContainerPolicy(policy);
    }
}


package oracle.toplink.essentials.internal.helper;

/**
 * An utility which
 * @author adam-bien.com
 */
public enum ReferenceMode {
    /**
     * Object hold with this reference will be not garbage collected.
     * It's the default setting
     */
    HARD,
    
    /**
     * Weak references are used. So the last reference will be garbage collected
     * anyway...
     * See: {_at_link java.lang.ref.WeakReference}
     */
    WEAK

}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.ejb.cmp3.base;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import oracle.toplink.essentials.config.TopLinkProperties;
import oracle.toplink.essentials.config.FlushClearCache;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.descriptors.DescriptorIterator;
import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;
import oracle.toplink.essentials.internal.localization.ExceptionLocalization;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.exceptions.DatabaseException;
import oracle.toplink.essentials.exceptions.OptimisticLockException;
import oracle.toplink.essentials.internal.helper.IdentityHashtable;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet;
import oracle.toplink.essentials.internal.sessions.ObjectChangeSet;
import oracle.toplink.essentials.mappings.ForeignReferenceMapping;
import oracle.toplink.essentials.sessions.IdentityMapAccessor;


public class RepeatableWriteUnitOfWork extends UnitOfWorkImpl {
    
    /** Used to store the final UnitOfWorkChangeSet for merge into the shared cache */
    protected UnitOfWorkChangeSet cumulativeUOWChangeSet;
    /** Used to store objects already deleted from the db and unregistered */
    protected HardIdentityHashtable unregisteredDeletedObjectsCloneToBackupAndOriginal;
    
    /** Used to determine if UnitOfWork should commit and rollback transactions
     * This is used when an EntityTransaction is controlling the transaction
     */
    protected boolean shouldTerminateTransaction;
    
    /** Used to determine if UnitOfWork.synchronizeAndResume method should
     * resume (the normal behaviour); or alternatively clear the UnitOfWork.
     */
    protected boolean shouldClearForCloseInsteadOfResume = false;
    
    /** The FlashClearCache mode to be used (see oracle.toplink.config.FlushClearCache).
     * Initialized by setUnitOfWorkChangeSet method in case it's null;
     * commitAndResume sets this attribute back to null.
     * Relevant only in case call to flush method followed by call to clear method.
     */
    protected transient String flushClearCache;
    
    /** Contains classes that should be invalidated in the shared cache on commit.
     * Used only in case fushClearCache == FlushClearCache.DropInvalidate:
     * clear method copies contents of updatedObjectsClasses to this set,
     * adding classes of deleted objects, too;
     * on commit the classes contained here are invalidated in the shared cache
     * and the set is cleared.
     * Relevant only in case call to flush method followed by call to clear method.
     * Works together with flushClearCache.
     */
    protected transient Set<Class> classesToBeInvalidated;
    
    public RepeatableWriteUnitOfWork(oracle.toplink.essentials.internal.sessions.AbstractSession parentSession){
        super(parentSession);
        this.shouldTerminateTransaction = true;
        this.shouldNewObjectsBeCached = true;
    }
    
    /**
     * INTERNAL:
     * This method will clear all registered objects from this UnitOfWork.
     * If parameter value is 'true' then the cache(s) are cleared, too.
     */
    public void clear(boolean shouldClearCache) {
        super.clear(shouldClearCache);
        if(cumulativeUOWChangeSet != null) {
            if(flushClearCache == null) {
                flushClearCache = PropertiesHandler.getSessionPropertyValueLogDebug(TopLinkProperties.FLUSH_CLEAR_CACHE, this);
                if(flushClearCache == null) {
                    flushClearCache = FlushClearCache.DEFAULT;
                }
            }
            if(flushClearCache == FlushClearCache.Drop) {
                cumulativeUOWChangeSet = null;
                unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
            } else if(flushClearCache == FlushClearCache.DropInvalidate) {
                // classes of the updated objects should be invalidated in the shared cache on commit.
                Set updatedObjectsClasses = cumulativeUOWChangeSet.findUpdatedObjectsClasses();
                if(updatedObjectsClasses != null) {
                    if(classesToBeInvalidated == null) {
                        classesToBeInvalidated = updatedObjectsClasses;
                    } else {
                        classesToBeInvalidated.addAll(updatedObjectsClasses);
                    }
                }
                // unregisteredDeletedObjectsCloneToBackupAndOriginal != null because cumulativeUOWChangeSet != null
                if(!unregisteredDeletedObjectsCloneToBackupAndOriginal.isEmpty()) {
                    if(classesToBeInvalidated == null) {
                        classesToBeInvalidated = new HashSet<Class>();
                    }
                    Enumeration enumDeleted = unregisteredDeletedObjectsCloneToBackupAndOriginal.keys();
                    // classes of the deleted objects should be invalidated in the shared cache
                    while(enumDeleted.hasMoreElements()) {
                        classesToBeInvalidated.add(enumDeleted.nextElement().getClass());
                    }
                }
                cumulativeUOWChangeSet = null;
                unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
            }
        }
    }
    
    /**
     * INTERNAL:
     * Call this method if the uow will no longer used for comitting transactions:
     * all the changes sets will be dereferenced, and (optionally) the cache cleared.
     * If the uow is not released, but rather kept around for ValueHolders, then identity maps shouldn't be cleared:
     * the parameter value should be 'false'. The lifecycle set to Birth so that uow ValueHolder still could be used.
     * Alternatively, if called from release method then everything should go and therefore parameter value should be 'true'.
     * In this case lifecycle won't change - uow.release (optionally) calls this method when it (uow) is already dead.
     * The reason for calling this method from release is to free maximum memory right away:
     * the uow might still be referenced by objects using UOWValueHolders (though they shouldn't be around
     * they still might).
     */
    public void clearForClose(boolean shouldClearCache){
        this.cumulativeUOWChangeSet = null;
        this.unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
        super.clearForClose(shouldClearCache);
    }
    
    /**
     * INTERNAL:
     * Indicates whether clearForClose methor should be called by release method.
     */
    public boolean shouldClearForCloseOnRelease() {
        return true;
    }
    
    /**
     * INTERNAL:
     * Commit the changes to any objects to the parent.
     */
    public void commitRootUnitOfWork() throws DatabaseException, OptimisticLockException {
        commitToDatabaseWithChangeSet(false);
        // unit of work has been committed so it's ok to set the cumulative into the UOW for merge
        if(this.cumulativeUOWChangeSet != null) {
            this.cumulativeUOWChangeSet.mergeUnitOfWorkChangeSet((UnitOfWorkChangeSet)this.getUnitOfWorkChangeSet(), this, true);
            setUnitOfWorkChangeSet(this.cumulativeUOWChangeSet);
        }

        commitTransactionAfterWriteChanges(); // this method will commit the transaction
                                              // and set the transaction flags appropriately

        // Merge after commit
        mergeChangesIntoParent();
    }

    /**
     * INTERNAL:
     * Traverse the object to find references to objects not registered in this unit of work.
     */
    public void discoverUnregisteredNewObjects(IdentityHashtable knownNewObjects, IdentityHashtable unregisteredExistingObjects, IdentityHashtable visitedObjects) {
        // This define an inner class for process the itteration operation, don't be scared, its just an inner class.
        DescriptorIterator iterator = new DescriptorIterator() {
            IdentityHashtable visitedObjectsForRegisterNewObjectForPersist = (IdentityHashtable)getCloneMapping().clone();

            public void iterate(Object object) {
                // If the object is read-only the do not continue the traversal.
                if (isClassReadOnly(object.getClass()) || isObjectDeleted(object)) {
                    this.setShouldBreak(true);
                    return;
                }
                //check for null mapping, this may be the first iteration
                if ((getCurrentMapping() != null) && ((ForeignReferenceMapping)getCurrentMapping()).isCascadePersist() ){
                    ((RepeatableWriteUnitOfWork)getSession()).registerNewObjectForPersist(object, visitedObjectsForRegisterNewObjectForPersist);
                }else{
                    if (!getCloneMapping().containsKey(object)){
                        if (! checkForUnregisteredExistingObject(object)){
                            throw new IllegalStateException(ExceptionLocalization.buildMessage("new_object_found_during_commit", new Object[]{object}));
                        }else{
                            ((IdentityHashtable)getUnregisteredExistingObjects()).put(object, object);
                            this.setShouldBreak(true);
                            return;
                        }
                    }
                }
            }
        };

        //set the collection in the UnitofWork to be this list
        setUnregisteredExistingObjects(unregisteredExistingObjects);

        iterator.setVisitedObjects(visitedObjects);
        iterator.setResult(knownNewObjects);
        iterator.setSession(this);
        // When using wrapper policy in EJB the iteration should stop on beans,
        // this is because EJB forces beans to be registered anyway and clone identity can be violated
        // and the violated clones references to session objects should not be traversed.
        iterator.setShouldIterateOverWrappedObjects(false);
        // iterations may add more entries to CloneMapping - clone it to loop only through the original ones
        Enumeration clones = ((IdentityHashtable)getCloneMapping().clone()).keys();
        while (clones.hasMoreElements()) {
            iterator.startIterationOn(clones.nextElement());
        }
    }
    
    /**
     * INTERNAL:
     * Has writeChanges() been attempted on this UnitOfWork? It may have
     * either suceeded or failed but either way the UnitOfWork is in a highly
     * restricted state.
     */
    public boolean isAfterWriteChangesButBeforeCommit() {
        //dont' check for writechanges failure.
        return (getLifecycle() == CommitTransactionPending);
    }

    /**
     * INTERNAL:
     * Return if the object has been deleted in this unit of work.
     */
    public boolean isObjectDeleted(Object object) {
        if(super.isObjectDeleted(object)) {
            return true;
        } else {
            if(unregisteredDeletedObjectsCloneToBackupAndOriginal != null) {
                if(unregisteredDeletedObjectsCloneToBackupAndOriginal.containsKey(object)) {
                    return true;
                }
            }
            if (hasObjectsDeletedDuringCommit()) {
                return getObjectsDeletedDuringCommit().containsKey(object);
            } else {
                return false;
            }
        }
    }

    /**
     * INTERNAL:
     * For synchronized units of work, dump SQL to database
     */
    public void issueSQLbeforeCompletion() {

        super.issueSQLbeforeCompletion(false);

        if (this.cumulativeUOWChangeSet != null && this.getUnitOfWorkChangeSet() != null){
            // unit of work has been committed so it's ok to set the cumulative into the UOW for merge
            this.cumulativeUOWChangeSet.mergeUnitOfWorkChangeSet((UnitOfWorkChangeSet)this.getUnitOfWorkChangeSet(), this, true);
            setUnitOfWorkChangeSet(this.cumulativeUOWChangeSet);
        }

        commitTransactionAfterWriteChanges(); // this method will commit the transaction
                                              // and set the transaction flags appropriately
    }
    
    /**
     * INTERNAL: Merge the changes to all objects to the parent.
     */
    protected void mergeChangesIntoParent() {
        if(classesToBeInvalidated != null) {
            // get identityMap of the parent ServerSession
            IdentityMapAccessor accessor = this.getParentIdentityMapSession(null, false, true).getIdentityMapAccessor();
            Iterator<Class> it = classesToBeInvalidated.iterator();
            while(it.hasNext()) {
               accessor.invalidateClass(it.next(), false);
            }
            classesToBeInvalidated = null;
        }
        flushClearCache = null;
        super.mergeChangesIntoParent();
    }
    
    /**
     * INTERNAL:
     * Merge the attributes of the clone into the unit of work copy.
     */
    public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy, boolean forceCascade) {
        Object mergedObject = super.mergeCloneWithReferences(rmiClone, cascadePolicy, forceCascade);
        
        if (mergedObject != null) {
            // This will assign a sequence number to the merged object if it
            // doesn't already have one (that is, it is a new object).
            assignSequenceNumber(mergedObject);
        }
        
        return mergedObject;
    }

    /**
     * INTERNAL:
     * This method is used internally to update the tracked objects if required
     */
    public void updateChangeTrackersIfRequired(Object objectToWrite, ObjectChangeSet changeSetToWrite, UnitOfWorkImpl uow, ClassDescriptor descriptor) {
        descriptor.getObjectChangePolicy().updateWithChanges(objectToWrite, changeSetToWrite, uow, descriptor);
    }

    /**
     * INTERNAL:
     * This method will cause the all of the tracked objects at this level to have
     * their changes written to the database. It will then decrement the depth
     * level.
     */
    public void writeChanges() {
            if(unregisteredDeletedObjectsCloneToBackupAndOriginal == null) {
                unregisteredDeletedObjectsCloneToBackupAndOriginal = new HardIdentityHashtable(2);
            }
            HardIdentityHashtable allObjectsList = new HardIdentityHashtable(2);
            HardIdentityHashtable visitedNodes = new HardIdentityHashtable(2);
            HardIdentityHashtable newObjects = new HardIdentityHashtable(2);
            HardIdentityHashtable existingObjects = new HardIdentityHashtable(2);
            HardIdentityHashtable insertedNewObjects = new HardIdentityHashtable(2);
            for (Enumeration clones = getCloneMapping().keys(); clones.hasMoreElements();){
                Object object = clones.nextElement();
                allObjectsList.put(object, object);
            }
            discoverUnregisteredNewObjects(newObjects, existingObjects, visitedNodes);
            for (Enumeration newClones = getNewObjectsCloneToOriginal().keys();newClones.hasMoreElements();){
                Object object = newClones.nextElement();
                assignSequenceNumber(object);
                insertedNewObjects.put(object, object);
                // add potentially newly discovered new objects
                allObjectsList.put(object, object);
            }

            if (getUnitOfWorkChangeSet() == null) {
                setUnitOfWorkChangeSet(new UnitOfWorkChangeSet());
            }
            calculateChanges(allObjectsList, (UnitOfWorkChangeSet)getUnitOfWorkChangeSet());
            // write those changes to the database.
            UnitOfWorkChangeSet changeSet = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet();
            if (!changeSet.hasChanges() && !changeSet.hasForcedChanges() && ! this.hasDeletedObjects() && ! this.hasModifyAllQueries()){
                    return;
            }
            try {
                commitToDatabaseWithPreBuiltChangeSet(changeSet, false);
                this.writesCompleted();
            } catch (RuntimeException ex) {
                clearFlushClearCache();
                setLifecycle(WriteChangesFailed);
                throw ex;
            }
            //bug 4730595: fix puts deleted objects in the UnitOfWorkChangeSet as they are removed.
            getDeletedObjects().clear();

            // unregister all deleted objects,
            // keep them along with their original and backup values in unregisteredDeletedObjectsCloneToBackupAndOriginal
            Enumeration enumDeleted = getObjectsDeletedDuringCommit().keys();
            while(enumDeleted.hasMoreElements()) {
                Object deletedObject = enumDeleted.nextElement();
                Object[] backupAndOriginal = {getCloneMapping().get(deletedObject), getCloneToOriginals().get(deletedObject)};
                unregisteredDeletedObjectsCloneToBackupAndOriginal.put(deletedObject, backupAndOriginal);
                unregisterObject(deletedObject);
            }
            getObjectsDeletedDuringCommit().clear();

            if(this.cumulativeUOWChangeSet == null) {
                this.cumulativeUOWChangeSet = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet();
            } else {
                //merge those changes back into the backup clones and the final uowChangeSet
                this.cumulativeUOWChangeSet.mergeUnitOfWorkChangeSet((UnitOfWorkChangeSet)getUnitOfWorkChangeSet(), this, true);
            }
            //clean up
            setUnitOfWorkChangeSet(new UnitOfWorkChangeSet());
            Enumeration enumtr = insertedNewObjects.elements();
            while (enumtr.hasMoreElements()) {
                Object clone = enumtr.nextElement();
                Object original = getNewObjectsCloneToOriginal().remove(clone);
                if (original != null) {
                    getNewObjectsOriginalToClone().remove(original);
                    //no longer new to this unit of work
                    getCloneToOriginals().put(clone, original);
                }
            }
        }

    /**
     * INTERNAL:
     * Called only by registerNewObjectForPersist method,
     * and only if newObject is not already registered.
     * If newObject is found in
     * unregisteredDeletedObjectsCloneToBackupAndOriginal then it's re-registered,
     * otherwise the superclass method called.
     */
    protected void registerNotRegisteredNewObjectForPersist(Object newObject, ClassDescriptor descriptor) {
        if(unregisteredDeletedObjectsCloneToBackupAndOriginal != null) {
            Object[] backupAndOriginal = (Object[])unregisteredDeletedObjectsCloneToBackupAndOriginal.remove(newObject);
            if(backupAndOriginal != null) {
                // backup
                getCloneMapping().put(newObject, backupAndOriginal[0]);
                // original
                registerNewObjectClone(newObject, backupAndOriginal[1], descriptor);

                // Check if the new objects should be cached.
                registerNewObjectInIdentityMap(newObject, newObject);
                
                return;
            }
        }
        super.registerNotRegisteredNewObjectForPersist(newObject, descriptor);
    }

    /**
     * INTERNAL:
     * This is internal to the uow, transactions should not be used explictly in a uow.
     * The uow shares its parents transactions.
     */
    public void rollbackTransaction() throws DatabaseException {
        if (this.shouldTerminateTransaction || getParent().getTransactionMutex().isNested()){
            super.rollbackTransaction();
        }else{
            //rollback called which means txn failed.
            //but rollback was stopped by entitytransaction which means the
            //transaction will want to call release later. Make sure release
            //will rollback transaction.
            setWasTransactionBegunPrematurely(true);
        }
    }

    /**
     * INTERNAL
     * Synchronize the clones and update their backup copies.
     * Called after commit and commit and resume.
     */
    public void synchronizeAndResume() {
        if(this.shouldClearForCloseInsteadOfResume()) {
            this.clearForClose(false);
        } else {
            this.cumulativeUOWChangeSet = null;
            this.unregisteredDeletedObjectsCloneToBackupAndOriginal = null;
            super.synchronizeAndResume();
        }
    }
    
    /**
     * INTERNAL:
     * Called only by UnitOfWorkIdentityMapAccessor.getAndCloneCacheKeyFromParent method.
     * Return unregisteredDeletedClone corresponding to the passed original, or null
     */
    public Object getUnregisteredDeletedCloneForOriginal(Object original) {
        if(unregisteredDeletedObjectsCloneToBackupAndOriginal != null) {
            Enumeration keys = unregisteredDeletedObjectsCloneToBackupAndOriginal.keys();
            Enumeration values = unregisteredDeletedObjectsCloneToBackupAndOriginal.elements();
            while(keys.hasMoreElements()) {
                Object deletedObjectClone = keys.nextElement();
                Object[] backupAndOriginal = (Object[])values.nextElement();
                Object currentOriginal = backupAndOriginal[1];
                if(original == currentOriginal) {
                    return deletedObjectClone;
                }
            }
        }
        return null;
    }
    
  /**
   * INTERNAL:
   * Wraps the oracle.toplink.essentials.exceptions.OptimisticLockException in a
   * javax.persistence.OptimisticLockException. This conforms to the EJB3 specs
   * @param commitTransaction
   */
    protected void commitToDatabase(boolean commitTransaction) {
        try {
            super.commitToDatabase(commitTransaction);
        } catch (oracle.toplink.essentials.exceptions.OptimisticLockException ole) {
            throw new javax.persistence.OptimisticLockException(ole);
        }
    }

    /**
     * INTERNAL:
     * This is internal to the uow, transactions should not be used explictly in a uow.
     * The uow shares its parents transactions.
     */
    public void commitTransaction() throws DatabaseException {
        if (this.shouldTerminateTransaction || getParent().getTransactionMutex().isNested()){
            super.commitTransaction();
        }
    }

    public void setShouldTerminateTransaction(boolean shouldTerminateTransaction) {
        this.shouldTerminateTransaction = shouldTerminateTransaction;
    }
    
    public void setShouldClearForCloseInsteadOfResume(boolean shouldClearForCloseInsteadOfResume) {
        this.shouldClearForCloseInsteadOfResume = shouldClearForCloseInsteadOfResume;
    }

    public boolean shouldClearForCloseInsteadOfResume() {
        return shouldClearForCloseInsteadOfResume;
    }

    /**
     * INTERNAL:
     * Clears flushClearCache attribute and the related collections.
     */
    public void clearFlushClearCache() {
        flushClearCache = null;
        classesToBeInvalidated = null;
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package oracle.toplink.essentials.internal.expressions;

import java.io.*;
import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.databaseaccess.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.mappings.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.platform.database.DB2MainframePlatform;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: Print SELECT statement.
 * <p><b>Responsibilities</b>:<ul>
 * <li> Print SELECT statement.
 * </ul>
 * @author Dorin Sandu
 * @since TOPLink/Java 1.0
 */
public class SQLSelectStatement extends SQLStatement {

    /** Fields being selected (can include expressions). */
    protected Vector fields;
    
    /** Fields not being selected (can include expressions). */
    protected Vector nonSelectFields;

    /** Tables being selected from. */
    protected Vector tables;

    /** Used for "Select Distinct" option. */
    protected short distinctState;

    /** Order by clause for read all queries. */
    protected Vector orderByExpressions;

    /** Group by clause for report queries. */
    protected Vector groupByExpressions;

    /** Having clause for report queries. */
    protected Expression havingExpression;

    /** Used for pessimistic locking ie. "For Update". */
    protected ForUpdateClause forUpdateClause;

    /** Used for report query or counts so we know how to treat distincts. */
    protected boolean isAggregateSelect;

    /** Used for DB2 style from clause outer joins. */
    protected Vector outerJoinedExpressions;
    protected Vector outerJoinedMappingCriteria;
    protected Vector outerJoinedAdditionalJoinCriteria;
    // used in case no corresponding outerJoinExpression is provided -
    // only multitable inheritance should be outer joined
    protected List descriptorsForMultitableInheritanceOnly;

    /** Used for Oracle Hierarchical Queries */
    protected Expression startWithExpression;
    protected Expression connectByExpression;
    protected Vector orderSiblingsByExpressions;

    /** Variables used for aliasing and normalizing. */
    protected boolean requiresAliases;
    protected Hashtable tableAliases;
    protected DatabaseTable lastTable;
    protected DatabaseTable currentAlias;
    protected int currentAliasNumber;

    /** Used for subselects. */
    protected SQLSelectStatement parentStatement;

    public SQLSelectStatement() {
        this.fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(2);
        this.tables = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(4);
        this.requiresAliases = false;
        this.isAggregateSelect = false;
        this.distinctState = ObjectLevelReadQuery.UNCOMPUTED_DISTINCT;
        this.currentAliasNumber = 0;
    }

    public void addField(DatabaseField field) {
        getFields().addElement(field);
    }

    /**
     * INTERNAL: adds an expression to the fields. set a flag if the expression
     * is for and aggregate function.
     */
    public void addField(Expression expression) {
        if (expression instanceof FunctionExpression) {
            if (((FunctionExpression)expression).getOperator().isAggregateOperator()) {
                setIsAggregateSelect(true);
            }
        }

        getFields().addElement(expression);
    }

    /**
     * When distinct is used with order by the ordered fields must be in the select clause.
     */
    protected void addOrderByExpressionToSelectForDistinct() {
        for (Enumeration orderExpressionsEnum = getOrderByExpressions().elements();
                 orderExpressionsEnum.hasMoreElements();) {
            Expression orderExpression = (Expression)orderExpressionsEnum.nextElement();
            Expression fieldExpression = null;

            if (orderExpression.isFunctionExpression() && (orderExpression.getOperator().isOrderOperator())) {
                fieldExpression = ((FunctionExpression)orderExpression).getBaseExpression();
            } else {
                fieldExpression = orderExpression;
            }

            // Changed to call a method to loop throught the fields vector and check each element
            // individually. Jon D. May 4, 2000 for pr 7811
            if ((fieldExpression.selectIfOrderedBy()) && !fieldsContainField(getFields(), fieldExpression)) {
                addField(fieldExpression);
            }
        }
    }

    /**
     * Add a table to the statement. The table will
     * be used in the FROM part of the SQL statement.
     */
    public void addTable(DatabaseTable table) {
        if (!getTables().contains(table)) {
            getTables().addElement(table);
        }
    }

    /**
     * ADVANCED:
     * If a platform is Informix, then the outer join must be in the FROM clause.
     * This is used internally by TopLink for building Informix outer join syntax which differs from
     * other platforms(Oracle,Sybase) that print the outer join in the WHERE clause and from DB2 which prints
     * the.
     * OuterJoinedAliases passed in to keep track of tables used for outer join so no normal join is given
     */
    public void appendFromClauseForInformixOuterJoin(ExpressionSQLPrinter printer, Vector outerJoinedAliases) throws IOException {
        Writer writer = printer.getWriter();
        AbstractSession session = printer.getSession();

        // Print outer joins
        boolean firstTable = true;

        for (int index = 0; index < getOuterJoinExpressions().size(); index++) {
            QueryKeyExpression outerExpression = (QueryKeyExpression)getOuterJoinExpressions().elementAt(index);
            CompoundExpression relationExpression = (CompoundExpression)getOuterJoinedMappingCriteria().elementAt(index);// get expression for multiple table case

            // CR#3083929 direct collection/map mappings do not have reference descriptor.
            DatabaseTable targetTable = null;
            if (outerExpression.getMapping().isDirectCollectionMapping()) {
                targetTable = ((DirectCollectionMapping)outerExpression.getMapping()).getReferenceTable();
            } else {
                targetTable = (DatabaseTable)outerExpression.getMapping().getReferenceDescriptor().getTables().firstElement();
            }

            // Grab the source table from the mapping not just the first table
            // from the descriptor. In an joined inheritance hierarchy, the
            // fk used in the outer join may be from a subclasses's table .
            DatabaseTable sourceTable;
            if (outerExpression.getMapping().isObjectReferenceMapping() && ((ObjectReferenceMapping) outerExpression.getMapping()).isForeignKeyRelationship()) {
                sourceTable = (DatabaseTable)((DatabaseField) outerExpression.getMapping().getFields().firstElement()).getTable();
            } else {
                sourceTable = (DatabaseTable)((ObjectExpression)outerExpression.getBaseExpression()).getDescriptor().getTables().firstElement();
            }

            DatabaseTable sourceAlias = outerExpression.getBaseExpression().aliasForTable(sourceTable);
            DatabaseTable targetAlias = outerExpression.aliasForTable(targetTable);

            if (!(outerJoinedAliases.contains(sourceAlias) || outerJoinedAliases.contains(targetAlias))) {
                if (!firstTable) {
                    writer.write(", ");
                }

                firstTable = false;
                writer.write(sourceTable.getQualifiedName());
                outerJoinedAliases.addElement(sourceAlias);
                writer.write(" ");
                writer.write(sourceAlias.getQualifiedName());

                if (outerExpression.getMapping().isManyToManyMapping()) {// for many to many mappings, you need to do some funky stuff to get the relation table's alias
                    DatabaseTable newTarget = ((ManyToManyMapping)outerExpression.getMapping()).getRelationTable();
                    DatabaseTable newAlias = relationExpression.aliasForTable(newTarget);
                    writer.write(", OUTER ");// need to outer join only to relation table for many-to-many case in Informix
                    writer.write(newTarget.getQualifiedName());
                    writer.write(" ");
                    outerJoinedAliases.addElement(newAlias);
                    writer.write(newAlias.getQualifiedName());
                } else if (outerExpression.getMapping().isDirectCollectionMapping()) {// for many to many mappings, you need to do some funky stuff to get the relation table's alias
                    DatabaseTable newTarget = ((DirectCollectionMapping)outerExpression.getMapping()).getReferenceTable();
                    DatabaseTable newAlias = relationExpression.aliasForTable(newTarget);
                    writer.write(", OUTER ");
                    writer.write(newTarget.getQualifiedName());
                    writer.write(" ");
                    outerJoinedAliases.addElement(newAlias);
                    writer.write(newAlias.getQualifiedName());
                } else {// do normal outer stuff for Informix
                    for (Enumeration target = outerExpression.getMapping().getReferenceDescriptor().getTables().elements();
                             target.hasMoreElements();) {
                        DatabaseTable newTarget = (DatabaseTable)target.nextElement();
                        Expression onExpression = outerExpression;
                        DatabaseTable newAlias = outerExpression.aliasForTable(newTarget);
                        writer.write(", OUTER ");
                        writer.write(newTarget.getQualifiedName());
                        writer.write(" ");
                        outerJoinedAliases.addElement(newAlias);
                        writer.write(newAlias.getQualifiedName());
                    }
                }
            }
        }
    }

    /**
     * ADVANCED:
     * If a platform is DB2 or MySQL, then the outer join must be in the FROM clause.
     * This is used internally by TopLink for building DB2 outer join syntax which differs from
     * other platforms(Oracle,Sybase) that print the outer join in the WHERE clause.
     * OuterJoinedAliases passed in to keep track of tables used for outer join so no normal join is given
     */
    public void appendFromClauseForOuterJoin(ExpressionSQLPrinter printer, Vector outerJoinedAliases) throws IOException {
        Writer writer = printer.getWriter();
        AbstractSession session = printer.getSession();

        // Print outer joins
        boolean firstTable = true;
        boolean requiresEscape = false;//checks if the jdbc closing escape syntax is needed

        OuterJoinExpressionHolders outerJoinExpressionHolders = new OuterJoinExpressionHolders();
        for (int index = 0; index < getOuterJoinExpressions().size(); index++) {
            QueryKeyExpression outerExpression = (QueryKeyExpression)getOuterJoinExpressions().elementAt(index);

            // CR#3083929 direct collection/map mappings do not have reference descriptor.
            DatabaseTable targetTable = null;
            DatabaseTable sourceTable = null;
            DatabaseTable sourceAlias = null;
            DatabaseTable targetAlias = null;
            if(outerExpression != null) {
                if (outerExpression.getMapping().isDirectCollectionMapping()) {
                    targetTable = ((DirectCollectionMapping)outerExpression.getMapping()).getReferenceTable();
                } else {
                    targetTable = (DatabaseTable)outerExpression.getMapping().getReferenceDescriptor().getTables().firstElement();
                }
                
                // Grab the source table from the mapping not just the first table
                // from the descriptor. In an joined inheritance hierarchy, the
                // fk used in the outer join may be from a subclasses's table.
                if (outerExpression.getMapping().isObjectReferenceMapping() && ((ObjectReferenceMapping) outerExpression.getMapping()).isForeignKeyRelationship()) {
                    sourceTable = (DatabaseTable)((DatabaseField) outerExpression.getMapping().getFields().firstElement()).getTable();
                } else {
                    sourceTable = (DatabaseTable)((ObjectExpression)outerExpression.getBaseExpression()).getDescriptor().getTables().firstElement();
                }
                
                sourceAlias = outerExpression.getBaseExpression().aliasForTable(sourceTable);
                targetAlias = outerExpression.aliasForTable(targetTable);
            } else {
                sourceTable = (DatabaseTable)((ClassDescriptor)getDescriptorsForMultitableInheritanceOnly().get(index)).getTables().firstElement();
                targetTable = (DatabaseTable)((ClassDescriptor)getDescriptorsForMultitableInheritanceOnly().get(index)).getInheritancePolicy().getChildrenTables().get(0);
                Expression exp = (Expression)((Map)getOuterJoinedAdditionalJoinCriteria().elementAt(index)).get(targetTable);
                sourceAlias = exp.aliasForTable(sourceTable);
                targetAlias = exp.aliasForTable(targetTable);
            }

            outerJoinExpressionHolders.add(
                new OuterJoinExpressionHolder(outerExpression, index, targetTable,
                                              sourceTable, targetAlias, sourceAlias));
        }
        
        for (Iterator i = outerJoinExpressionHolders.linearize().iterator(); i.hasNext();) {
            OuterJoinExpressionHolder holder = (OuterJoinExpressionHolder)i.next();
            QueryKeyExpression outerExpression = holder.joinExpression;
            int index = holder.index;
            DatabaseTable targetTable = holder.targetTable;
            DatabaseTable sourceTable = holder.sourceTable;
            DatabaseTable sourceAlias = holder.sourceAlias;
            DatabaseTable targetAlias = holder.targetAlias;

            if (!outerJoinedAliases.contains(targetAlias)) {
                if (!outerJoinedAliases.contains(sourceAlias)) {
                    if (requiresEscape && session.getPlatform().shouldUseJDBCOuterJoinSyntax()) {
                        writer.write("}");
                    }

                    if (!firstTable) {
                        writer.write(",");
                    }

                    if (session.getPlatform().shouldUseJDBCOuterJoinSyntax()) {
                        writer.write(session.getPlatform().getJDBCOuterJoinString());
                    }

                    requiresEscape = true;
                    firstTable = false;
                    writer.write(sourceTable.getQualifiedName());
                    outerJoinedAliases.addElement(sourceAlias);
                    writer.write(" ");
                    writer.write(sourceAlias.getQualifiedName());
                }

                if(outerExpression == null) {
                    printAdditionalJoins(printer, outerJoinedAliases, (ClassDescriptor)getDescriptorsForMultitableInheritanceOnly().get(index), (Map)getOuterJoinedAdditionalJoinCriteria().elementAt(index));
                } else if (outerExpression.getMapping().isDirectCollectionMapping()) {
                    // Append the join clause,
                    // If this is a direct collection, join to direct table.
                    Expression onExpression = (Expression)getOuterJoinedMappingCriteria().elementAt(index);

                    DatabaseTable newAlias = onExpression.aliasForTable(targetTable);
                    writer.write(" LEFT OUTER JOIN ");
                    writer.write(targetTable.getQualifiedName());
                    writer.write(" ");
                    outerJoinedAliases.addElement(newAlias);
                    writer.write(newAlias.getQualifiedName());
                    writer.write(" ON ");

                    if (session.getPlatform() instanceof DB2MainframePlatform) {
                        ((RelationExpression)onExpression).printSQLNoParens(printer);
                    } else {
                        onExpression.printSQL(printer);
                    }

                    //Bug#4240751 Treat ManyToManyMapping separately for out join
                } else if (outerExpression.getMapping().isManyToManyMapping()) {
                    // Must outerjoin each of the targets tables.
                    // The first table is joined with the mapping join criteria,
                    // the rest of the tables are joined with the additional join criteria.
                    // For example: EMPLOYEE t1 LEFT OUTER JOIN (PROJ_EMP t3 LEFT OUTER JOIN PROJECT t0 ON (t0.PROJ_ID = t3.PROJ_ID)) ON (t3.EMP_ID = t1.EMP_ID)
                    DatabaseTable relationTable = ((ManyToManyMapping)outerExpression.getMapping()).getRelationTable();
                    DatabaseTable relationAlias = ((Expression)getOuterJoinedMappingCriteria().elementAt(index)).aliasForTable(relationTable);
                    writer.write(" LEFT OUTER JOIN (");
                    
                    writer.write(relationTable.getQualifiedName());
                    writer.write(" ");
                    outerJoinedAliases.addElement(relationAlias);
                    writer.write(relationAlias.getQualifiedName());
                    
                    Vector tablesInOrder = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(3);
                    // glassfish issue 2440: store aliases instead of tables
                    // in the tablesInOrder. This allows to distinguish source
                    // and target table in case of an self referencing relationship.
                    tablesInOrder.add(sourceAlias);
                    tablesInOrder.add(relationAlias);
                    tablesInOrder.add(targetAlias);
                    TreeMap indexToExpressionMap = new TreeMap();
                    mapTableIndexToExpression((Expression)getOuterJoinedMappingCriteria().elementAt(index), indexToExpressionMap, tablesInOrder);
                    Expression sourceToRelationJoin = (Expression)indexToExpressionMap.get(new Integer(1));
                    Expression relationToTargetJoin = (Expression)indexToExpressionMap.get(new Integer(2));
                    
                    writer.write(" JOIN ");
                    writer.write(targetTable.getQualifiedName());
                    writer.write(" ");
                    outerJoinedAliases.addElement(targetAlias);
                    writer.write(targetAlias.getQualifiedName());
                    writer.write(" ON ");
                    if (session.getPlatform() instanceof DB2MainframePlatform) {
                        ((RelationExpression)relationToTargetJoin).printSQLNoParens(printer);
                    } else {
                        relationToTargetJoin.printSQL(printer);
                    }
                    
                    Map tablesJoinExpression = (Map)getOuterJoinedAdditionalJoinCriteria().elementAt(index);
                    if(tablesJoinExpression != null && !tablesJoinExpression.isEmpty()) {
                        printAdditionalJoins(printer, outerJoinedAliases, outerExpression.getMapping().getReferenceDescriptor(), tablesJoinExpression);
                    }
                    writer.write(") ON ");
                    if (session.getPlatform() instanceof DB2MainframePlatform) {
                        ((RelationExpression)sourceToRelationJoin).printSQLNoParens(printer);
                    } else {
                        sourceToRelationJoin.printSQL(printer);
                    }
                } else {
                    // Must outerjoin each of the targets tables.
                    // The first table is joined with the mapping join criteria,
                    // the rest of the tables are joined with the additional join criteria.
                    writer.write(" LEFT OUTER JOIN ");
                    Map tablesJoinExpression = (Map)getOuterJoinedAdditionalJoinCriteria().elementAt(index);
                    boolean hasAdditionalJoinExpressions = tablesJoinExpression != null && !tablesJoinExpression.isEmpty();
                    if(hasAdditionalJoinExpressions) {
                        writer.write("(");
                    }
                    writer.write(targetTable.getQualifiedName());
                    writer.write(" ");
                    outerJoinedAliases.addElement(targetAlias);
                    writer.write(targetAlias.getQualifiedName());
                    if(hasAdditionalJoinExpressions) {
                        printAdditionalJoins(printer, outerJoinedAliases, outerExpression.getMapping().getReferenceDescriptor(), tablesJoinExpression);
                        writer.write(")");
                    }
                    writer.write(" ON ");
                    Expression sourceToTargetJoin = (Expression)getOuterJoinedMappingCriteria().elementAt(index);
                    if (session.getPlatform() instanceof DB2MainframePlatform) {
                        ((RelationExpression)sourceToTargetJoin).printSQLNoParens(printer);
                    } else {
                        sourceToTargetJoin.printSQL(printer);
                    }
                }
            }
        }

        if (requiresEscape && session.getPlatform().shouldUseJDBCOuterJoinSyntax()) {
            writer.write("}");
        }
    }

    protected void printAdditionalJoins(ExpressionSQLPrinter printer, Vector outerJoinedAliases, ClassDescriptor desc, Map tablesJoinExpressions) throws IOException {
        Writer writer = printer.getWriter();
        AbstractSession session = printer.getSession();
        Vector descriptorTables = desc.getTables();
        int nDescriptorTables = descriptorTables.size();
        Vector tables;
        if(desc.hasInheritance()) {
            tables = desc.getInheritancePolicy().getAllTables();
        } else {
            tables = descriptorTables;
        }

        // skip main table - start with i=1
        int tablesSize = tables.size();
        for(int i=1; i < tablesSize; i++) {
            DatabaseTable table = (DatabaseTable)tables.elementAt(i);
            Expression onExpression = (Expression)tablesJoinExpressions.get(table);
            if (onExpression != null) {
                if(i < nDescriptorTables) {
                    // it's descriptor's own table
                    writer.write(" JOIN ");
                } else {
                    // it's child's table
                    writer.write(" LEFT OUTER JOIN ");
                }
                writer.write(table.getQualifiedName());
                writer.write(" ");
                DatabaseTable alias = onExpression.aliasForTable(table);
                outerJoinedAliases.addElement(alias);
                writer.write(alias.getQualifiedName());
                writer.write(" ON ");
                if (session.getPlatform() instanceof DB2MainframePlatform) {
                    ((RelationExpression)onExpression).printSQLNoParens(printer);
                } else {
                    onExpression.printSQL(printer);
                }
            }
        }
    }

    /**
     * Print the from clause.
     * This includes outer joins, these must be printed before the normal join to ensure that the source tables are not joined again.
     * Outer joins are not printed in the FROM clause on Oracle or Sybase.
     */
    public void appendFromClauseToWriter(ExpressionSQLPrinter printer) throws IOException {
        Writer writer = printer.getWriter();
        AbstractSession session = printer.getSession();
        writer.write(" FROM ");

        // Print outer joins
        boolean firstTable = true;
        Vector outerJoinedAliases = new Vector(1);// Must keep track of tables used for outer join so no normal join is given

        if (hasOuterJoinExpressions()) {
            if (session.getPlatform().isInformixOuterJoin()) {
                appendFromClauseForInformixOuterJoin(printer, outerJoinedAliases);
            } else if (!session.getPlatform().shouldPrintOuterJoinInWhereClause()) {
                appendFromClauseForOuterJoin(printer, outerJoinedAliases);
            }

            firstTable = false;
        }

        // If there are no table aliases it means the query was malformed,
        // most likely the wrong builder was used, or wrong builder on the left in a sub-query.
        if (getTableAliases().isEmpty()) {
            throw QueryException.invalidBuilderInQuery(null);// Query is set in execute.
        }

        // Print tables for normal join
        for (Enumeration aliasesEnum = getTableAliases().keys(); aliasesEnum.hasMoreElements();) {
            DatabaseTable alias = (DatabaseTable)aliasesEnum.nextElement();

            if (!outerJoinedAliases.contains(alias)) {
                DatabaseTable table = (DatabaseTable)getTableAliases().get(alias);

                if (requiresAliases()) {
                    if (!firstTable) {
                        writer.write(", ");
                    }

                    firstTable = false;
                    writer.write(table.getQualifiedName());
                    writer.write(" ");
                    writer.write(alias.getQualifiedName());
                } else {
                    writer.write(table.getQualifiedName());
                }
            }
        }
    }

    /**
     * This method will append the group by clause to the end of the
     * select statement.
     */
    public void appendGroupByClauseToWriter(ExpressionSQLPrinter printer) throws IOException {
        if (getGroupByExpressions().isEmpty()) {
            return;
        }

        printer.getWriter().write(" GROUP BY ");

        Vector newFields = new Vector();
        // to avoid printing a comma before the first field
        printer.setIsFirstElementPrinted(false);
        for (Enumeration expressionsEnum = getGroupByExpressions().elements();
                 expressionsEnum.hasMoreElements();) {
            Expression expression = (Expression)expressionsEnum.nextElement();
            writeFieldsFromExpression(printer, expression, newFields);
        }
    }

    /**
     * This method will append the Hierarchical Query Clause to the end of the
     * select statement
     */
    public void appendHierarchicalQueryClauseToWriter(ExpressionSQLPrinter printer) throws IOException {
        Expression startWith = getStartWithExpression();
        Expression connectBy = getConnectByExpression();
        Vector orderSiblingsBy = getOrderSiblingsByExpressions();

        //Create the START WITH CLAUSE
        if (startWith != null) {
            printer.getWriter().write(" START WITH ");
            startWith.printSQL(printer);
        }

        if (connectBy != null) {
            if (!connectBy.isQueryKeyExpression()) {
                throw QueryException.illFormedExpression(connectBy);
            }

            printer.getWriter().write(" CONNECT BY ");

            DatabaseMapping mapping = ((QueryKeyExpression)connectBy).getMapping();
            ClassDescriptor descriptor = mapping.getDescriptor();

            //only works for these kinds of mappings. The data isn't hierarchical otherwise
            //Should also check that the source class and target class are the same.
            Map foreignKeys = null;

            if (mapping.isOneToManyMapping()) {
                OneToManyMapping otm = (OneToManyMapping)mapping;
                foreignKeys = otm.getTargetForeignKeyToSourceKeys();
            } else if (mapping.isOneToOneMapping()) {
                OneToOneMapping oto = (OneToOneMapping)mapping;
                foreignKeys = oto.getSourceToTargetKeyFields();
            } else if (mapping.isAggregateCollectionMapping()) {
                AggregateCollectionMapping acm = (AggregateCollectionMapping)mapping;
                foreignKeys = acm.getTargetForeignKeyToSourceKeys();
            } else {
                throw QueryException.invalidQueryKeyInExpression(connectBy);
            }

            DatabaseTable defaultTable = descriptor.getDefaultTable();
            String tableName = "";

            //determine which table name to use
            if (requiresAliases()) {
                tableName = getBuilder().aliasForTable(defaultTable).getName();
            } else {
                tableName = defaultTable.getName();
            }

            if ((foreignKeys != null) && !foreignKeys.isEmpty()) {
                //get the source and target fields.
                Iterator sourceKeys = foreignKeys.keySet().iterator();

                //for each source field, get the target field and create the link. If there's
                //only one, use the simplest version without ugly bracets
                if (foreignKeys.size() > 1) {
                    printer.getWriter().write("((");
                }

                DatabaseField source = (DatabaseField)sourceKeys.next();
                DatabaseField target = (DatabaseField)foreignKeys.get(source);

                //OneToOneMappings -> Source in parent object goes to target in child object
                if (mapping.isOneToOneMapping()) {
                    printer.getWriter().write("PRIOR " + tableName + "." + source.getName());
                    printer.getWriter().write(" = " + tableName + "." + target.getName());
                } else {//collections are the opposite way
                    printer.getWriter().write(tableName + "." + source.getName());
                    printer.getWriter().write(" = PRIOR " + tableName + "." + target.getName());
                }

                while (sourceKeys.hasNext()) {
                    printer.getWriter().write(") AND (");
                    source = (DatabaseField)sourceKeys.next();
                    target = (DatabaseField)foreignKeys.get(source);

                    if (mapping.isOneToOneMapping()) {
                        printer.getWriter().write("PRIOR " + tableName + "." + source.getName());
                        printer.getWriter().write(" = " + tableName + "." + target.getName());
                    } else {//collections are the opposite way
                        printer.getWriter().write(tableName + "." + source.getName());
                        printer.getWriter().write(" = PRIOR " + tableName + "." + target.getName());
                    }
                }

                if (foreignKeys.size() > 1) {
                    printer.getWriter().write("))");
                }
            }
        }

        if (orderSiblingsBy != null) {
            printer.getWriter().write(" ORDER SIBLINGS BY ");

            for (Enumeration expressionEnum = orderSiblingsBy.elements();
                     expressionEnum.hasMoreElements();) {
                Expression ex = (Expression)expressionEnum.nextElement();
                ex.printSQL(printer);

                if (expressionEnum.hasMoreElements()) {
                    printer.getWriter().write(", ");
                }
            }
        }
    }

    /**
     * This method will append the order clause to the end of the
     * select statement.
     */
    public void appendOrderClauseToWriter(ExpressionSQLPrinter printer) throws IOException {
        if (!hasOrderByExpressions()) {
            return;
        }

        printer.getWriter().write(" ORDER BY ");

        for (Enumeration expressionsEnum = getOrderByExpressions().elements();
                 expressionsEnum.hasMoreElements();) {
            Expression expression = (Expression)expressionsEnum.nextElement();
            expression.printSQL(printer);

            if (expressionsEnum.hasMoreElements()) {
                printer.getWriter().write(", ");
            }
        }
    }

    /**
     * INTERNAL: Alias the tables in all of our nodes.
     */
    public void assignAliases(Vector allExpressions) {
        // For sub-selects all statements must share aliasing information.
        // For CR#2627019
        currentAliasNumber = getCurrentAliasNumber();

        ExpressionIterator iterator = new ExpressionIterator() {
            public void iterate(Expression each) {
                currentAliasNumber = each.assignTableAliasesStartingAt(currentAliasNumber);
            }
        };

        if (allExpressions.isEmpty()) {
            // bug 3878553 - ensure aliases are always assigned for when required .
            if ((getBuilder() != null) && requiresAliases()) {
                getBuilder().assignTableAliasesStartingAt(currentAliasNumber);
            }
        } else {
            for (Enumeration expressionEnum = allExpressions.elements();
                     expressionEnum.hasMoreElements();) {
                Expression expression = (Expression)expressionEnum.nextElement();
                iterator.iterateOn(expression);
            }
        }

        // For sub-selects update aliasing information of all statements.
        // For CR#2627019
        setCurrentAliasNumber(currentAliasNumber);
    }

    /**
     * Print the SQL representation of the statement on a stream.
     */
    public DatabaseCall buildCall(AbstractSession session) {
        SQLCall call = new SQLCall();
        call.returnManyRows();

        Writer writer = new CharArrayWriter(200);

        ExpressionSQLPrinter printer = new ExpressionSQLPrinter(session, getTranslationRow(), call, requiresAliases());
        printer.setWriter(writer);

        call.setFields(printSQL(printer));
        call.setSQLString(writer.toString());

        return call;
    }

    /**
     * INTERNAL:
     * This is used by cursored stream to determine if an expression used distinct as the size must account for this.
     */
    public void computeDistinct() {
        ExpressionIterator iterator = new ExpressionIterator() {
            public void iterate(Expression expression) {
                if (expression.isQueryKeyExpression() && ((QueryKeyExpression)expression).shouldQueryToManyRelationship()) {
                    // Aggregate should only use distinct as specified by the user.
                    if (!isDistinctComputed()) {
                        useDistinct();
                    }
                }
            }
        };

        if (getWhereClause() != null) {
            iterator.iterateOn(getWhereClause());
        }
    }

    public boolean isSubSelect() {
        return (getParentStatement() != null);
    }

    /**
     * INTERNAL:
     * Computes all aliases which will appear in the FROM clause.
     */
    public void computeTables() {
        // Compute tables should never defer to computeTablesFromTables
        // This iterator will pull all the table aliases out of an expression, and
        // put them in a hashtable.
        ExpressionIterator iterator = new ExpressionIterator() {
            public void iterate(Expression each) {
                TableAliasLookup aliases = each.getTableAliases();

                if (aliases != null) {
                    // Insure that an aliased table is only added to a single
                    // FROM clause.
                    if (!aliases.haveBeenAddedToStatement()) {
                        aliases.addToHashtable((Hashtable)getResult());
                        aliases.setHaveBeenAddedToStatement(true);
                    }
                }
            }
        };

        iterator.setResult(new Hashtable(5));

        if (getWhereClause() != null) {
            iterator.iterateOn(getWhereClause());
        } else if (hasOuterJoinExpressions()) {
            Expression outerJoinCriteria = (Expression)getOuterJoinedMappingCriteria().firstElement();
            if (outerJoinCriteria != null){
                iterator.iterateOn(outerJoinCriteria);
            }
        }

        //Iterate on fields as well in that rare case where the select is not in the where clause
        for (Iterator fields = getFields().iterator(); fields.hasNext();) {
            Object field = fields.next();
            if (field instanceof Expression) {
                iterator.iterateOn((Expression)field);
            }
        }

        // Always iterator on the builder, as the where clause may not contain the builder, i.e. value=value.
        iterator.iterateOn(getBuilder());

        Hashtable allTables = (Hashtable)iterator.getResult();
        setTableAliases(allTables);

        for (Enumeration e = allTables.elements(); e.hasMoreElements();) {
            addTable((DatabaseTable)e.nextElement());
        }
    }

    /**
     * If there is no where clause, alias the tables from the tables list directly. Assume there's
     * no ambiguity
     */
    public void computeTablesFromTables() {
        Hashtable allTables = new Hashtable();

        for (int index = 0; index < getTables().size(); index++) {
            DatabaseTable next = (DatabaseTable)getTables().elementAt(index);
            DatabaseTable alias = new DatabaseTable("t" + (index));
            allTables.put(alias, next);
        }

        setTableAliases(allTables);
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void dontUseDistinct() {
        setDistinctState(ObjectLevelReadQuery.DONT_USE_DISTINCT);
    }

    /**
     * Check if the field from the field expression is already contained in the select clause of the statement.
     * This is used on order by expression when the field being ordered by must be in the select,
     * but cannot be in the select twice.
     */
    protected boolean fieldsContainField(Vector fields, Expression expression) {
        DatabaseField orderByField;

        if (expression instanceof DataExpression) {
            orderByField = ((DataExpression)expression).getField();
        } else {
            return false;
        }

        //check all fields for a match
        for (Enumeration fieldsEnum = fields.elements(); fieldsEnum.hasMoreElements();) {
            Object fieldOrExpression = fieldsEnum.nextElement();

            if (fieldOrExpression instanceof DatabaseField) {
                DatabaseField field = (DatabaseField)fieldOrExpression;
                DataExpression exp = (DataExpression)expression;

                if (field.equals(orderByField) && (exp.getBaseExpression() == getBuilder())) {
                    // found a match
                    return true;
                }
            }
            // For CR#2589. This method was not getting the fields in the same way that
            // printSQL does (i.e. using getFields() instead of getField()).
            // The problem was that getField() on an expression builder led to a null pointer
            // exception.
            else {
                Expression exp = (Expression)fieldOrExpression;
                DatabaseTable table = orderByField.getTable();

                if (exp.getFields().contains(orderByField) && (expression.aliasForTable(table).equals(exp.aliasForTable(table)))) {
                    //found a match
                    return true;
                }
            }
        }

        // no matches
        return false;
    }

    /**
     * Gets a unique id that will be used to alias the next table.
     * For sub-selects all must use this same aliasing information, maintained
     * in the root enclosing statement. For CR#2627019
     */
    public int getCurrentAliasNumber() {
        if (getParentStatement() != null) {
            return getParentStatement().getCurrentAliasNumber();
        } else {
            return currentAliasNumber;
        }
    }

    /**
     * INTERNAL:
     * Return all the fields
     */
    public Vector getFields() {
        return fields;
    }

    protected ForUpdateClause getForUpdateClause() {
        return forUpdateClause;
    }

    /**
     * INTERNAL:
     * Return the group bys.
     */
    public Vector getGroupByExpressions() {
        if (groupByExpressions == null) {
            groupByExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(3);
        }

        return groupByExpressions;
    }

    /**
     * INTERNAL:
     * Return the having expression.
     */
    public Expression getHavingExpression() {
        return havingExpression;
    }

    /**
     * INTERNAL:
     * Return the StartWith expression
     */
    public Expression getStartWithExpression() {
        return startWithExpression;
    }

    /**
     * INTERNAL:
     * Return the CONNECT BY expression
     */
    public Expression getConnectByExpression() {
        return connectByExpression;
    }

    /**
     * INTERNAL:
     * Return the ORDER SIBLINGS BY expression
     */
    public Vector getOrderSiblingsByExpressions() {
        return orderSiblingsByExpressions;
    }

    /**
     * Return the fields we don't want to select but want to join on.
     */
    public Vector getNonSelectFields() {
        return nonSelectFields;
    }
    
    /**
     * INTERNAL:
     * Return the order expressions for the query.
     */
    public Vector getOrderByExpressions() {
        if (orderByExpressions == null) {
            orderByExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(3);
        }

        return orderByExpressions;
    }

    /**
     * INTERNAL:
     * Each Vector's element is a map of tables join expressions keyed by the tables
     */
    public Vector getOuterJoinedAdditionalJoinCriteria() {
        if (outerJoinedAdditionalJoinCriteria == null) {
            outerJoinedAdditionalJoinCriteria = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(3);
        }

        return outerJoinedAdditionalJoinCriteria;
    }

    public Vector getOuterJoinedMappingCriteria() {
        if (outerJoinedMappingCriteria == null) {
            outerJoinedMappingCriteria = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(3);
        }

        return outerJoinedMappingCriteria;
    }

    public Vector getOuterJoinExpressions() {
        if (outerJoinedExpressions == null) {
            outerJoinedExpressions = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(3);
        }

        return outerJoinedExpressions;
    }

    public List getDescriptorsForMultitableInheritanceOnly() {
        if (descriptorsForMultitableInheritanceOnly == null) {
            descriptorsForMultitableInheritanceOnly = new ArrayList(3);
        }

        return descriptorsForMultitableInheritanceOnly;
    }

    /**
     * Return the parent statement if using subselects.
     * This is used to normalize correctly with subselects.
     */
    public SQLSelectStatement getParentStatement() {
        return parentStatement;
    }

    /**
     * INTERNAL:
     * Return the aliases used.
     */
    public Hashtable getTableAliases() {
        return tableAliases;
    }

    /**
     * INTERNAL:
     * Return all the tables.
     */
    public Vector getTables() {
        return tables;
    }

    protected boolean hasAliasForTable(DatabaseTable table) {
        if (tableAliases != null) {
            return getTableAliases().containsKey(table);
        }

        return false;
    }

    public boolean hasGroupByExpressions() {
        return (groupByExpressions != null) && (!groupByExpressions.isEmpty());
    }

    public boolean hasHavingExpression() {
        return (havingExpression != null);
    }

    public boolean hasStartWithExpression() {
        return startWithExpression != null;
    }

    public boolean hasConnectByExpression() {
        return connectByExpression != null;
    }

    public boolean hasOrderSiblingsByExpressions() {
        return orderSiblingsByExpressions != null;
    }

    public boolean hasHierarchicalQueryExpressions() {
        return ((startWithExpression != null) || (connectByExpression != null) || (orderSiblingsByExpressions != null));
    }

    public boolean hasOrderByExpressions() {
        return (orderByExpressions != null) && (!orderByExpressions.isEmpty());
    }
    
    public boolean hasNonSelectFields() {
        return (nonSelectFields != null) && (!nonSelectFields.isEmpty());
    }

    public boolean hasOuterJoinedAdditionalJoinCriteria() {
        return (outerJoinedAdditionalJoinCriteria != null) && (!outerJoinedAdditionalJoinCriteria.isEmpty());
    }

    public boolean hasOuterJoinExpressions() {
        return (outerJoinedExpressions != null) && (!outerJoinedExpressions.isEmpty());
    }

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

    /**
     * INTERNAL:
     * return true if this query has computed its distinct value already
     */
    public boolean isDistinctComputed() {
        return distinctState != ObjectLevelReadQuery.UNCOMPUTED_DISTINCT;
    }

    /**
     * INTERNAL:
     * Normalize an expression into a printable structure.
     * i.e. merge the expression with the join expressions.
     * Also replace table names with corresponding aliases.
     * @param clonedExpressions
     */
    public final void normalize(AbstractSession session, ClassDescriptor descriptor) {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        normalize(session, descriptor, new HardIdentityHashtable());
    }

    /**
     * INTERNAL:
     * Normalize an expression into a printable structure.
     * i.e. merge the expression with the join expressions.
     * Also replace table names with corresponding aliases.
     * @param clonedExpressions With 2612185 allows additional expressions
     * from multiple bases to be rebuilt on the correct cloned base.
     */
    public void normalize(AbstractSession session, ClassDescriptor descriptor, Dictionary clonedExpressions) {
        // Initialize the builder.
        if (getBuilder() == null) {
            if (getWhereClause() == null) {
                setBuilder(new ExpressionBuilder());
            } else {
                setBuilder(getWhereClause().getBuilder());
            }
        }

        ExpressionBuilder builder = getBuilder();

        // For flashback: The builder is increasingly important. It can store
        // an AsOfClause, needs to be normalized for history, and aliased for
        // pessimistic locking to work. Hence everything that would have
        // been applied to the where clause will be applied to the builder if
        // the former is null. In the past though if there was no where
        // clause just threw away the builder (to get to this point we had to
        // pass it in via the vacated where clause), and neither normalized nor
        // aliased it directly.
        if (getWhereClause() == builder) {
            setWhereClause(null);
        }

        builder.setSession(session.getRootSession(null));

        // Some queries are not on objects but for data, thus no descriptor.
        if (!builder.doesNotRepresentAnObjectInTheQuery()) {
            if (descriptor != null) {
                Class queryClass = builder.getQueryClass();
                // GF 2333 Only change the descriptor class if it is not set, or if this is an inheritance query
                if ((queryClass == null) || descriptor.isChildDescriptor()) {
                    builder.setQueryClass(descriptor.getJavaClass());
                }
            }
        }

        // Compute all other expressions used other than the where clause, i.e. select, order by, group by.
        // Also must ensure that all expression use a unique builder.
        Vector allExpressions = new Vector();

        // Process select expressions.
        rebuildAndAddExpressions(getFields(), allExpressions, builder, clonedExpressions);

        // Process non-select expressions
        if (hasNonSelectFields()) {
            rebuildAndAddExpressions(getNonSelectFields(), allExpressions, builder, clonedExpressions);
        }
        
        // Process group by expressions.
        if (hasGroupByExpressions()) {
            rebuildAndAddExpressions(getGroupByExpressions(), allExpressions, builder, clonedExpressions);
        }

        if (hasHavingExpression()) {
            //rebuildAndAddExpressions(getHavingExpression(), allExpressions, builder, clonedExpressions);
            Expression expression = getHavingExpression();
            ExpressionBuilder originalBuilder = expression.getBuilder();
            if (originalBuilder != builder) {
                // For bug 2612185 avoid rebuildOn if possible as it rebuilds all on a single base.
                // i.e. Report query items could be from parallel expressions.
                if (clonedExpressions.get(originalBuilder) != null) {
                    expression = expression.copiedVersionFrom(clonedExpressions);
                } else {
                    // Possibly the expression was built with the wrong builder.
                    expression = expression.rebuildOn(builder);
                }

                setHavingExpression(expression);
            }

            allExpressions.addElement(expression);
        }

        // Process order by expressions.
        if (hasOrderByExpressions()) {
            rebuildAndAddExpressions(getOrderByExpressions(), allExpressions, builder, clonedExpressions);
        }

        // Process outer join by expressions.
        if (hasOuterJoinExpressions()) {
            rebuildAndAddExpressions(getOuterJoinedMappingCriteria(), allExpressions, builder, clonedExpressions);
            for (Iterator criterias = getOuterJoinedAdditionalJoinCriteria().iterator();
                     criterias.hasNext();) {
                rebuildAndAddExpressions((Map)criterias.next(), allExpressions, builder, clonedExpressions);
            }
        }

        //Process hierarchical query expressions.
        if (hasStartWithExpression()) {
            startWithExpression = getStartWithExpression().rebuildOn(builder);
            allExpressions.addElement(startWithExpression);
        }

        if (hasConnectByExpression()) {
            connectByExpression = getConnectByExpression().rebuildOn(builder);
        }

        if (hasOrderSiblingsByExpressions()) {
            rebuildAndAddExpressions(getOrderSiblingsByExpressions(), allExpressions, builder, clonedExpressions);
        }

        // We have to handle the cases where the where
        // clause is initially empty but might have clauses forced into it because the class
        // has multiple tables, order by forces a join, etc. So we have to create a builder
        // and add expressions for it and the extras, but throw it away if they didn't force anything
        Expression oldRoot = getWhereClause();
        ExpressionNormalizer normalizer = new ExpressionNormalizer(this);
        normalizer.setSession(session);

        Expression newRoot = null;

        if (oldRoot != null) {
            newRoot = oldRoot.normalize(normalizer);
        }

        // CR#3166542 always ensure that the builder has been normalized,
        // there may be an expression that does not refer to the builder, i.e. value=value.
        if (descriptor != null) {
            builder.normalize(normalizer);
        }

        for (int index = 0; index < allExpressions.size(); index++) {
            Expression expression = (Expression)allExpressions.elementAt(index);
            expression.normalize(normalizer);
        }

        // Sets the where clause and AND's it with the additional Expression
        // setNormalizedWhereClause must be called to avoid the builder side-effects
        if (newRoot == null) {
            setNormalizedWhereClause(normalizer.getAdditionalExpression());
        } else {
            setNormalizedWhereClause(newRoot.and(normalizer.getAdditionalExpression()));
        }

        if (getWhereClause() != null) {
            allExpressions.addElement(getWhereClause());
        }

        // CR#3166542 always ensure that the builder has been normalized,
        // there may be an expression that does not refer to the builder, i.e. value=value.
        if (descriptor != null) {
            allExpressions.addElement(builder);
        }

        // Must also assign aliases to outer joined mapping criterias.
        if (hasOuterJoinExpressions()) {
            // Check for null on criterias.
            for (Iterator criterias = getOuterJoinedMappingCriteria().iterator();
                     criterias.hasNext();) {
                Expression criteria = (Expression)criterias.next();
                if (criteria != null) {
                    allExpressions.add(criteria);
                }
            }

            // Check for null on criterias.
            for (Iterator criterias = getOuterJoinedAdditionalJoinCriteria().iterator();
                     criterias.hasNext();) {
                Map map = (Map)criterias.next();
                if (map != null) {
                    Iterator it = map.values().iterator();
                    while(it.hasNext()) {
                        Expression criteria = (Expression)it.next();
                        if(criteria != null) {
                            allExpressions.add(criteria);
                        }
                    }
                }
            }
        }

        // Bug 2956674 Remove validate call as validation will be completed as the expression was normalized
        // Assign all table aliases.
        assignAliases(allExpressions);

        // If this is data level then the tables must be set manually.
        if (descriptor == null) {
            computeTablesFromTables();
        } else {
            computeTables();
        }

        // Now that the parent statement has been normalized, aliased, etc.,
        // normalize the subselect expressions. For CR#4223.
        if (normalizer.encounteredSubSelectExpressions()) {
            normalizer.normalizeSubSelects(clonedExpressions);
        }

        // CR2114; If this is data level then we don't have a descriptor.
        // We don't have a target class so we must use the root platform. PWK
        // We are not fixing the informix.
        Class aClass = null;

        if (descriptor != null) {
            aClass = descriptor.getJavaClass();
        }

        // When a distinct is used the order bys must be in the select clause, so this forces them into the select.
        if ((session.getPlatform(aClass).isInformix()) || (shouldDistinctBeUsed() && hasOrderByExpressions())) {
            addOrderByExpressionToSelectForDistinct();
        }
    }

    /**
     * INTERNAL:
     * Normalize an expression mapping all of the descriptor's tables to the view.
     * This is used to allow a descriptor to read from a view, but write to tables.
     * This is used in the multiple table and subclasses read so all of the descriptor's
     * possible tables must be mapped to the view.
     */
    public void normalizeForView(AbstractSession theSession, ClassDescriptor theDescriptor, Dictionary clonedExpressions) {
        ExpressionBuilder builder;

        // bug 3878553 - alias all view selects.
        setRequiresAliases(true);

        if (getWhereClause() != null) {
            builder = getWhereClause().getBuilder();
        } else {
            builder = new ExpressionBuilder();
            setBuilder(builder);
        }

        builder.setViewTable((DatabaseTable)getTables().firstElement());

        normalize(theSession, theDescriptor, clonedExpressions);
    }

    /**
     * Print the SQL representation of the statement on a stream.
     */
    public Vector printSQL(ExpressionSQLPrinter printer) {
        try {
            Vector selectFields = null;
            printer.setRequiresDistinct(shouldDistinctBeUsed());
            printer.printString("SELECT ");

            if (shouldDistinctBeUsed()) {
                printer.printString("DISTINCT ");
            }

            selectFields = writeFieldsIn(printer);

            appendFromClauseToWriter(printer);

            if (!(getWhereClause() == null)) {
                printer.printString(" WHERE ");
                printer.printExpression(getWhereClause());
            }

            if (hasHierarchicalQueryExpressions()) {
                appendHierarchicalQueryClauseToWriter(printer);
            }

            if (hasGroupByExpressions()) {
                appendGroupByClauseToWriter(printer);
            }
            if (hasHavingExpression()) {
                //appendHavingClauseToWriter(printer);
                printer.printString(" HAVING ");
                printer.printExpression(getHavingExpression());
            }

            if (hasOrderByExpressions()) {
                appendOrderClauseToWriter(printer);
            }

            // For pessimistic locking.
            if (getForUpdateClause() != null) {
                getForUpdateClause().printSQL(printer, this);
            }

            return selectFields;
        } catch (IOException exception) {
            throw ValidationException.fileError(exception);
        }
    }

    /**
     * Rebuild the expressions with the correct expression builder if using a different one.
     */
    public void rebuildAndAddExpressions(Vector expressions, Vector allExpressions, ExpressionBuilder primaryBuilder, Dictionary clonedExpressions) {
        for (int index = 0; index < expressions.size(); index++) {
            Object fieldOrExpression = expressions.elementAt(index);

            if (fieldOrExpression instanceof Expression) {
                Expression expression = (Expression)fieldOrExpression;
                ExpressionBuilder originalBuilder = expression.getBuilder();

                if (originalBuilder != primaryBuilder) {
                    // For bug 2612185 avoid rebuildOn if possible as it rebuilds all on a single base.
                    // i.e. Report query items could be from parallel expressions.
                    if (clonedExpressions.get(originalBuilder) != null) {
                        expression = expression.copiedVersionFrom(clonedExpressions);
                        //if there is no builder or it is a copy of the base builder then rebuild otherwise it is a parallel expression not joined
                    }
                    if (originalBuilder.wasQueryClassSetInternally()) {
                        // Possibly the expression was built with the wrong builder.
                        expression = expression.rebuildOn(primaryBuilder);
                    }
                    expressions.setElementAt(expression, index);
                }

                allExpressions.addElement(expression);
            }
        }
    }

    /**
     * Rebuild the expressions with the correct expression builder if using a different one.
     * Exact copy of the another rebuildAndAddExpressions adopted to a Map with Expression values
     * as the first parameter (instead of Vector in the original method)
     */
    public void rebuildAndAddExpressions(Map expressions, Vector allExpressions, ExpressionBuilder primaryBuilder, Dictionary clonedExpressions) {
        Iterator it = expressions.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            Object fieldOrExpression = entry.getValue();

            if (fieldOrExpression instanceof Expression) {
                Expression expression = (Expression)fieldOrExpression;
                ExpressionBuilder originalBuilder = expression.getBuilder();

                if (originalBuilder != primaryBuilder) {
                    // For bug 2612185 avoid rebuildOn if possible as it rebuilds all on a single base.
                    // i.e. Report query items could be from parallel expressions.
                    if (clonedExpressions.get(originalBuilder) != null) {
                        expression = expression.copiedVersionFrom(clonedExpressions);
                        //if there is no builder or it is a copy of the base builder then rebuild otherwise it is a parallel expression not joined
                    }
                    if (originalBuilder.wasQueryClassSetInternally()) {
                        // Possibly the expression was built with the wrong builder.
                        expression = expression.rebuildOn(primaryBuilder);
                    }

                    entry.setValue(expression);
                }

                allExpressions.addElement(expression);
            }
        }
    }

    /**
     * INTERNAL:
     */
    public void removeField(DatabaseField field) {
        getFields().removeElement(field);
    }

    /**
     * Remove a table from the statement. The table will
     * be dropped from the FROM part of the SQL statement.
     */
    public void removeTable(DatabaseTable table) {
        getTables().removeElement(table);
    }

    /**
     * INTERNAL: Returns true if aliases are required, false otherwise.
     * If requiresAliases is set then force aliasing, this is required for object-rel.
     */
    public boolean requiresAliases() {
        if (requiresAliases || hasOuterJoinExpressions()) {
            return true;
        }

        if (tableAliases != null) {
            return getTableAliases().size() > 1;
        }

        // tableAliases is null
        return false;
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void resetDistinct() {
        setDistinctState(ObjectLevelReadQuery.UNCOMPUTED_DISTINCT);
    }

    /**
     * Sets a unique id that will be used to alias the next table.
     * For sub-selects all must use this same aliasing information, maintained
     * in the root enclosing statement. For CR#2627019
     */
    public void setCurrentAliasNumber(int currentAliasNumber) {
        if (getParentStatement() != null) {
            getParentStatement().setCurrentAliasNumber(currentAliasNumber);
        } else {
            this.currentAliasNumber = currentAliasNumber;
        }
    }

    /**
     * Set the non select fields. The fields are used only on joining.
     */
    public void setNonSelectFields(Vector nonSelectFields) {
        this.nonSelectFields = nonSelectFields;
    }

    /**
     * Set the where clause expression.
     * This must be used during normalization as the normal setWhereClause has the side effect
     * of setting the builder, which must not occur during normalize.
     */
    public void setNormalizedWhereClause(Expression whereClause) {
        this.whereClause = whereClause;
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void setDistinctState(short distinctState) {
        this.distinctState = distinctState;
    }

    /**
     * INTERNAL:
     * Set the fields, if any are aggregate selects then record this so that the distinct is not printed through anyOfs.
     */
    public void setFields(Vector fields) {
        for (Enumeration fieldsEnum = fields.elements(); fieldsEnum.hasMoreElements();) {
            Object fieldOrExpression = fieldsEnum.nextElement();

            if (fieldOrExpression instanceof FunctionExpression) {
                if (((FunctionExpression)fieldOrExpression).getOperator().isAggregateOperator()) {
                    setIsAggregateSelect(true);

                    break;
                }
            }
        }

        this.fields = fields;
    }

    public void setGroupByExpressions(Vector expressions) {
        this.groupByExpressions = expressions;
    }

    public void setHavingExpression(Expression expressions) {
        this.havingExpression = expressions;
    }

    /**
     * INTERNAL:
     * takes the hierarchical query expression which have been set on the query and sets them here
     * used to generate the Hierarchical Query Clause in the SQL
     */
    public void setHierarchicalQueryExpressions(Expression startWith, Expression connectBy, Vector orderSiblingsExpressions) {
        this.startWithExpression = startWith;
        this.connectByExpression = connectBy;
        this.orderSiblingsByExpressions = orderSiblingsExpressions;
    }

    public void setIsAggregateSelect(boolean isAggregateSelect) {
        this.isAggregateSelect = isAggregateSelect;
    }

    protected void setForUpdateClause(ForUpdateClause clause) {
        this.forUpdateClause = clause;
    }

    public void setLockingClause(ForUpdateClause lockingClause) {
        this.forUpdateClause = lockingClause;
    }

    public void setOrderByExpressions(Vector orderByExpressions) {
        this.orderByExpressions = orderByExpressions;
    }

    public void setOuterJoinedAdditionalJoinCriteria(Vector outerJoinedAdditionalJoinCriteria) {
        this.outerJoinedAdditionalJoinCriteria = outerJoinedAdditionalJoinCriteria;
    }

    public void setOuterJoinedMappingCriteria(Vector outerJoinedMappingCriteria) {
        this.outerJoinedMappingCriteria = outerJoinedMappingCriteria;
    }

    public void setOuterJoinExpressions(Vector outerJoinedExpressions) {
        this.outerJoinedExpressions = outerJoinedExpressions;
    }

    /**
     * Set the parent statement if using subselects.
     * This is used to normalize correctly with subselects.
     */
    public void setParentStatement(SQLSelectStatement parentStatement) {
        this.parentStatement = parentStatement;
    }

    public void setRequiresAliases(boolean requiresAliases) {
        this.requiresAliases = requiresAliases;
    }

    protected void setTableAliases(Hashtable theTableAliases) {
        tableAliases = theTableAliases;
    }

    public void setTables(Vector theTables) {
        tables = theTables;
    }

    /**
     * INTERNAL:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is required for batch reading.
     */
    public boolean shouldDistinctBeUsed() {
        return distinctState == ObjectLevelReadQuery.USE_DISTINCT;
    }

    /**
     * ADVANCED:
     * If a distinct has been set the DISTINCT clause will be printed.
     * This is used internally by TopLink for batch reading but may also be
     * used directly for advanced queries or report queries.
     */
    public void useDistinct() {
        setDistinctState(ObjectLevelReadQuery.USE_DISTINCT);
    }

    /**
     * INTERNAL:
     */
    protected void writeField(ExpressionSQLPrinter printer, DatabaseField field) {
        //print ", " before each selected field except the first one
        if (printer.isFirstElementPrinted()) {
            printer.printString(", ");
        } else {
            printer.setIsFirstElementPrinted(true);
        }

        if (printer.shouldPrintQualifiedNames()) {
            if (field.getTable() != lastTable) {
                lastTable = field.getTable();
                currentAlias = getBuilder().aliasForTable(lastTable);

                // This is really for the special case where things were pre-aliased
                if (currentAlias == null) {
                    currentAlias = lastTable;
                }
            }

            printer.printString(currentAlias.getQualifiedName());
            printer.printString(".");
            printer.printString(field.getName());
        } else {
            printer.printString(field.getName());
        }
    }

    /**
     * INTERNAL:
     */
    protected void writeFieldsFromExpression(ExpressionSQLPrinter printer, Expression expression, Vector newFields) {
        expression.writeFields(printer, newFields, this);
    }

    /**
     * INTERNAL:
     */
    protected Vector writeFieldsIn(ExpressionSQLPrinter printer) {
        this.lastTable = null;

        Vector newFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();

        for (Enumeration fieldsEnum = getFields().elements(); fieldsEnum.hasMoreElements();) {
            Object next = fieldsEnum.nextElement();

            if (next instanceof Expression) {
                writeFieldsFromExpression(printer, (Expression)next, newFields);
            } else {
                writeField(printer, (DatabaseField)next);
                newFields.addElement(next);
            }
        }

        return newFields;
    }

    /**
     * INTERNAL:
     * The method searches for expressions that join two tables each in a given expression.
     * Given expression and tablesInOrder and an empty SortedMap (TreeMap with no Comparator), this method
     * populates the map with expressions corresponding to two tables
     * keyed by an index (in tablesInOrder) of the table with the highest (of two) index;
     * returns all the participating in at least one of the expressions.
     * Example:
     * expression (joining Employee to Project through m-m mapping "projects"):
     * (employee.emp_id = proj_emp.emp_id) and (proj_emp.proj_id = project.proj_id)
     * tablesInOrder:
     * employee, proj_emp, project
     *
     * results:
     * map:
     * 1 -> (employee.emp_id = proj_emp.emp_id)
     * 2 -> (proj_emp.proj_id = project.proj_id)
     * returned SortedSet: {0, 1, 2}.
     *
     * Note that tablesInOrder must contain all tables used by expression
     */
    protected static SortedSet mapTableIndexToExpression(Expression expression, SortedMap map, Vector tablesInOrder) {
        // glassfish issue 2440:
        // - Use DataExpression.getAliasedField instead of getField. This
        // allows to distinguish source and target tables in case of a self
        // referencing relationship.
        // - Removed the block handling ParameterExpressions, because it is
        // not possible to get into that method with a ParameterExpression.
        TreeSet tables = new TreeSet();
        if(expression instanceof DataExpression) {
            DataExpression de = (DataExpression)expression;
            if(de.getAliasedField() != null) {
                tables.add(new Integer(tablesInOrder.indexOf(de.getAliasedField().getTable())));
            }
        } else if(expression instanceof CompoundExpression) {
            CompoundExpression ce = (CompoundExpression)expression;
            tables.addAll(mapTableIndexToExpression(ce.getFirstChild(), map, tablesInOrder));
            tables.addAll(mapTableIndexToExpression(ce.getSecondChild(), map, tablesInOrder));
        } else if(expression instanceof FunctionExpression) {
            FunctionExpression fe = (FunctionExpression)expression;
            Iterator it = fe.getChildren().iterator();
            while(it.hasNext()) {
                tables.addAll(mapTableIndexToExpression((Expression)it.next(), map, tablesInOrder));
            }
        }
        
        if(tables.size() == 2) {
            map.put(tables.last(), expression);
        }
        
        return tables;
    }

    /**
     * INTERNAL:
     * The method searches for expressions that join two tables each in a given expression.
     * Given expression and tablesInOrder, this method
     * returns the map with expressions corresponding to two tables
     * keyed by tables (from tablesInOrder) with the highest (of two) index;
     * Example:
     * expression (joining Employee to Project through m-m mapping "projects"):
     * (employee.emp_id = proj_emp.emp_id) and (proj_emp.proj_id = project.proj_id)
     * tablesInOrder:
     * employee, proj_emp, project
     *
     * results:
     * returned map:
     * proj_emp -> (employee.emp_id = proj_emp.emp_id)
     * project -> (proj_emp.proj_id = project.proj_id)
     *
     * Note that tablesInOrder must contain all tables used by expression
     */
    public static Map mapTableToExpression(Expression expression, Vector tablesInOrder) {
        TreeMap indexToExpressionMap = new TreeMap();
        mapTableIndexToExpression(expression, indexToExpressionMap, tablesInOrder);
        HashMap map = new HashMap(indexToExpressionMap.size());
        Iterator it = indexToExpressionMap.entrySet().iterator();
        while(it.hasNext()) {
            Map.Entry entry = (Map.Entry)it.next();
            int index = ((Integer)entry.getKey()).intValue();
            map.put(tablesInOrder.elementAt(index), entry.getValue());
        }
        return map;
    }

    // Outer join support methods / classes
        
    /**
     * This class manages outer join expressions. It stores them per source
     * aliases, resolves nested joins and provides a method retuning a list of
     * outer join expressions in the correct order for code generation.
     */
    static class OuterJoinExpressionHolders {
        
        /**
         * key: sourceAlias name
         * value: list of OuterJoinExpressionHolder instances
         */
        Map sourceAlias2HoldersMap = new HashMap();

        /**
         * Adds the specified outer join expression holder to the
         * internal map.
         */
        void add(OuterJoinExpressionHolder holder) {
            Object key = holder.sourceAlias.getName();
            List holders = (List)sourceAlias2HoldersMap.get(key);
            if (holders == null) {
                holders = new ArrayList();
                sourceAlias2HoldersMap.put(key, holders);
            }
            holders.add(holder);
        }
        
        /**
         * Returns a list of OuterJoinExpressionHolder instances in the correct order.
         */
        List linearize() {
            // Handle nested joins:
            // Iterate the lists of OuterJoinExpressionHolder instances stored as
            // values in the map. For each OuterJoinExpressionHolder check whether
            // the target alias has an entry in the map. If so, this is a
            // nested join. Get the list of OuterJoinExpressionHolder instance from
            // the map and store it as nested joins in the current
            // OuterJoinExpressionHolder instance being processed.
            List remove = new ArrayList();
            for (Iterator i = sourceAlias2HoldersMap.values().iterator();
                 i.hasNext(); ) {
                List holders = (List)i.next();
                for (Iterator j = holders.iterator(); j.hasNext();) {
                    OuterJoinExpressionHolder holder = (OuterJoinExpressionHolder)j.next();
                    Object key = holder.targetAlias.getName();
                    List nested = (List)sourceAlias2HoldersMap.get(key);
                    if (nested != null) {
                        remove.add(key);
                        holder.nested = nested;
                    }
                }
            }

            // Remove the entries from the map that are handled as nested joins.
            for (Iterator r = remove.iterator(); r.hasNext();) {
                sourceAlias2HoldersMap.remove(r.next());
            }
        
            // Linearize the map:
            // Iterate the remaining lists of OuterJoinExpressionHolder instances
            // stored as values in the map. Add the OuterJoinExpressionHolder to the
            // result list plus its nested joins in depth first order.
            List outerJoinExprHolders = new ArrayList();
            for (Iterator i = sourceAlias2HoldersMap.values().iterator();
                 i.hasNext();) {
                List holders = (List)i.next();
                addHolders(outerJoinExprHolders, holders);
            }
            
            return outerJoinExprHolders;
        }

        /**
         * Add all elements from the specified holders list to the specified
         * result list. If a holder has nested join add them to the result
         * list before processing its sibling (depth first order).
         */
        void addHolders(List result, List holders) {
            if ((holders == null) || holders.isEmpty()) {
                // nothing to be done
                return;
            }
            for (Iterator i = holders.iterator(); i.hasNext();) {
                OuterJoinExpressionHolder holder = (OuterJoinExpressionHolder)i.next();
                // add current holder
                result.add(holder);
                // add nested holders, if any
                addHolders(result, holder.nested);
            }
        }
    }
    

    /**
     * Holder class storing a QueryKeyExpression representing an outer join
     * plus some data calculated by method appendFromClauseForOuterJoin.
     */
    static class OuterJoinExpressionHolder
    {
        final QueryKeyExpression joinExpression;
        final int index;
        final DatabaseTable targetTable;
        final DatabaseTable sourceTable;
        final DatabaseTable targetAlias;
        final DatabaseTable sourceAlias;
        List nested;
        
        public OuterJoinExpressionHolder(QueryKeyExpression joinExpression,
                                         int index,
                                         DatabaseTable targetTable,
                                         DatabaseTable sourceTable,
                                         DatabaseTable targetAlias,
                                         DatabaseTable sourceAlias) {
            this.joinExpression = joinExpression;
            this.index = index;
            this.targetTable = targetTable;
            this.sourceTable = sourceTable;
            this.targetAlias = targetAlias;
            this.sourceAlias = sourceAlias;
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.config;

import java.util.Map;
import java.util.HashMap;

/**
 *
 * The class defines TopLink properties' names.
 *
 * JPA persistence properties could be specified either in PersistenceUnit or
 * passes to createEntityManagerFactory / createContainerEntityManagerFactory
 * methods of EntityManagerFactoryProvider.
 *
 * Property values are usually case-insensitive with some common sense exceptions,
 * for instance class names.
 *
 * @see CacheType
 * @see TargetDatabase
 * @see TargetServer
 *
 */
public class TopLinkProperties {
    //Persistence Unit Properties
    public static final String TRANSACTION_TYPE = "javax.persistence.transactionType";
    public static final String JTA_DATASOURCE = "javax.persistence.jtaDataSource";
    public static final String NON_JTA_DATASOURCE = "javax.persistence.nonJtaDataSource";
    // Connection properties.
    public static final String JDBC_DRIVER = "toplink.jdbc.driver";
    public static final String JDBC_URL = "toplink.jdbc.url";
    // use "" to reset user name
    public static final String JDBC_USER = "toplink.jdbc.user";
    public static final String JDBC_PASSWORD = "toplink.jdbc.password";

    //specifies, whether there should be used hard or soft references in the Persistence Context.
    //Default is "HARD". With soft references entities no longer referenced by the application
    // may be garbage collected freeing resources. Any changes that have not been flushed in these
    // entities will be lost.
    public static final String PERSISTENCE_CONTEXT_REFERENCE_MODE="toplink.persistence.context.reference.mode";
    
    // TopLink JDBC (internal) connection pools properties. Ignored in case external connection pools are used.
    // Maximum number of connections in TopLink write connection pool by default is 10.
    public static final String JDBC_WRITE_CONNECTIONS_MAX = "toplink.jdbc.write-connections.max";
    // Minimum number of connections in TopLink write connection pool by default is 5.
    public static final String JDBC_WRITE_CONNECTIONS_MIN = "toplink.jdbc.write-connections.min";
    // Maximum number of connections in TopLink read connection pool by default is 2.
    public static final String JDBC_READ_CONNECTIONS_MAX = "toplink.jdbc.read-connections.max";
    // Minimum number of connections in TopLink read connection pool by default is 2.
    public static final String JDBC_READ_CONNECTIONS_MIN = "toplink.jdbc.read-connections.min";
    // Indicates wheather connections in TopLink read connection pool should be shared.
    // Valid values are case-insensitive "false" and "true"; "false" is default.
    public static final String JDBC_READ_CONNECTIONS_SHARED = "toplink.jdbc.read-connections.shared";

    // Bind all parameters property. Valid values are case-insensitive "true" and "false"; "true" is default.
    public static final String JDBC_BIND_PARAMETERS = "toplink.jdbc.bind-parameters";

    // Caching Prefixes
    // Property names formed out of these prefixes by appending either
    // entity name, or class name (indicating that the property values applies only to a particular entity)
    // or DEFAULT suffix (indicating that the property value applies to all entities).
    // CACHE_SIZE_ properties default value is 1000
    public static final String CACHE_SIZE_ = "toplink.cache.size.";
    // All valid values for CACHE_TYPE_ properties are declared in CacheType class.
    public static final String CACHE_TYPE_ = "toplink.cache.type.";
    // Indicates whether entity's cache should be shared.
    // Valid values are case-insensitive "false" and "true"; "false" is default.
    public static final String CACHE_SHARED_ = "toplink.cache.shared.";
    
    // Default Suffix could be appended to some prefixes to form a property name
    public static final String DEFAULT = "default";
    
    // Default caching properties - apply to all entities.
    // May be overridden by individual entity property with the same prefix.
    public static final String CACHE_SIZE_DEFAULT = CACHE_SIZE_ + DEFAULT;
    public static final String CACHE_TYPE_DEFAULT = CACHE_TYPE_ + DEFAULT;
    public static final String CACHE_SHARED_DEFAULT = CACHE_SHARED_ + DEFAULT;

    // Customizations properties

    // The type of logger. By default DefaultSessionLog is used.
    // Valid values are the logger class name which implements oracle.toplink.essentials.logging.SessionLog
    // or one of values defined in LoggerType.
    public static final String LOGGING_LOGGER = "toplink.logging.logger";
    // Valid values are names of levels defined in java.util.logging.Level,
    // default value is java.util.logging.Level.CONFIG.getName()
    public static final String LOGGING_LEVEL = "toplink.logging.level";
    
    // Category-specific logging level prefix
    // Property names formed out of this prefix by appending a category name
    // e.g.) toplink.logging.level.sql
    // Valid categories are defined in SessionLog
    public static final String CATEGORY_LOGGING_LEVEL_ = "toplink.logging.level.";
    
    // By default ("true") the date is always logged.
    // This can be turned off ("false").
    public static final String LOGGING_TIMESTAMP = "toplink.logging.timestamp";
    // By default ("true") the thread is logged at FINE or less level.
    // This can be turned off ("false").
    public static final String LOGGING_THREAD = "toplink.logging.thread";
    // By default ("true") the Session is always printed whenever available.
    // This can be turned off ("false").
    public static final String LOGGING_SESSION = "toplink.logging.session";
    // By default ("true") stack trace is logged for SEVERE all the time and at FINER level for WARNING or less.
    // This can be turned off ("false").
    public static final String LOGGING_EXCEPTIONS = "toplink.logging.exceptions";
    
    // Valid values are defined in TargetDatabase class - they correspond to database platforms currently supported by TopLink.
    // Also a customary database platform may be specified by supplying a full class name.
    // Default value is TargetDatabase.Auto which means TopLink will try to automatically determine
    // the correct database platrorm type.
    public static final String TARGET_DATABASE = "toplink.target-database";
    
    // By default a unique session name is generated by TopLink, but the user
    // can provide a customary session name - and make sure it's unique.
    public static final String SESSION_NAME = "toplink.session-name";
    
    // Indicates whether weaving should be performed - "true" by default.
        public static final String WEAVING = "toplink.weaving";
    
    // Valid values are defined in TargetServer class - they correspond to server platforms currently supported by TopLink.
    // Also a customary server platform may be specified by supplying a full class name.
    // Specifying a name of the class implementing ExternalTransactionController sets
    // CustomServerPlatform with this controller.
    // Default is TargetServer.None - JSE case.
    public static final String TARGET_SERVER = "toplink.target-server";
    
    // Allows session customization. The value is a full name for a class which implements SessionCustomizer.
    // Session customizer called after all other properties have been processed.
    public static final String SESSION_CUSTOMIZER = "toplink.session.customizer";
// Under review public static final String RELATIONSHIPS_FETCH_DEFAULT = "toplink.relationships-fetch-default";

    // Customization Prefix
    // Property names formed out of this prefix by appending either
    // entity name, or class name (indicating that the property values applies only to a particular entity)
    // Allows descriptor customization. The value is a full name for a class which implements DescriptorCustomizer.
    // Only session customizer is called after processing these properties.
    public static final String DESCRIPTOR_CUSTOMIZER_ = "toplink.descriptor.customizer.";
    
    /**
     * Defines EntityManager cache behaviour after a call to flush method
     * followed by a call to clear method.
     * This property could be specified while creating either EntityManagerFactory
     * (either in the map passed to createEntityManagerFactory method or in persistence.xml)
     * or EntityManager (in the map passed to createEntityManager method);
     * the latter overrides the former.
     * @see FlushClearCache
     */
    public static final String FLUSH_CLEAR_CACHE = "toplink.flush-clear.cache";
    
    // The following properties will not be displayed through logging but instead have an alternate value shown in the log
    public static final Map<String, String> PROPERTY_LOG_OVERRIDES = new HashMap<String, String>(1);
    
    //for gf3334, this property force persistence context to read through JTA-managed ("write") connection in case there is an active transaction.
    public static final String JOIN_EXISTING_TRANSACTION = "toplink.transaction.join-existing";
    static {
        PROPERTY_LOG_OVERRIDES.put(JDBC_PASSWORD, "xxxxxx");
    }
    
    public static final String getOverriddenLogStringForProperty(String propertyName){
        return PROPERTY_LOG_OVERRIDES.get(propertyName);
    }
    
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import java.util.*;
import java.io.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p>
 * <b>Purpose</b>: This is the overall collection of changes.
 * <p>
 * <b>Description</b>: It holds all of the object changes and
 * all ObjectChanges, with the same classType and primary keys, referenced in a changeSet should be
 * the same object.
 * <p>
 */
public class UnitOfWorkChangeSet implements Serializable, oracle.toplink.essentials.changesets.UnitOfWorkChangeSet {

    /** This is the collection of ObjectChanges held by this ChangeSet */

    // Holds a hashtable of objectChageSets keyed on class
    transient protected java.util.Hashtable objectChanges;

    //This collection holds the new objects which will have no real identity until inserted
    transient protected java.util.Hashtable newObjectChangeSets;
    transient protected oracle.toplink.essentials.internal.helper.HardIdentityHashtable cloneToObjectChangeSet;
    transient protected oracle.toplink.essentials.internal.helper.HardIdentityHashtable objectChangeSetToUOWClone;
    protected HardIdentityHashtable aggregateList;
    protected HardIdentityHashtable allChangeSets;
    protected HardIdentityHashtable deletedObjects;

    /** This attribute is set to true if a changeSet with changes has been added */
    protected boolean hasChanges;
    protected boolean hasForcedChanges;

    /* Collection of ObjectChangeSets that is built from other collections and mapped with SDK */
    private transient Vector sdkAllChangeSets;
    private transient int objectChangeSetCounter = 0;

    /**
     * INTERNAL:
     * Create a ChangeSet
     */
    public UnitOfWorkChangeSet() {
        super();
        this.setHasChanges(false);
    }

    /**
     * INTERNAL:
     * Recreate a UnitOfWorkChangeSet that has been converted to a byte array with the
     * getByteArrayRepresentation() method.
     */
    public UnitOfWorkChangeSet(byte[] bytes) throws java.io.IOException, ClassNotFoundException {
        java.io.ByteArrayInputStream byteIn = new java.io.ByteArrayInputStream(bytes);
        ObjectInputStream objectIn = new ObjectInputStream(byteIn);
                //bug 4416412: allChangeSets set directly instead of using setInternalAllChangeSets
        allChangeSets = (HardIdentityHashtable)objectIn.readObject();
        deletedObjects = (HardIdentityHashtable)objectIn.readObject();
    }

    /**
     * INTERNAL:
     * Add the Deleted objects to the changeSet
     * @param objectChanges prototype.changeset.ObjectChanges
     */
    public void addDeletedObjects(HardIdentityHashtable deletedObjects, AbstractSession session) {
        Enumeration enumtr = deletedObjects.keys();
        while (enumtr.hasMoreElements()) {
            Object object = enumtr.nextElement();

            this.addDeletedObject(object, session);
        }
    }
    
    /**
     * INTERNAL:
     * Add the Deleted object to the changeSet
     * @param objectChanges prototype.changeset.ObjectChanges
     */
    public void addDeletedObject(Object object, AbstractSession session) {
        //CR 4080 - must prevent aggregate objects added to DeletedObjects list
        ClassDescriptor descriptor = session.getDescriptor(object);
        if (!descriptor.isAggregateCollectionDescriptor()) {
            ObjectChangeSet set = descriptor.getObjectBuilder().createObjectChangeSet(object, this, false, session);

            // needed for xml change set
            set.setShouldBeDeleted(true);
            getDeletedObjects().put(set, set);
        }
    }

    /**
     * INTERNAL:
     * Add to the changes for 'object' object to this changeSet. This method will not
     * add to the lists that are used for identity lookups.
     * The passed change set *must* either have changes or forced changes.
     * @see addObjectChangeSetForIdentity()
     * @param objectChanges prototype.changeset.ObjectChanges
     * @param object java.lang.Object
     */
    public void addObjectChangeSet(ObjectChangeSet objectChanges) {
        if ((objectChanges == null)) {
            return;
        }

        // If this object change set has changes or forced changes then record this. Must
        // be done for each change set added because some may not contain 'real' changes. This
        // is the case for opt. read lock and forceUdpate. Keep the flags separate because
        // we don't want to cache sync. a change set with no 'real' changes.
            boolean objectChangeSetHasChanges = objectChanges.hasChanges();
        if (objectChangeSetHasChanges) {
            this.setHasChanges(true);
            this.hasForcedChanges = this.hasForcedChanges || objectChanges.hasForcedChanges();
        } else {
            // object change set doesn't have changes so it has to have forced changes.
            this.hasForcedChanges = true;
        }

        if (!objectChanges.isAggregate()) {
            if (objectChangeSetHasChanges) {
                // Each time I create a changeSet it is added to this list and when I compute a changeSet for this
                // object I again add it to these lists so that before this UOWChangeSet is
                // Serialised there is a copy of every changeSet which has changes affecting cache
                // in allChangeSets
                getAllChangeSets().put(objectChanges, objectChanges);
            }
            
            if (objectChanges.getCacheKey() != null) {
                Hashtable table = (Hashtable)getObjectChanges().get(objectChanges.getClassName());

                if (table == null) {
                    table = new Hashtable(2);
                    getObjectChanges().put(objectChanges.getClassName(), table);
                    table.put(objectChanges, objectChanges);
                } else {
                    table.put(objectChanges, objectChanges);
                }
            }
        }
    }

    /**
     * INTERNAL:
     * Add to the changes for 'object' object to this changeSet. This method will not
     * add to the lists that are used for identity lookups. It is called specifically
     * for new objects, and new object will be moved to the standard changes list by
     * the QueryMechanism after insert.
     * @see addObjectChangeSetForIdentity()
     * @param objectChanges the new object change set
     */
    public void addNewObjectChangeSet(ObjectChangeSet objectChanges, AbstractSession session) {
        if ((objectChanges == null)) {
            return;
        }
        HardIdentityHashtable changeSetTable = (HardIdentityHashtable)getNewObjectChangeSets().get(objectChanges.getClassType(session));
        if (changeSetTable == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            changeSetTable = new HardIdentityHashtable();
            getNewObjectChangeSets().put(objectChanges.getClassType(session), changeSetTable);
        }
        changeSetTable.put(objectChanges, objectChanges);
    }

    /**
     * INTERNAL:
     * This method can be used find the equivalent changeset within this UnitOfWorkChangeSet
     * Aggregates, and new objects without primaryKeys from serialized ChangeSets will not be found
     * Which may result in duplicates, in the UnitOfWorkChangeSet.
     */
    public ObjectChangeSet findObjectChangeSet(ObjectChangeSet changeSet, UnitOfWorkChangeSet mergeFromChangeSet) {
        Hashtable changes = (Hashtable)getObjectChanges().get(changeSet.getClassName());
        ObjectChangeSet potential = null;
        if (changes != null) {
            potential = (ObjectChangeSet)changes.get(changeSet);
        }
        if (potential == null) {
            potential = (ObjectChangeSet)this.getObjectChangeSetForClone(changeSet.getUnitOfWorkClone());
        }
        return potential;
    }

    /**
     * INTERNAL:
     * This method will be used during the merge process to either find an equivalent change set
     * within this UnitOfWorkChangeSet or integrate that changeset into this UOW ChangeSet
     */
    public ObjectChangeSet findOrIntegrateObjectChangeSet(ObjectChangeSet tofind, UnitOfWorkChangeSet mergeFromChangeSet) {
        if (tofind == null) {
            return tofind;
        }
        ObjectChangeSet localChangeSet = this.findObjectChangeSet(tofind, mergeFromChangeSet);
        if (localChangeSet == null) {//not found locally then replace it with the one from the merging changeset
            localChangeSet = new ObjectChangeSet(tofind.getPrimaryKeys(), tofind.getClassType(), tofind.getUnitOfWorkClone(), this, tofind.isNew());
            this.addObjectChangeSetForIdentity(localChangeSet, localChangeSet.getUnitOfWorkClone());
        }
        return localChangeSet;
    }

    /**
     * INTERNAL:
     * Add change records to the lists used to maintain identity. This will not actually
     * add the changes to 'object' to the change set.
     * @see addObjectChangeSet()
     * @param objectChanges prototype.changeset.ObjectChanges
     */
    public void addObjectChangeSetForIdentity(ObjectChangeSet objectChanges, Object object) {
        if ((objectChanges == null) || (object == null)) {
            return;
        }

        if (objectChanges.isAggregate()) {
            getAggregateList().put(objectChanges, objectChanges);
        }

        getObjectChangeSetToUOWClone().put(objectChanges, object);
        getCloneToObjectChangeSet().put(object, objectChanges);

    }

    /**
     * INTERNAL:
     * Get the Aggregate list. Lazy initialises the hashtable if required
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected HardIdentityHashtable getAggregateList() {
        if (aggregateList == null) {
            aggregateList = new HardIdentityHashtable();
        }
        return aggregateList;
    }

    /**
     * INTERNAL:
     * This method returns a reference to the collection
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    public oracle.toplink.essentials.internal.helper.HardIdentityHashtable getAllChangeSets() {
        if (this.allChangeSets == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            this.allChangeSets = new HardIdentityHashtable();
        }
        return allChangeSets;
    }

    /**
     * INTERNAL:
     * Get the clone to object change hash table. Lazy initialises the hashtable if required
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    public oracle.toplink.essentials.internal.helper.HardIdentityHashtable getCloneToObjectChangeSet() {
        if (cloneToObjectChangeSet == null) {
            cloneToObjectChangeSet = new HardIdentityHashtable();
        }
        return cloneToObjectChangeSet;
    }

    /**
     * INTERNAL:
     * This method returns the reference to the deleted objects from the changeSet
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    public oracle.toplink.essentials.internal.helper.HardIdentityHashtable getDeletedObjects() {
        if (this.deletedObjects == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            this.deletedObjects = new HardIdentityHashtable();
        }
        return deletedObjects;
    }

    /**
     * INTERNAL:
     * Returns the ObjectChanges held by this ChangeSet.
     * @return prototype.changeset.ObjectChanges
     */
    public Hashtable getObjectChanges() {
        if (objectChanges == null) {
            objectChanges = new Hashtable(2);
        }
        return objectChanges;
    }

    /**
     * INTERNAL:
     * Returns the set of classes corresponding to updated objects in objectChanges.
     * @return HashSet<Class>
     */
    public Set<Class> findUpdatedObjectsClasses() {
        if(objectChanges == null || objectChanges.isEmpty()) {
            return null;
        }
        HashSet<Class> updatedObjectsClasses = new HashSet<Class>(getObjectChanges().size());
        Iterator it = getObjectChanges().values().iterator();
        while(it.hasNext()) {
            Hashtable table = (Hashtable)it.next();
            Iterator itChangeSets = table.keySet().iterator();
            while(itChangeSets.hasNext()) {
                // any change set will do
                ObjectChangeSet changeSet = (ObjectChangeSet)itChangeSets.next();
                if(!changeSet.isNew()) {
                    // found updated object - add its class to the set
                    updatedObjectsClasses.add(changeSet.getClassType());
                    // and go to the table corresponding to the next class
                    break;
                }
            }
        }
        return updatedObjectsClasses;
    }

    /**
     * ADVANCED:
     * Get ChangeSet for a particular clone
     * @return ObjectChangeSet the changeSet that represents a particular clone
     */
    public oracle.toplink.essentials.changesets.ObjectChangeSet getObjectChangeSetForClone(Object clone) {
        if ((clone == null) || (getCloneToObjectChangeSet() == null)) {
            return null;
        }
        return (oracle.toplink.essentials.changesets.ObjectChangeSet)getCloneToObjectChangeSet().get(clone);
    }

    /**
     * INTERNAL:
     * This method returns a reference to the collection
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected oracle.toplink.essentials.internal.helper.HardIdentityHashtable getObjectChangeSetToUOWClone() {
        if (this.objectChangeSetToUOWClone == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            this.objectChangeSetToUOWClone = new HardIdentityHashtable();
        }
        return objectChangeSetToUOWClone;
    }

    /**
     * ADVANCED:
     * This method returns the Clone for a particular changeSet
     * @return Object the clone represented by the changeSet
     */
    public Object getUOWCloneForObjectChangeSet(oracle.toplink.essentials.changesets.ObjectChangeSet changeSet) {
        if ((changeSet == null) || (getObjectChangeSetToUOWClone() == null)) {
            return null;
        }
        return getObjectChangeSetToUOWClone().get(changeSet);
    }

    /**
     * INTERNAL:
     * Returns true if the Unit Of Work change Set has changes
     */
    public boolean hasChanges() {
        // All of the object change sets were empty (none contained changes)
        // The this.hasChanges variable is set in addObjectChangeSet
        return (this.hasChanges || (!getDeletedObjects().isEmpty()));
    }

    /**
     * INTERNAL:
     * Set whether the Unit Of Work change Set has changes
     */
    public void setHasChanges(boolean flag) {
        this.hasChanges = flag;
    }

    /**
     * INTERNAL:
     * Returns true if this uowChangeSet contains an objectChangeSet that has forced
     * SQL changes. This is true whenever CMPPolicy.getForceUpdate() == true.
     * @return boolean
     */
    public boolean hasForcedChanges() {
        return this.hasForcedChanges;
    }

    /**
     * INTERNAL:
     * This method will be used to merge a change set into an UnitOfWorkChangeSet
     * This method returns the local instance of the changeset
     */
    public ObjectChangeSet mergeObjectChanges(ObjectChangeSet objectChangeSet, UnitOfWorkChangeSet mergeFromChangeSet) {
        ObjectChangeSet localChangeSet = this.findOrIntegrateObjectChangeSet(objectChangeSet, mergeFromChangeSet);
        if (localChangeSet != null) {
            localChangeSet.mergeObjectChanges(objectChangeSet, this, mergeFromChangeSet);
        }
        return localChangeSet;
    }

    /**
    * INTERNAL:
    * THis method will be used to merge another changeset into this changeset. The
    * Main use of this method is for non-deferred writes and checkpointing so that
    * the acumulated changes are collected and merged at the end of the transaction
    *
    */
    public void mergeUnitOfWorkChangeSet(UnitOfWorkChangeSet mergeFromChangeSet, AbstractSession session, boolean postCommit) {
        Iterator iterator = mergeFromChangeSet.getObjectChanges().values().iterator();
        while (iterator.hasNext()) {
            //iterate over the classes
            Hashtable table = (Hashtable)iterator.next();
            Iterator changes = table.values().iterator();
            while (changes.hasNext()) {
                ObjectChangeSet objectChangeSet = (ObjectChangeSet)changes.next();
                objectChangeSet = mergeObjectChanges(objectChangeSet, mergeFromChangeSet);
                if (objectChangeSet.isNew() && !postCommit) {// if it is post commit then we can trust the cache key
                    this.addNewObjectChangeSet(objectChangeSet, session);
                } else {
                    this.addObjectChangeSet(objectChangeSet);
                }
            }
        }

        //merging a serialized UnitOfWorkChangeSet can result in duplicate deletes
        //if a delete for the same object already exists in this UOWChangeSet.
        Enumeration deletedEnum = mergeFromChangeSet.getDeletedObjects().elements();
        while (deletedEnum.hasMoreElements()) {
            ObjectChangeSet objectChangeSet = (ObjectChangeSet)deletedEnum.nextElement();
            ObjectChangeSet localObjectChangeSet = findObjectChangeSet(objectChangeSet, mergeFromChangeSet);
            if (localObjectChangeSet == null) {
                localObjectChangeSet = objectChangeSet;
            }
            this.getDeletedObjects().put(localObjectChangeSet, localObjectChangeSet);
        }
    }

    /**
     * INTERNAL:
     * Used to rehash the new objects back into the objectChanges list for serialization
     */
    public void putNewObjectInChangesList(ObjectChangeSet objectChangeSet, AbstractSession session) {
        this.addObjectChangeSet(objectChangeSet);
        removeObjectChangeSetFromNewList(objectChangeSet, session);
    }

    /**
     * INTERNAL:
     * Used to remove a new object from the new objects list once it has been
     * inserted and added to the objectChangesList
     */
    public void removeObjectChangeSetFromNewList(ObjectChangeSet objectChangeSet, AbstractSession session) {
        HardIdentityHashtable table = (HardIdentityHashtable)getNewObjectChangeSets().get(objectChangeSet.getClassType(session));
        if (table != null) {
            table.remove(objectChangeSet);
        }
    }

    /**
     * INTERNAL:
     * Add the changed Object's records to the ChangeSet
     * @param objectChanges prototype.changeset.ObjectChanges
     */
    public void removeObjectChangeSet(ObjectChangeSet objectChanges) {
        if (objectChanges == null) {
            return;
        }
        Object object = getObjectChangeSetToUOWClone().get(objectChanges);
        if (objectChanges.isAggregate()) {
            getAggregateList().remove(objectChanges);
        } else {
            // Bug 3294426 - index object changes by classname instead of class for remote classloader issues
            Hashtable table = (Hashtable)getObjectChanges().get(object.getClass().getName());
            if (table != null) {
                table.remove(objectChanges);
            }
        }
        getObjectChangeSetToUOWClone().remove(objectChanges);
        if (object != null) {
            getCloneToObjectChangeSet().remove(object);
        }
        getAllChangeSets().remove(objectChanges);
    }

    /**
     * INTERNAL:
     * This method is used to set the hashtable for cloneToObject reference
     * @param newCloneToObjectChangeSet oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected void setCloneToObjectChangeSet(oracle.toplink.essentials.internal.helper.HardIdentityHashtable newCloneToObjectChangeSet) {
        cloneToObjectChangeSet = newCloneToObjectChangeSet;
    }

    /**
     * INTERNAL:
     * Sets the collection of ObjectChanges in the change Set
     * @param newValue prototype.changeset.ObjectChanges
     */
    protected void setObjectChanges(Hashtable objectChanges) {
        this.objectChanges = objectChanges;
    }

    /**
     * INTERNAL:
     * This method is used to insert a new collection into the UOWChangeSet.
     * @param newObjectChangeSetToUOWClone oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected void setObjectChangeSetToUOWClone(oracle.toplink.essentials.internal.helper.HardIdentityHashtable newObjectChangeSetToUOWClone) {
        objectChangeSetToUOWClone = newObjectChangeSetToUOWClone;
    }

    /**
     * INTERNAL:
     * This method will return a reference to the new object change set collections
     */
    public java.util.Hashtable getNewObjectChangeSets() {
        if (this.newObjectChangeSets == null) {
            this.newObjectChangeSets = new java.util.Hashtable();
        }
        return this.newObjectChangeSets;
    }

    /**
     * INTERNAL:
     * This method take a collection of ObjectChangeSet rebuilds this UOW change set to a ready to merge stage
     */
    public void setInternalAllChangeSets(Vector objectChangeSets) {
        if (objectChangeSets == null) {
            return;
        }
        sdkAllChangeSets = objectChangeSets;

        for (int i = 0; i < objectChangeSets.size(); i++) {
            ObjectChangeSet objChangeSet = (ObjectChangeSet)objectChangeSets.elementAt(i);
            objChangeSet.setUOWChangeSet(this);

            if (objChangeSet.isAggregate()) {
                getAggregateList().put(objChangeSet, objChangeSet);

            } else if (objChangeSet.shouldBeDeleted()) {
                getDeletedObjects().put(objChangeSet, objChangeSet);
            } else {
                getAllChangeSets().put(objChangeSet, objChangeSet);
            }
            if (objChangeSet.getCacheKey() != null) {
                Hashtable table = (Hashtable)getObjectChanges().get(objChangeSet.getClassName());
                if (table == null) {
                    table = new Hashtable(2);
                    getObjectChanges().put(objChangeSet.getClassName(), table);
                }
                table.put(objChangeSet, objChangeSet);
            }
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import java.util.*;
import java.io.*;
import oracle.toplink.essentials.config.TopLinkProperties;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.localization.ExceptionLocalization;
import oracle.toplink.essentials.platform.server.ServerPlatform;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.databaseaccess.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.sequencing.Sequencing;
import oracle.toplink.essentials.logging.SessionLog;
import oracle.toplink.essentials.internal.localization.LoggingLocalization;
import oracle.toplink.essentials.sessions.SessionProfiler;
import oracle.toplink.essentials.sessions.UnitOfWork;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.DescriptorEventManager;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;

import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.ejb.cmp3.base.PropertiesHandler;
import oracle.toplink.essentials.internal.queryframework.JoinedAttributeManager;

/**
 * Implementation of oracle.toplink.essentials.sessions.UnitOfWork
 * The public interface should be used by public API and testing, the implementation should be used internally.
 * @see oracle.toplink.essentials.sessions.UnitOfWork
 *
 * <b>Purpose</b>: To allow object level transactions.
 * <p>
 * <b>Description</b>: The unit of work is a session that implements all of the normal
 * protocol of a TopLink session. It can be spawned from any other session including another unit of work.
 * Objects can be brought into the unit of work through reading them or through registering them.
 * The unit of work will opperate on its own object space, that is the objects within the unit of work
 * will be clones of the orignial objects. When the unit of work is commited, all changes to any objects
 * registered within the unit of work will be commited to the database. A minimal commit/update will
 * be performed and any foreign keys/circular reference/referencial integrity will be resolved.
 * If the commit to the database is successful the changed objects will be merged back into the unit of work
 * parent session.
 * <p>
 * <b>Responsibilities</b>:
 * <ul>
 * <li> Allow parallel transactions against a session's objects.
 * <li> Allow nested transactions.
 * <li> Not require the application to write objects that is changes, automatically determine what has changed.
 * <li> Perform a minimal commit/update of all changes that occured.
 * <li> Resolve foreign keys for newly created objects and maintain referencial integrity.
 * <li> Allow for the object transaction to use its own object space.
 * </ul>
 */
public class UnitOfWorkImpl extends AbstractSession implements oracle.toplink.essentials.sessions.UnitOfWork {
    /**
     * Specifies, whether hard, or weak references should be used for
     * the "cloneMapping", "cloneToOriginals", "newObjectsCloneToOriginal", "newObjectsOriginalToClone" and "newAggregates"
     * IdentityHashtables.
     */
    protected ReferenceMode referenceMode;

    /** Fix made for weak caches to avoid garbage collection of the originals. **/
    /** As well as used as lookup in merge algorithm for aggregates and others **/
    protected transient IdentityHashtable cloneToOriginals;
    protected transient AbstractSession parent;

    /** Hashtable of all the clones. The key contains the clone of the object. */
    protected IdentityHashtable cloneMapping;
    protected IdentityHashtable newObjectsCloneToOriginal;
    protected IdentityHashtable newObjectsOriginalToClone;
    protected IdentityHashtable deletedObjects;

    /** This member variable contains a copy of all of the clones for this particular UOW */
    protected IdentityHashtable allClones;
    protected IdentityHashtable objectsDeletedDuringCommit;
    protected IdentityHashtable removedObjects;
    protected IdentityHashtable unregisteredNewObjects;
    protected IdentityHashtable unregisteredExistingObjects;

    protected IdentityHashtable newAggregates;

    /** This method is used to store the current changeSet for this UnitOfWork. */
    protected UnitOfWorkChangeSet unitOfWorkChangeSet;

    /** use to track pessimistic locked objects */
    protected IdentityHashtable pessimisticLockedObjects;

    /** Used to store the list of locks that this UnitOfWork has acquired for this merge */
    protected MergeManager lastUsedMergeManager;

    /** Read-only class can be used for reference data to avoid cloning when not required. */
    protected Hashtable readOnlyClasses;

    /** Flag indicating that the transaction for this UOW was already begun. */
    protected boolean wasTransactionBegunPrematurely;

    /** Allow for double merges of new objects by putting them into the cache. */
    protected boolean shouldNewObjectsBeCached;

    /** Flag indicating that deletes should be performed before other updates. */
    protected boolean shouldPerformDeletesFirst;

    /** Flag indicating how to deal with exceptions on conforming queries. **/
    protected int shouldThrowConformExceptions;

    /** The amount of validation can be configured. */
    protected int validationLevel;
    static public final int None = 0;
    static public final int Partial = 1;
    static public final int Full = 2;

    /**
     * With the new synchronized unit of work, need a lifecycle state variable to
     * track birth, commited, pending_merge and death.
     */
    protected boolean isSynchronized;
    protected int lifecycle;
    public static final int Birth = 0;
    public static final int CommitPending = 1;

    // After a call to writeChanges() but before commit.
    public static final int CommitTransactionPending = 2;

    // After an unsuccessful call to writeChanges(). No recovery at all.
    public static final int WriteChangesFailed = 3;
    public static final int MergePending = 4;
    public static final int Death = 5;
    public static final int AfterExternalTransactionRolledBack = 6;

    /** Used for Conforming Queries */
    public static final int DO_NOT_THROW_CONFORM_EXCEPTIONS = 0;
    public static final int THROW_ALL_CONFORM_EXCEPTIONS = 1;
    
    public static final String LOCK_QUERIES_PROPERTY = "LockQueriesProperties";

    /** Used for merging dependent values without use of WL SessionAccessor */
    protected static boolean SmartMerge = false;

    /** Kept reference of read lock objects*/
    protected Hashtable optimisticReadLockObjects;

    /** lazy initialization done in storeModifyAllQuery. For UpdateAllQuery, only clones of all UpdateAllQuery's (deferred and non-deferred) are stored here for validation only.*/
    protected List modifyAllQueries;

    /** Contains deferred ModifyAllQuery's that have translation row for execution only. At commit their clones will be added to modifyAllQueries for validation afterwards*/
     //Bug4607551
    protected List deferredModifyAllQueries;

    /**
     * Used during the cloning process to track the recursive depth in. This will
     * be used to determine at which point the process can begin to wait on locks
     * without being concerned about creating deadlock situations.
     */
    protected int cloneDepth = 0;

    /**
     * This collection will be used to store those objects that are currently locked
     * for the clone process. It should be populated with an TopLinkIdentityHashMap
     */
    protected Map objectsLockedForClone;
        
    /**
     * PERF: Stores the JTA transaction to optimize activeUnitOfWork lookup.
     */
    protected Object transaction;
    




    
    /**
     * PERF: Cache the write-lock check to avoid cost of checking in every register/clone.
     */
    protected boolean shouldCheckWriteLock;

    /**
     * True if UnitOfWork should be resumed on completion of transaction.
     * Used when UnitOfWork is Synchronized with external transaction control
     */
    protected boolean resumeOnTransactionCompletion;
    
    /**
     * True if either DataModifyQuery or ModifyAllQuery was executed.
     * Gets reset on commit, effects DoesExistQuery behaviour and reading.
     */
    protected boolean wasNonObjectLevelModifyQueryExecuted;

    /**
     * True if the value holder for the joined attribute should be triggered.
     * Required by ejb30 fetch join.
     */
    protected boolean shouldCascadeCloneToJoinedRelationship;

    /**
     * INTERNAL:
     * Create and return a new unit of work with the sesson as its parent.
     */
    public UnitOfWorkImpl(AbstractSession parent) {
        super();
        this.referenceMode = getReferenceMode(parent);
        this.name = parent.getName();
        this.parent = parent;
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        this.cloneMapping = this.getIdentityHashTable();
        // PERF: lazy-init hashtables (3286089) - cloneToOriginals,
        // newObjectsInParentOriginalToClone, objectsDeletedDuringCommit
        // removedObjects.
        this.project = parent.getProject();
        this.profiler = parent.getProfiler();
        this.isInProfile = parent.isInProfile;
        this.sessionLog = parent.getSessionLog();
        this.eventManager = parent.getEventManager().clone(this);
        this.exceptionHandler = parent.getExceptionHandler();

        // Initialize the readOnlyClasses variable.
        this.setReadOnlyClasses(parent.copyReadOnlyClasses());
        this.wasTransactionBegunPrematurely = false;
        // False by default as this may screw up things for objects with 0, -1 or other non-null default keys.
        this.shouldNewObjectsBeCached = false;
        this.validationLevel = Partial;

        this.shouldPerformDeletesFirst = false;

        // for 3.0.x this conforming queries will not throw exceptions unless explicitly asked to
        this.shouldThrowConformExceptions = DO_NOT_THROW_CONFORM_EXCEPTIONS;

        // initialize lifecycle state variable
        this.isSynchronized = false;
        this.lifecycle = Birth;
        // PERF: Cache the write-lock check to avoid cost of checking in every register/clone.
        this.shouldCheckWriteLock = parent.getDatasourceLogin().shouldSynchronizedReadOnWrite() || parent.getDatasourceLogin().shouldSynchronizeWrites();
        this.resumeOnTransactionCompletion = false;

        getEventManager().postAcquireUnitOfWork();
        incrementProfile(SessionProfiler.UowCreated);
    }

    /**
     * PUBLIC:
     * Nested units of work are not supported in TopLink Essentials.
     */
    public UnitOfWork acquireUnitOfWork() {
        throw ValidationException.notSupported("acquireUnitOfWork", getClass());
    }

    /**
     * INTERNAL:
     * Register a new aggregate object with the unit of work.
     */
    public void addNewAggregate(Object originalObject) {
        getNewAggregates().put(originalObject, originalObject);
    }

    /**
     * INTERNAL:
     * Add object deleted during root commit of unit of work.
     */
    public void addObjectDeletedDuringCommit(Object object, ClassDescriptor descriptor) {
        // The object's key is keyed on the object, this avoids having to compute the key later on.
        getObjectsDeletedDuringCommit().put(object, keyFromObject(object, descriptor));
        //bug 4730595: changed to add deleted objects to the changesets.
        ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).addDeletedObject(object, this);
    }

    /**
     * PUBLIC:
     * Adds the given Java class to the receiver's set of read-only classes.
     * Cannot be called after objects have been registered in the unit of work.
     */
    public void addReadOnlyClass(Class theClass) throws ValidationException {
        if (!canChangeReadOnlySet()) {
            throw ValidationException.cannotModifyReadOnlyClassesSetAfterUsingUnitOfWork();
        }

        getReadOnlyClasses().put(theClass, theClass);

        ClassDescriptor descriptor = getDescriptor(theClass);

        // Also mark all subclasses as read-only.
        if (descriptor.hasInheritance()) {
            for (Enumeration childEnum = descriptor.getInheritancePolicy().getChildDescriptors().elements();
                     childEnum.hasMoreElements();) {
                ClassDescriptor childDescriptor = (ClassDescriptor)childEnum.nextElement();
                addReadOnlyClass(childDescriptor.getJavaClass());
            }
        }
    }

    /**
     * PUBLIC:
     * Adds the classes in the given Vector to the existing set of read-only classes.
     * Cannot be called after objects have been registered in the unit of work.
     */
    public void addReadOnlyClasses(Vector classes) {
        for (Enumeration enumtr = classes.elements(); enumtr.hasMoreElements();) {
            Class theClass = (Class)enumtr.nextElement();
            addReadOnlyClass(theClass);
        }
    }

    /**
     * INTERNAL:
     * Register that an object was removed in a nested unit of work.
     */
    public void addRemovedObject(Object orignal) {
        getRemovedObjects().put(orignal, orignal);// Use as set.
    }

    /**
     * ADVANCED:
     * Assign sequence number to the object.
     * This allows for an object's id to be assigned before commit.
     * It can be used if the application requires to use the object id before the object exists on the database.
     * Normally all ids are assigned during the commit automatically.
     */
    public void assignSequenceNumber(Object object) throws DatabaseException {
        //** sequencing refactoring
        startOperationProfile(SessionProfiler.AssignSequence);
        try {
            ObjectBuilder builder = getDescriptor(object).getObjectBuilder();

            // This is done outside of a transaction to ensure optimial concurrency and deadlock avoidance in the sequence table.
            if (builder.getDescriptor().usesSequenceNumbers() && !getSequencing().shouldAcquireValueAfterInsert(object.getClass())) {
                Object implementation = builder.unwrapObject(object, this);
                builder.assignSequenceNumber(implementation, this);
            }
        } catch (RuntimeException exception) {
            handleException(exception);
        }
        endOperationProfile(SessionProfiler.AssignSequence);
    }

    /**
     * ADVANCED:
     * Assign sequence numbers to all new objects registered in this unit of work,
     * or any new objects reference by any objects registered.
     * This allows for an object's id to be assigned before commit.
     * It can be used if the application requires to use the object id before the object exists on the database.
     * Normally all ids are assigned during the commit automatically.
     */
    public void assignSequenceNumbers() throws DatabaseException {
        // This should be done outside of a transaction to ensure optimal concurrency and deadlock avoidance in the sequence table.
        // discoverAllUnregisteredNewObjects() should be called no matter whether sequencing used
        // or not, because collectAndPrepareObjectsForCommit() method (which calls assignSequenceNumbers())
        // needs it.
        // It would be logical to remove discoverAllUnregisteredNewObjects() from assignSequenceNumbers()
        // and make collectAndPrepareObjectsForCommit() to call discoverAllUnregisteredNewObjects()
        // first and assignSequenceNumbers() next,
        // but assignSequenceNumbers() is a public method which could be called by user - and
        // in this case discoverAllUnregisteredNewObjects() is needed again (though
        // if sequencing is not used the call will make no sense - but no harm, too).
        discoverAllUnregisteredNewObjects();
        Sequencing sequencing = getSequencing();
        if (sequencing == null) {
            return;
        }
        int whenShouldAcquireValueForAll = sequencing.whenShouldAcquireValueForAll();
        if (whenShouldAcquireValueForAll == Sequencing.AFTER_INSERT) {
            return;
        }
        boolean shouldAcquireValueBeforeInsertForAll = whenShouldAcquireValueForAll == Sequencing.BEFORE_INSERT;
        startOperationProfile(SessionProfiler.AssignSequence);
        Enumeration unregisteredNewObjectsEnum = getUnregisteredNewObjects().keys();
        while (unregisteredNewObjectsEnum.hasMoreElements()) {
            Object object = unregisteredNewObjectsEnum.nextElement();
            if (getDescriptor(object).usesSequenceNumbers() && ((!isObjectRegistered(object)) || isCloneNewObject(object)) && (shouldAcquireValueBeforeInsertForAll || !sequencing.shouldAcquireValueAfterInsert(object.getClass()))) {
                getDescriptor(object).getObjectBuilder().assignSequenceNumber(object, this);
            }
        }
        Enumeration registeredNewObjectsEnum = getNewObjectsCloneToOriginal().keys();
        while (registeredNewObjectsEnum.hasMoreElements()) {
            Object object = registeredNewObjectsEnum.nextElement();
            if (getDescriptor(object).usesSequenceNumbers() && ((!isObjectRegistered(object)) || isCloneNewObject(object)) && (shouldAcquireValueBeforeInsertForAll || !sequencing.shouldAcquireValueAfterInsert(object.getClass()))) {
                getDescriptor(object).getObjectBuilder().assignSequenceNumber(object, this);
            }
        }

        endOperationProfile(SessionProfiler.AssignSequence);
    }

    /**
     * PUBLIC:
     * Tell the unit of work to begin a transaction now.
     * By default the unit of work will begin a transaction at commit time.
     * The default is the recommended approach, however sometimes it is
     * neccessary to start the transaction before commit time. When the
     * unit of work commits, this transcation will be commited.
     *
     * @see #commit()
     * @see #release()
     */
    public void beginEarlyTransaction() throws DatabaseException {
        beginTransaction();
        setWasTransactionBegunPrematurely(true);
    }

    /**
     * INTERNAL:
     * This is internal to the uow, transactions should not be used explictly in a uow.
     * The uow shares its parents transactions.
     */
    public void beginTransaction() throws DatabaseException {
        getParent().beginTransaction();
    }

    /**
     * INTERNAL:
     * Unregistered new objects have no original so we must create one for commit and resume and
     * to put into the parent. We can NEVER let the same copy of an object exist in multiple units of work.
     */
    public Object buildOriginal(Object workingClone) {
        ClassDescriptor descriptor = getDescriptor(workingClone);
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object original = builder.instantiateClone(workingClone, this);

        // If no original exists can mean any of the following:
        // -A RemoteUnitOfWork and cloneToOriginals is transient.
        // -A clone read while in transaction, and built directly from
        // the database row with no intermediary original.
        // -An unregistered new object
        if (checkIfAlreadyRegistered(workingClone, descriptor) != null) {
            getCloneToOriginals().put(workingClone, original);
            return original;
        } else {
            // Assume it is an unregisteredNewObject, but this is worrisome, as
            // it may be an unregistered existing object, not in the parent cache?
            Object backup = builder.instantiateClone(workingClone, this);
            
            // Original is fine for backup as state is the same.
            getCloneMapping().put(workingClone, backup);

            // Must register new instance / clone as the original.
            getNewObjectsCloneToOriginal().put(workingClone, original);
            getNewObjectsOriginalToClone().put(original, workingClone);

            // no need to register in identity map as the DatabaseQueryMechanism will have
            //placed the object in the identity map on insert. bug 3431586
        }
        return original;
    }

    /**
     * INTERNAL:
     *<p> This Method is designed to calculate the changes for all objects
     * within the PendingObjects.
     */
    public UnitOfWorkChangeSet calculateChanges(IdentityHashtable allObjects, UnitOfWorkChangeSet changeSet) {
        getEventManager().preCalculateUnitOfWorkChangeSet();

        Enumeration objects = allObjects.elements();
        while (objects.hasMoreElements()) {
            Object object = objects.nextElement();

            //block of code removed because it will never be touched see bug # 2903565
            ClassDescriptor descriptor = getDescriptor(object);

            //Block of code removed for code coverage, as it would never have been touched. bug # 2903600
            // Use the object change policy to determine if we should run a comparison for this object - TGW
            if (descriptor.getObjectChangePolicy().shouldCompareForChange(object, this, descriptor)) {
                ObjectChangeSet changes = descriptor.getObjectChangePolicy().calculateChanges(object, getBackupClone(object), changeSet, this, descriptor, true);
                if ((changes != null) && changes.isNew()) {
                    // add it to the new list as well so we do not loose it as it may not have a valid primary key
                    // it will be moved to the standard list once it is inserted.
                    changeSet.addNewObjectChangeSet(changes, this);
                } else {
                    changeSet.addObjectChangeSet(changes);
                }
            }
        }
        
        getEventManager().postCalculateUnitOfWorkChangeSet(changeSet);
        return changeSet;
    }

    /**
     * INTERNAL:
     * Checks whether the receiver has been used. i.e. objects have been registered.
     *
     * @return true or false depending on whether the read-only set can be changed or not.
     */
    protected boolean canChangeReadOnlySet() {
        return !hasCloneMapping() && !hasDeletedObjects();
    }

    /**
     * INTERNAL:
     */
    public boolean checkForUnregisteredExistingObject(Object object) {
        ClassDescriptor descriptor = getDescriptor(object.getClass());
        Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, this);

        DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery();

        existQuery = (DoesExistQuery)existQuery.clone();
        existQuery.setObject(object);
        existQuery.setPrimaryKey(primaryKey);
        existQuery.setDescriptor(descriptor);
        existQuery.setCheckCacheFirst(true);

        if (((Boolean)executeQuery(existQuery)).booleanValue()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * INTERNAL:
     * Register the object and return the clone if it is existing otherwise return null if it is new.
     * The unit of work determines existence during registration, not during the commit.
     */
    public Object checkExistence(Object object) {
        ClassDescriptor descriptor = getDescriptor(object.getClass());
        Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(object, this);
        // PERF: null primary key cannot exist.
        if (primaryKey.contains(null)) {
            return null;
        }
        DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery();

        existQuery = (DoesExistQuery)existQuery.clone();
        existQuery.setObject(object);
        existQuery.setPrimaryKey(primaryKey);
        existQuery.setDescriptor(descriptor);
        existQuery.setCheckCacheFirst(true);

        if (((Boolean)executeQuery(existQuery)).booleanValue()) {
            //we know if it exists or not, now find or register it
            Object objectFromCache = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, object.getClass(), descriptor);
    
            if (objectFromCache != null) {
                // Ensure that the registered object is the one from the parent cache.
                if (shouldPerformFullValidation()) {
                    if ((objectFromCache != object) && (getParent().getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, object.getClass(), descriptor) != object)) {
                        throw ValidationException.wrongObjectRegistered(object, objectFromCache);
                    }
                }
    
                // Has already been cloned.
                if (!this.isObjectDeleted(objectFromCache))
                    return objectFromCache;
            }
            // This is a case where the object is not in the session cache,
            // so a new cache-key is used as there is no original to use for locking.
            return cloneAndRegisterObject(object, new CacheKey(primaryKey));
        } else {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Return the value of the object if it already is registered, otherwise null.
     */
    protected Object checkIfAlreadyRegistered(Object object, ClassDescriptor descriptor) {
        // Don't register read-only classes
        if (isClassReadOnly(object.getClass(), descriptor)) {
            return null;
        }

        // Check if the working copy is again being registered in which case we return the same working copy
        Object registeredObject = getCloneMapping().get(object);
        if (registeredObject != null) {
            return object;
        }

        // Check if object exists in my new objects if it is in the new objects cache then it means domain object is being
        // re-registered and we should return the same working clone. This check holds only for the new registered objects
        // PERF: Avoid initialization of new objects if none.
        if (hasNewObjects()) {
            registeredObject = getNewObjectsOriginalToClone().get(object);
            if (registeredObject != null) {
                return registeredObject;
            }
        }

        return null;
    }

    /**
     * ADVANCED:
     * Register the new object with the unit of work.
     * This will register the new object with cloning.
     * Normally the registerObject method should be used for all registration of new and existing objects.
     * This version of the register method can only be used for new objects.
     * This method should only be used if a new object is desired to be registered without an existence Check.
     *
     * @see #registerObject(Object)
     */
    public Object cloneAndRegisterNewObject(Object original) {
        ClassDescriptor descriptor = getDescriptor(original);
        ObjectBuilder builder = descriptor.getObjectBuilder();

        // bug 2612602 create the working copy object.
        Object clone = builder.instantiateWorkingCopyClone(original, this);

        // Must put in the original to clone to resolv circular refs.
        getNewObjectsOriginalToClone().put(original, clone);
        // Must put in clone mapping.
        getCloneMapping().put(clone, clone);

        builder.populateAttributesForClone(original, clone, this);

        // Must reregister in both new objects.
        registerNewObjectClone(clone, original, descriptor);

        //Build backup clone for DeferredChangeDetectionPolicy or ObjectChangeTrackingPolicy,
        //but not for AttributeChangeTrackingPolicy
        Object backupClone = descriptor.getObjectChangePolicy().buildBackupClone(clone, builder, this);
        getCloneMapping().put(clone, backupClone);// The backup clone must be updated.

        return clone;
    }

    /**
     * INTERNAL:
     * Clone and register the object.
     * The cache key must the cache key from the session cache,
     * as it will be used for locking.
     */
    public Object cloneAndRegisterObject(Object original, CacheKey cacheKey) {
        ClassDescriptor descriptor = getDescriptor(original);

        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object workingClone = builder.instantiateWorkingCopyClone(original, this);

        // The cache/objects being registered must first be locked to ensure
        // that a merge or refresh does not oocur on the object while being cloned to
        // avoid cloning a partially merged/refreshed object.
        // If a cache isolation level is used, then lock the entire cache.
        // otherwise lock the object and it related objects (not using indirection) as a unit.
        // If just a simple object (all indirection) a simple read-lock can be used.
        // PERF: Cache if check to write is required.
        boolean identityMapLocked = this.shouldCheckWriteLock && getParent().getIdentityMapAccessorInstance().acquireWriteLock();
        boolean rootOfCloneRecursion = false;
        if ((!identityMapLocked) && (this.objectsLockedForClone == null)) {//we may have locked all required objects already
            // PERF: If a simple object just acquire a simple read-lock.
            if (descriptor.shouldAcquireCascadedLocks()) {
                this.objectsLockedForClone = getParent().getIdentityMapAccessorInstance().getWriteLockManager().acquireLocksForClone(original, descriptor, cacheKey.getKey(), getParent());
            } else {
                cacheKey.acquireReadLock();
            }
            rootOfCloneRecursion = true;
        }
        try {
            // This must be registered before it is built to avoid really obscure cycles.
            getCloneMapping().put(workingClone, workingClone);

            //also clone the fetch group reference if applied
            if (descriptor.hasFetchGroupManager()) {
                descriptor.getFetchGroupManager().copyFetchGroupInto(original, workingClone);
            }

            //store this for look up later
            getCloneToOriginals().put(workingClone, original);
            // just clone it.
            populateAndRegisterObject(original, workingClone, cacheKey.getKey(), descriptor, cacheKey.getWriteLockValue(), cacheKey.getReadTime());

        } finally {
            // If the entire cache was locke, release the cache lock,
            // otherwise either release the cache-key for a simple lock,
            // otherwise release the entire set of locks for related objects if this was the root.
            if (identityMapLocked) {
                getParent().getIdentityMapAccessorInstance().releaseWriteLock();
            } else {
                if (rootOfCloneRecursion) {
                    if (this.objectsLockedForClone == null) {
                        cacheKey.releaseReadLock();
                    } else {
                        for (Iterator iterator = this.objectsLockedForClone.values().iterator();
                                 iterator.hasNext();) {
                            ((CacheKey)iterator.next()).releaseReadLock();
                        }
                        this.objectsLockedForClone = null;
                    }
                }
            }
        }
        return workingClone;
    }

    /**
     * INTERNAL:
     * Prepare for commit.
     */
    public HardIdentityHashtable collectAndPrepareObjectsForCommit() {
        HardIdentityHashtable changedObjects = new HardIdentityHashtable(1 + getCloneMapping().size());

        // SPECJ: Avoid for CMP.
        if (! getProject().isPureCMP2Project()) {
            assignSequenceNumbers();
        }

        //assignSequenceNumbers will collect the unregistered new objects and assign id's to all new
        // objects
        // Add any registered objects.
        for (Enumeration clonesEnum = getCloneMapping().keys(); clonesEnum.hasMoreElements();) {
            Object clone = clonesEnum.nextElement();
            changedObjects.put(clone, clone);
        }
        for (Enumeration unregisteredNewObjectsEnum = getUnregisteredNewObjects().keys();
                 unregisteredNewObjectsEnum.hasMoreElements();) {
            Object newObject = unregisteredNewObjectsEnum.nextElement();
            changedObjects.put(newObject, newObject);
        }

        return changedObjects;
    }

    /**
     * INTERNAL:
     * Prepare for merge in nested uow.
     */
    public HardIdentityHashtable collectAndPrepareObjectsForNestedMerge() {
        HardIdentityHashtable changedObjects = new HardIdentityHashtable(1 + getCloneMapping().size());

        discoverAllUnregisteredNewObjects();

        //assignSequenceNumbers will collect the unregistered new objects and assign id's to all new
        // objects
        // Add any registered objects.
        for (Enumeration clonesEnum = getCloneMapping().keys(); clonesEnum.hasMoreElements();) {
            Object clone = clonesEnum.nextElement();
            changedObjects.put(clone, clone);
        }
        for (Enumeration unregisteredNewObjectsEnum = getUnregisteredNewObjects().keys();
                 unregisteredNewObjectsEnum.hasMoreElements();) {
            Object newObject = unregisteredNewObjectsEnum.nextElement();
            changedObjects.put(newObject, newObject);
        }

        return changedObjects;
    }

    /**
     * PUBLIC:
     * Commit the unit of work to its parent.
     * For a nested unit of work this will merge any changes to its objects
     * with its parents.
     * For a first level unit of work it will commit all changes to its objects
     * to the database as a single transaction. If successful the changes to its
     * objects will be merged to its parent's objects. If the commit fails the database
     * transaction will be rolledback, and the unit of work will be released.
     * If the commit is successful the unit of work is released, and a new unit of work
     * must be acquired if further changes are desired.
     *
     * @see #commitAndResumeOnFailure()
     * @see #commitAndResume()
     * @see #release()
     */
    public void commit() throws DatabaseException, OptimisticLockException {
        //CR#2189 throwing exception if UOW try to commit again(XC)
        if (!isActive()) {
            throw ValidationException.cannotCommitUOWAgain();
        }
        if (isAfterWriteChangesFailed()) {
            throw ValidationException.unitOfWorkAfterWriteChangesFailed("commit");
        }

        if (!isNestedUnitOfWork()) {
            if (isSynchronized()) {
                // If we started the JTS transaction then we have to commit it as well.
                if (getParent().wasJTSTransactionInternallyStarted()) {
                    commitInternallyStartedExternalTransaction();
                }

                // Do not commit until the JTS wants to.
                return;
            }
        }
        if (getLifecycle() == CommitTransactionPending) {
            commitAfterWriteChanges();
            return;
        }
        log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");// bjv - correct spelling
        getEventManager().preCommitUnitOfWork();
        setLifecycle(CommitPending);
        commitRootUnitOfWork();
        getEventManager().postCommitUnitOfWork();
        log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit");
        release();
    }

    /**
     * PUBLIC:
     * Commit the unit of work to its parent.
     * For a nested unit of work this will merge any changes to its objects
     * with its parents.
     * For a first level unit of work it will commit all changes to its objects
     * to the database as a single transaction. If successful the changes to its
     * objects will be merged to its parent's objects. If the commit fails the database
     * transaction will be rolledback, and the unit of work will be released.
     * The normal commit releases the unit of work, forcing a new one to be acquired if further changes are desired.
     * The resuming feature allows for the same unit of work (and working copies) to be continued to be used.
     *
     * @see #commitAndResumeOnFailure()
     * @see #commit()
     * @see #release()
     */
    public void commitAndResume() throws DatabaseException, OptimisticLockException {
        //CR#2189 throwing exception if UOW try to commit again(XC)
        if (!isActive()) {
            throw ValidationException.cannotCommitUOWAgain();
        }

        if (isAfterWriteChangesFailed()) {
            throw ValidationException.unitOfWorkAfterWriteChangesFailed("commit");
        }

        if (!isNestedUnitOfWork()) {
            if (isSynchronized()) {
                // JTA synchronized units of work, cannot be resumed as there is no
                // JTA transaction to register with after the commit,
                // technically this could be supported if the uow started the transaction,
                // but currently the after completion releases the uow and client session so not really possible.
                throw ValidationException.cannotCommitAndResumeSynchronizedUOW(this);
            }
        }
        if (getLifecycle() == CommitTransactionPending) {
            commitAndResumeAfterWriteChanges();
            return;
        }
        log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");// bjv - correct spelling
        getEventManager().preCommitUnitOfWork();
        setLifecycle(CommitPending);
        commitRootUnitOfWork();
        getEventManager().postCommitUnitOfWork();
        log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit");
        
        log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work");
        synchronizeAndResume();
        getEventManager().postResumeUnitOfWork();
    }

    /**
     * INTERNAL:
     * This method is used by the MappingWorkbench for their read-only file feature
     * this method must not be exposed to or used by customers until it has been revised
     * and the feature revisited to support OptimisticLocking and Serialization
     */
    public void commitAndResumeWithPreBuiltChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException {
        if (!isNestedUnitOfWork()) {
            if (isSynchronized()) {
                // If we started the JTS transaction then we have to commit it as well.
                if (getParent().wasJTSTransactionInternallyStarted()) {
                    commitInternallyStartedExternalTransaction();
                }

                // Do not commit until the JTS wants to.
                return;
            }
        }
        log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");// bjv - correct spelling
        getEventManager().preCommitUnitOfWork();
        setLifecycle(CommitPending);
        commitRootUnitOfWorkWithPreBuiltChangeSet(uowChangeSet);
        getEventManager().postCommitUnitOfWork();
        log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit");
        log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work");

        synchronizeAndResume();
        getEventManager().postResumeUnitOfWork();
    }

    /**
     * PUBLIC:
     * Commit the unit of work to its parent.
     * For a nested unit of work this will merge any changes to its objects
     * with its parents.
     * For a first level unit of work it will commit all changes to its objects
     * to the database as a single transaction. If successful the changes to its
     * objects will be merged to its parent's objects. If the commit fails the database
     * transaction will be rolledback, but the unit of work will remain active.
     * It can then be retried or released.
     * The normal commit failure releases the unit of work, forcing a new one to be acquired if further changes are desired.
     * The resuming feature allows for the same unit of work (and working copies) to be continued to be used if an error occurs.
     *
     * @see #commit()
     * @see #release()
     */
    public void commitAndResumeOnFailure() throws DatabaseException, OptimisticLockException {
        // First clone the identity map, on failure replace the clone back as the cache.
        IdentityMapManager failureManager = (IdentityMapManager)getIdentityMapAccessorInstance().getIdentityMapManager().clone();
        try {
            // Call commitAndResume.
            // Oct 13, 2000 - JED PRS #13551
            // This method will always resume now. Calling commitAndResume will sync the cache
            // if successful. This method will take care of resuming if a failure occurs
            commitAndResume();
        } catch (RuntimeException exception) {
            //reset unitOfWorkChangeSet. Needed for ObjectChangeTrackingPolicy and DeferredChangeDetectionPolicy
            setUnitOfWorkChangeSet(null);
            getIdentityMapAccessorInstance().setIdentityMapManager(failureManager);
            log(SessionLog.FINER, SessionLog.TRANSACTION, "resuming_unit_of_work_from_failure");
            throw exception;
        }
    }

    /**
     * INTERNAL:
     * Commits a UnitOfWork where the commit process has already been
     * initiated by all call to writeChanges().
     * <p>
     * a.k.a finalizeCommit()
     */
    protected void commitAfterWriteChanges() {
        commitTransactionAfterWriteChanges();
        mergeClonesAfterCompletion();
        setDead();
        release();
    }

    /**
     * INTERNAL:
     * Commits and resumes a UnitOfWork where the commit process has already been
     * initiated by all call to writeChanges().
     * <p>
     * a.k.a finalizeCommit()
     */
    protected void commitAndResumeAfterWriteChanges() {
        commitTransactionAfterWriteChanges();
        mergeClonesAfterCompletion();
        log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work");
        synchronizeAndResume();
        getEventManager().postResumeUnitOfWork();
    }

    /**
     * PROTECTED:
     * Used in commit and commit-like methods to commit
     * internally started external transaction
     */
    protected boolean commitInternallyStartedExternalTransaction() {
        boolean committed = false;
        if (!getParent().isInTransaction() || (wasTransactionBegunPrematurely() && (getParent().getTransactionMutex().getDepth() == 1))) {
            committed = getParent().commitExternalTransaction();
        }
        return committed;
    }

    /**
     * INTERNAL:
     * Commit the changes to any objects to the parent.
     */
    public void commitRootUnitOfWork() throws DatabaseException, OptimisticLockException {
        commitToDatabaseWithChangeSet(true);

        // Merge after commit
        mergeChangesIntoParent();
    }

    /**
     * INTERNAL:
     * This method is used by the MappingWorkbench read-only files feature
     * It will commit a pre-built unitofwork change set to the database
     */
    public void commitRootUnitOfWorkWithPreBuiltChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException {
        //new code no need to check old commit
        commitToDatabaseWithPreBuiltChangeSet(uowChangeSet, true);

        // Merge after commit
        mergeChangesIntoParent();
    }

    /**
     * INTERNAL:
     * CommitChanges To The Database from a calculated changeSet
     * @param commitTransaction false if called by writeChanges as intent is
     * not to finalize the transaction.
     */
    protected void commitToDatabase(boolean commitTransaction) {
        try {
            //CR4202 - ported from 3.6.4
            if (wasTransactionBegunPrematurely()) {
                // beginTransaction() has been already called
                setWasTransactionBegunPrematurely(false);
            } else {
                beginTransaction();
            }

            if(commitTransaction) {
                setWasNonObjectLevelModifyQueryExecuted(false);
            }
            
            Vector deletedObjects = null;// PERF: Avoid deletion if nothing to delete.
            if (hasDeletedObjects()) {
                deletedObjects = new Vector(getDeletedObjects().size());
                for (Enumeration objects = getDeletedObjects().keys(); objects.hasMoreElements();) {
                    deletedObjects.addElement(objects.nextElement());
                }
            }

            if (shouldPerformDeletesFirst) {
                if (hasDeletedObjects()) {
                    // This must go to the commit manager because uow overrides to do normal deletion.
                    getCommitManager().deleteAllObjects(deletedObjects);

                    // Clear change sets of the deleted object to avoid redundant updates.
                    for (Enumeration objects = getObjectsDeletedDuringCommit().keys();
                             objects.hasMoreElements();) {
                        oracle.toplink.essentials.internal.sessions.ObjectChangeSet objectChangeSet = (oracle.toplink.essentials.internal.sessions.ObjectChangeSet)this.unitOfWorkChangeSet.getObjectChangeSetForClone(objects.nextElement());
                        if (objectChangeSet != null) {
                            objectChangeSet.clear();
                        }
                    }
                }

                // Let the commit manager figure out how to write the objects
                super.writeAllObjectsWithChangeSet(this.unitOfWorkChangeSet);
                // Issue all the SQL for the ModifyAllQuery's, don't touch the cache though
                issueModifyAllQueryList();
            } else {
                // Let the commit manager figure out how to write the objects
                super.writeAllObjectsWithChangeSet(this.unitOfWorkChangeSet);
                if (hasDeletedObjects()) {
                    // This must go to the commit manager because uow overrides to do normal deletion.
                    getCommitManager().deleteAllObjects(deletedObjects);
                }

                // Issue all the SQL for the ModifyAllQuery's, don't touch the cache though
                issueModifyAllQueryList();
            }

            // Issue prepare event.
            getEventManager().prepareUnitOfWork();

            // writeChanges() does everything but this step.
            // do not lock objects unless we are at the commit s
            if (commitTransaction) {
                try{
                    // if we should be acquiring locks before commit let's do that here
                    if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase()){
                        setMergeManager(new MergeManager(this));
                        //If we are merging into the shared cache acquire all required locks before merging.
                        getParent().getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(getMergeManager(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet());
                    }
                    commitTransaction();
                }catch (RuntimeException throwable){
                    if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase() && (getMergeManager() != null)) {
                        // exception occurred durring the commit.
                        getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager());
                        this.setMergeManager(null);
                    }
                    throw throwable;
                }catch (Error throwable){
                    if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase() && (getMergeManager() != null)) {
                        // exception occurred durring the commit.
                        getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager());
                        this.setMergeManager(null);
                    }
                    throw throwable;
                }
            }else{
                setWasTransactionBegunPrematurely(true);
            }

        } catch (RuntimeException exception) {
            rollbackTransaction(commitTransaction);
            if (hasExceptionHandler()) {
                getExceptionHandler().handleException(exception);
            } else {
                throw exception;
            }
        }
    }

    /**
     * INTERNAL:
     * Commit the changes to any objects to the parent.
     * @param commitTransaction false if called by writeChanges as intent is
     * not to finalize the transaction.
     */
    protected void commitToDatabaseWithChangeSet(boolean commitTransaction) throws DatabaseException, OptimisticLockException {
        try {
            startOperationProfile(SessionProfiler.UowCommit);
            // The sequence numbers are assigned outside of the commit transaction.
            // This improves concurrency, avoids deadlock and in the case of three-tier will
            // not leave invalid cached sequences on rollback.
            // Also must first set the commit manager active.
            getCommitManager().setIsActive(true);
            // This will assgin sequence numbers.
            HardIdentityHashtable allObjects = collectAndPrepareObjectsForCommit();

            // Must clone because the commitManager will remove the objects from the collection
            // as the objects are written to the database.
            setAllClonesCollection((HardIdentityHashtable)allObjects.clone());
            // Iterate over each clone and let the object build merge to clones into the originals.
            // The change set may already exist if using change tracking.
            if (getUnitOfWorkChangeSet() == null) {
                setUnitOfWorkChangeSet(new UnitOfWorkChangeSet());
            }
            calculateChanges(getAllClones(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet());

            // Bug 2834266 only commit to the database if changes were made, avoid begin/commit of transaction
            if (hasModifications()) {
                commitToDatabase(commitTransaction);
            } else {
                // CR#... need to commit the transaction if begun early.
                if (wasTransactionBegunPrematurely()) {
                    if (commitTransaction) {
                        // Must be set to false for release to know not to rollback.
                        setWasTransactionBegunPrematurely(false);
                        setWasNonObjectLevelModifyQueryExecuted(false);
                        commitTransaction();
                    }
                }
                getCommitManager().setIsActive(false);
            }
            endOperationProfile(SessionProfiler.UowCommit);
        } catch (RuntimeException exception) {
            handleException((RuntimeException)exception);
        }
    }

    /**
     * INTERNAL:
     * Commit pre-built changeSet to the database changest to the database.
     */
    protected void commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkChangeSet uowChangeSet, boolean commitTransaction) throws DatabaseException, OptimisticLockException {
        try {
            // The sequence numbers are assigned outside of the commit transaction.
            // This improves concurrency, avoids deadlock and in the case of three-tier will
            // not leave invalid cached sequences on rollback.
            // Also must first set the commit manager active.
            getCommitManager().setIsActive(true);
            //Set empty collection in allClones for merge.
            setAllClonesCollection(new HardIdentityHashtable());
            // Iterate over each clone and let the object build merge to clones into the originals.
            setUnitOfWorkChangeSet(uowChangeSet);
            commitToDatabase(commitTransaction);

        } catch (RuntimeException exception) {
            handleException((RuntimeException)exception);
        }
    }

    /**
     * INTERNAL:
     * This is internal to the uow, transactions should not be used explictly in a uow.
     * The uow shares its parents transactions.
     */
    public void commitTransaction() throws DatabaseException {
        getParent().commitTransaction();
    }

    /**
     * INTERNAL:
     * After writeChanges() everything has been done except for committing
     * the transaction. This allows that execution path to 'catch up'.
     */
    protected void commitTransactionAfterWriteChanges() {
        setWasNonObjectLevelModifyQueryExecuted(false);
        if (hasModifications() || wasTransactionBegunPrematurely()) {
             try{
                //gf934: ensuring release doesn't cause an extra rollback call if acquireRequiredLocks throws an exception
                setWasTransactionBegunPrematurely(false);
                // if we should be acquiring locks before commit let's do that here
                if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase() && (getUnitOfWorkChangeSet() != null)) {
                    setMergeManager(new MergeManager(this));
                    //If we are merging into the shared cache acquire all required locks before merging.
                    getParent().getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(getMergeManager(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet());
                }
                commitTransaction();
            }catch (RuntimeException exception){
                if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase() && (getMergeManager() != null)) {
                    // exception occurred durring the commit.
                    getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager());
                    this.setMergeManager(null);
                }
                rollbackTransaction();
                release();
                handleException(exception);
            }catch (Error throwable){
                if (getDatasourceLogin().shouldSynchronizeObjectLevelReadWriteDatabase() && (getMergeManager() != null)) {
                    // exception occurred durring the commit.
                    getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager());
                    this.setMergeManager(null);
                }
                throw throwable;
            }
        }
    }

    /**
     * INTERNAL:
     * Copy the read only classes from the unit of work.
     */
    // Added Nov 8, 2000 JED for Patch 2.5.1.8, Ref: Prs 24502
    public Vector copyReadOnlyClasses() {
        return Helper.buildVectorFromHashtableElements(getReadOnlyClasses());
    }

    /**
     * PUBLIC:
     * Merge the attributes of the clone into the unit of work copy.
     * This can be used for objects that are returned from the client through
     * RMI serialization or other serialization mechanisms, because the RMI object will
     * be a clone this will merge its attributes correctly to preserve object identity
     * within the unit of work and record its changes.
     * Everything connected to this object (i.e. the entire object tree where rmiClone
     * is the root) is also merged.
     *
     * @return the registered version for the clone being merged.
     * @see #mergeClone(Object)
     * @see #shallowMergeClone(Object)
     */
    public Object deepMergeClone(Object rmiClone) {
        return mergeClone(rmiClone, MergeManager.CASCADE_ALL_PARTS);
    }

    /**
     * PUBLIC:
     * Revert the object's attributes from the parent.
     * This reverts everything the object references.
     *
     * @return the object reverted.
     * @see #revertObject(Object)
     * @see #shallowRevertObject(Object)
     */
    public Object deepRevertObject(Object clone) {
        return revertObject(clone, MergeManager.CASCADE_ALL_PARTS);
    }

    /**
     * ADVANCED:
     * Unregister the object with the unit of work.
     * This can be used to delete an object that was just created and is not yet persistent.
     * Delete object can also be used, but will result in inserting the object and then deleting it.
     * The method should be used carefully because it will delete all the reachable parts.
     */
    public void deepUnregisterObject(Object clone) {
        unregisterObject(clone, DescriptorIterator.CascadeAllParts);
    }

    /**
     * PUBLIC:
     * Delete all of the objects and all of their privately owned parts in the database.
     * Delete operations are delayed in a unit of work until commit.
     */
    public void deleteAllObjects(Vector domainObjects) {
        // This must be overriden to avoid dispatching to the commit manager.
        for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) {
            deleteObject(objectsEnum.nextElement());
        }
    }

    /**
     * INTERNAL:
     * Search for any objects in the parent that have not been registered.
     * These are required so that the nested unit of work does not add them to the parent
     * clone mapping on commit, causing possible incorrect insertions if they are dereferenced.
     */
    protected void discoverAllUnregisteredNewObjects() {
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        HardIdentityHashtable visitedNodes = new HardIdentityHashtable();
        HardIdentityHashtable newObjects = new HardIdentityHashtable();
        HardIdentityHashtable existingObjects = new HardIdentityHashtable();

        // Iterate over each clone.
        for (Enumeration clonesEnum = getCloneMapping().keys(); clonesEnum.hasMoreElements();) {
            Object clone = clonesEnum.nextElement();
            discoverUnregisteredNewObjects(newObjects, existingObjects, visitedNodes);
        }

        setUnregisteredNewObjects(newObjects);
        setUnregisteredExistingObjects(existingObjects);
    }

    /**
     * INTERNAL:
     * Traverse the object to find references to objects not registered in this unit of work.
     */
    public void discoverUnregisteredNewObjects(HardIdentityHashtable knownNewObjects, HardIdentityHashtable unregisteredExistingObjects, HardIdentityHashtable visitedObjects) {
        // This define an inner class for process the itteration operation, don't be scared, its just an inner class.
        DescriptorIterator iterator = new DescriptorIterator() {
            public void iterate(Object object) {
                // If the object is read-only the do not continue the traversal.
                if (isClassReadOnly(object.getClass(), this.getCurrentDescriptor())) {
                    this.setShouldBreak(true);
                    return;
                }

                /* CR3440: Steven Vo
                 * Include the case that object is original then do nothing
                 */
                if (isSmartMerge() && isOriginalNewObject(object)) {
                    return;
                } else if (!isObjectRegistered(object)) {// Don't need to check for aggregates, as iterator does not iterate on them by default.
                    if ((shouldPerformNoValidation()) && (checkForUnregisteredExistingObject(object))) {
                        // If no validation is performed and the object exists we need
                        // To keep a record of this object to ignore it, also I need to
                        // Stop iterating over it.
                        ((HardIdentityHashtable)getUnregisteredExistingObjects()).put(object, object);
                        this.setShouldBreak(true);
                        return;

                    }

                    // This means it is a unregistered new object
                    ((HardIdentityHashtable)getResult()).put(object, object);
                }
            }
        };

        //set the collection in the UnitofWork to be this list
        setUnregisteredExistingObjects(unregisteredExistingObjects);

        iterator.setVisitedObjects(visitedObjects);
        iterator.setResult(knownNewObjects);
        iterator.setSession(this);
        // When using wrapper policy in EJB the iteration should stop on beans,
        // this is because EJB forces beans to be registered anyway and clone identity can be violated
        // and the violated clones references to session objects should not be traversed.
        iterator.setShouldIterateOverWrappedObjects(false);
        // Iterate over each clone.
        for (Enumeration clonesEnum = getCloneMapping().keys(); clonesEnum.hasMoreElements();) {
            iterator.startIterationOn(clonesEnum.nextElement());
        }
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public void dontPerformValidation() {
        setValidationLevel(None);
    }

    /**
     * INTERNAL:
     * Override From session. Get the accessor based on the query, and execute call,
     * this is here for session broker.
     */
    public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException {
        Accessor accessor;
        if (query.getSessionName() == null) {
            accessor = query.getSession().getAccessor(query.getReferenceClass());
        } else {
            accessor = query.getSession().getAccessor(query.getSessionName());
        }

        query.setAccessor(accessor);
        try {
            return query.getAccessor().executeCall(call, translationRow, this);
        } finally {
            if (call.isFinished()) {
                query.setAccessor(null);
            }
        }
    }

    /**
     * ADVANCED:
     * Set optmistic read lock on the object. This feature is overide by normal optimistic lock.
     * when the object is changed in UnitOfWork. The cloneFromUOW must be the clone of from this
     * UnitOfWork and it must implements version locking or timestamp locking.
     * The SQL would look like the followings.
     *
     * If shouldModifyVersionField is true,
     * "UPDATE EMPLOYEE SET VERSION = 2 WHERE EMP_ID = 9 AND VERSION = 1"
     *
     * If shouldModifyVersionField is false,
     * "UPDATE EMPLOYEE SET VERSION = 1 WHERE EMP_ID = 9 AND VERSION = 1"
     */
    public void forceUpdateToVersionField(Object lockObject, boolean shouldModifyVersionField) {
        ClassDescriptor descriptor = getDescriptor(lockObject);
        if (descriptor == null) {
            throw DescriptorException.missingDescriptor(lockObject.getClass().toString());
        }
        getOptimisticReadLockObjects().put(descriptor.getObjectBuilder().unwrapObject(lockObject, this), new Boolean(shouldModifyVersionField));
    }

    /**
     * INTERNAL:
     * The uow does not store a local accessor but shares its parents.
     */
    public Accessor getAccessor() {
        return getParent().getAccessor();
    }

    /**
     * INTERNAL:
     * The commit manager is used to resolve referncial integrity on commits of multiple objects.
     * The commit manage is lazy init from parent.
     */
    public CommitManager getCommitManager() {
        // PERF: lazy init, not always required for release/commit with no changes.
        if (commitManager == null) {
            commitManager = new CommitManager(this);
            // Initialize the commit manager
            commitManager.setCommitOrder(getParent().getCommitManager().getCommitOrder());
        }
        return commitManager;
    }

    /**
     * INTERNAL:
     * The uow does not store a local accessor but shares its parents.
     */
    public Accessor getAccessor(Class domainClass) {
        return getParent().getAccessor(domainClass);
    }

    /**
     * INTERNAL:
     * The uow does not store a local accessor but shares its parents.
     */
    public Accessor getAccessor(String sessionName) {
        return getParent().getAccessor(sessionName);
    }

    /**
     * PUBLIC:
     * Return the active unit of work for the current active external (JTS) transaction.
     * This should only be used with JTS and will return null if no external transaction exists.
     */
    public oracle.toplink.essentials.sessions.UnitOfWork getActiveUnitOfWork() {

        /* Steven Vo: CR# 2517
           This fixed the problem of returning null when this method is called on a UOW.
           UOW does not copy the parent session's external transaction controller
           when it is acquired but session does */
        return getParent().getActiveUnitOfWork();
    }

    /**
     * INTERNAL:
     * This method is used to get a copy of the collection of all clones in the UnitOfWork
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected IdentityHashtable getAllClones() {
        return this.allClones;
    }

    /**
     * INTERNAL:
     * Return any new objects matching the expression.
     * Used for in-memory querying.
     */
    public Vector getAllFromNewObjects(Expression selectionCriteria, Class theClass, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy) {
        // If new object are in the cache then they will have already been queried.
        if (shouldNewObjectsBeCached()) {
            return new Vector(1);
        }

        // PERF: Avoid initialization of new objects if none.
        if (!hasNewObjects()) {
            return new Vector(1);
        }

        Vector objects = new Vector();
        for (Enumeration newObjectsEnum = getNewObjectsOriginalToClone().elements();
                 newObjectsEnum.hasMoreElements();) {
            Object object = newObjectsEnum.nextElement();
            if (theClass.isInstance(object)) {
                if (selectionCriteria == null) {
                    objects.addElement(object);
                } else if (selectionCriteria.doesConform(object, this, translationRow, valueHolderPolicy)) {
                    objects.addElement(object);
                }
            }
        }
        return objects;
    }

    /**
     * INTERNAL:
     * Return the backup clone for the working clone.
     */
    public Object getBackupClone(Object clone) throws QueryException {
        Object backupClone = getCloneMapping().get(clone);
        if (backupClone != null) {
            return backupClone;
        }

        /* CR3440: Steven Vo
         * Smart merge if neccessary in isObjectRegistered()
         */
        if (isObjectRegistered(clone)) {
            return getCloneMapping().get(clone);

        } else {
            ClassDescriptor descriptor = getDescriptor(clone);
            Vector primaryKey = keyFromObject(clone, descriptor);

            // This happens if clone was from the parent identity map.
            if (getParent().getIdentityMapAccessorInstance().containsObjectInIdentityMap(primaryKey, clone.getClass(), descriptor)) {
                //cr 3796
                if ((getUnregisteredNewObjects().get(clone) != null) && isMergePending()) {
                    //Another thread has read the new object before it has had a chance to
                    //merge this object.
                    // It also means it is an unregistered new object, so create a new backup clone for it.
                    return descriptor.getObjectBuilder().buildNewInstance();
                }
                if (hasObjectsDeletedDuringCommit() && getObjectsDeletedDuringCommit().containsKey(clone)) {
                    throw QueryException.backupCloneIsDeleted(clone);
                }
                throw QueryException.backupCloneIsOriginalFromParent(clone);
            }
            // Also check that the object is not the original to a registered new object
            // (the original should not be referenced if not smart merge, this is an error.
            else if (hasNewObjects() && getNewObjectsOriginalToClone().containsKey(clone)) {

                /* CR3440: Steven Vo
                 * Check case that clone is original
                 */
                if (isSmartMerge()) {
                    backupClone = getCloneMapping().get(getNewObjectsOriginalToClone().get(clone));

                } else {
                    throw QueryException.backupCloneIsOriginalFromSelf(clone);
                }
            } else {
                // This means it is an unregistered new object, so create a new backup clone for it.
                backupClone = descriptor.getObjectBuilder().buildNewInstance();
            }
        }

        return backupClone;
    }

    /**
     * INTERNAL:
     * Return the backup clone for the working clone.
     */
    public Object getBackupCloneForCommit(Object clone) {
        Object backupClone = getBackupClone(clone);

        /* CR3440: Steven Vo
         * Build new instance only if it was not handled by getBackupClone()
         */
        if (isCloneNewObject(clone)) {
            return getDescriptor(clone).getObjectBuilder().buildNewInstance();
        }

        return backupClone;
    }

    /**
     * ADVANCED:
     * This method Will Calculate the chages for the UnitOfWork. Without assigning sequence numbers
     * This is a Computationaly intensive operation and should be avoided unless necessary.
     * A valid changeSet, with sequencenumbers can be collected from the UnitOfWork After the commit
     * is complete by calling unitOfWork.getUnitOfWorkChangeSet()
     */
    public oracle.toplink.essentials.changesets.UnitOfWorkChangeSet getCurrentChanges() {
        HardIdentityHashtable allObjects = null;
        allObjects = collectAndPrepareObjectsForNestedMerge();
        return calculateChanges(allObjects, new UnitOfWorkChangeSet());
    }

    /**
     * INTERNAL:
     * Gets the next link in the chain of sessions followed by a query's check
     * early return, the chain of sessions with identity maps all the way up to
     * the root session.
     * <p>
     * Used for session broker which delegates to registered sessions, or UnitOfWork
     * which checks parent identity map also.
     * @param canReturnSelf true when method calls itself. If the path
     * starting at <code>this</code> is acceptable. Sometimes true if want to
     * move to the first valid session, i.e. executing on ClientSession when really
     * should be on ServerSession.
     * @param terminalOnly return the session we will execute the call on, not
     * the next step towards it.
     * @return this if there is no next link in the chain
     */
    public AbstractSession getParentIdentityMapSession(DatabaseQuery query, boolean canReturnSelf, boolean terminalOnly) {
        if (canReturnSelf && !terminalOnly) {
            return this;
        } else {
            return getParent().getParentIdentityMapSession(query, true, terminalOnly);
        }
    }

    /**
     * INTERNAL:
     * Gets the session which this query will be executed on.
     * Generally will be called immediately before the call is translated,
     * which is immediately before session.executeCall.
     * <p>
     * Since the execution session also knows the correct datasource platform
     * to execute on, it is often used in the mappings where the platform is
     * needed for type conversion, or where calls are translated.
     * <p>
     * Is also the session with the accessor. Will return a ClientSession if
     * it is in transaction and has a write connection.
     * @return a session with a live accessor
     * @param query may store session name or reference class for brokers case
     */
    public AbstractSession getExecutionSession(DatabaseQuery query) {
        // This optimization is only for when executing with a ClientSession in
        // transaction. In that case log with the UnitOfWork instead of the
        // ClientSession.
        // Note that if actually executing on ServerSession or a registered
        // session of a broker, must execute on that session directly.

        //bug 5201121 Always use the parent or execution session from the parent
        // should never use the unit of work as it does not controll the
        //accessors and with a sessioon broker it will not have the correct
        //login info
        return getParent().getExecutionSession(query);
    }

    /**
     * INTERNAL:
     * Return the clone mapping.
     * The clone mapping contains clone of all registered objects,
     * this is required to store the original state of the objects when registered
     * so that only what is changed will be commited to the database and the parent,
     * (this is required to support parralel unit of work).
     */
    public IdentityHashtable getCloneMapping() {
        // PERF: lazy-init (3286089)
        if (cloneMapping == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            cloneMapping = new HardIdentityHashtable();
        }
        return cloneMapping;
    }

    protected boolean hasCloneMapping() {
        return ((cloneMapping != null) && !cloneMapping.isEmpty());
    }

    /**
     * INTERNAL:
     * Hashtable used to avoid garbage collection in weak caches.
     * ALSO, hashtable used as lookup when originals used for merge when original in
     * identitymap can not be found. As in a CacheIdentityMap
     */
    public IdentityHashtable getCloneToOriginals() {
        //Helper.toDo("proper fix, collection merge can have objects disapear for original.");
        if (cloneToOriginals == null) {// Must lazy initialize for remote.
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            cloneToOriginals = getIdentityHashTable();
        }
        return cloneToOriginals;
    }

    protected boolean hasCloneToOriginals() {
        return ((cloneToOriginals != null) && !cloneToOriginals.isEmpty());
    }

    /**
     * INTERNAL:
     * Return if there are any registered new objects.
     * This is used for both newObjectsOriginalToClone and newObjectsCloneToOriginal as they are always in synch.
     * PERF: Used to avoid initialization of new objects hashtable unless required.
     */
    public boolean hasNewObjects() {
        return ((newObjectsOriginalToClone != null) && !newObjectsOriginalToClone.isEmpty());
    }

    /**
     * INTERNAL: Returns the set of read-only classes that gets assigned to each newly created UnitOfWork.
     *
     * @see oracle.toplink.essentials.sessions.Project#setDefaultReadOnlyClasses(Vector)
     */
    public Vector getDefaultReadOnlyClasses() {
        return getParent().getDefaultReadOnlyClasses();
    }

    /**
     * INTERNAL:
     * The deleted objects stores any objects removed during the unit of work.
     * On commit they will all be removed from the database.
     */
    public IdentityHashtable getDeletedObjects() {
        if (deletedObjects == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            deletedObjects = new HardIdentityHashtable();
        }
        return deletedObjects;
    }

    protected boolean hasDeletedObjects() {
        return ((deletedObjects != null) && !deletedObjects.isEmpty());
    }

    /**
     * PUBLIC:
     * Return the descriptor for the alias.
     * UnitOfWork delegates this to the parent
     * Introduced because of Bug#2610803
     */
    public ClassDescriptor getDescriptorForAlias(String alias) {
        return getParent().getDescriptorForAlias(alias);
    }

    /**
     * PUBLIC:
     * Return all registered descriptors.
     * The unit of work inherits its parent's descriptors. The each descriptor's Java Class
     * is used as the key in the Hashtable returned.
     */
    public Map getDescriptors() {
        return getParent().getDescriptors();
    }

    /**
     * INTERNAL:
     * The life cycle tracks if the unit of work is active and is used for JTS.
     */
    public int getLifecycle() {
        return lifecycle;
    }

    /**
     * A reference to the last used merge manager. This is used to track locked
     * objects.
     */
    public MergeManager getMergeManager() {
        return this.lastUsedMergeManager;
    }

    /**
     * INTERNAL:
     * The hashtable stores any new aggregates that have been cloned.
     */
    public IdentityHashtable getNewAggregates() {
        if (this.newAggregates == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            this.newAggregates = getIdentityHashTable();
        }
        return newAggregates;
    }

    /**
     * INTERNAL:
     * The new objects stores any objects newly created during the unit of work.
     * On commit they will all be inserted into the database.
     */
    public synchronized IdentityHashtable getNewObjectsCloneToOriginal() {
        if (newObjectsCloneToOriginal == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            newObjectsCloneToOriginal = getIdentityHashTable();
        }
        return newObjectsCloneToOriginal;
    }

    /**
     * INTERNAL:
     * The new objects stores any objects newly created during the unit of work.
     * On commit they will all be inserted into the database.
     */
    public synchronized IdentityHashtable getNewObjectsOriginalToClone() {
        if (newObjectsOriginalToClone == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            if(isWeakReference())
                newObjectsOriginalToClone = new WeakKeyAndValueIdentityHashtable();
            else
                newObjectsOriginalToClone = new HardIdentityHashtable();
        }
        return newObjectsOriginalToClone;
    }

    
    private boolean isWeakReference(){
        return (referenceMode == ReferenceMode.WEAK);
    }
    
    private IdentityHashtable getIdentityHashTable(){
        if(isWeakReference())
            return new WeakKeyIdentityHashtable();
        else
            return new HardIdentityHashtable();
        
    }
    /**
     * INTERNAL:
     * Return the Sequencing object used by the session.
     */
    public Sequencing getSequencing() {
        return getParent().getSequencing();
    }

    /**
     * INTERNAL:
     * Marked internal as this is not customer API but helper methods for
     * accessing the server platform from within TopLink's other sessions types
     * (ie not DatabaseSession)
     */
    public ServerPlatform getServerPlatform(){
        return getParent().getServerPlatform();
    }

    /**
     * INTERNAL:
     * Returns the type of session, its class.
     * <p>
     * Override to hide from the user when they are using an internal subclass
     * of a known class.
     * <p>
     * A user does not need to know that their UnitOfWork is a
     * non-deferred UnitOfWork, or that their ClientSession is an
     * IsolatedClientSession.
     */
    public String getSessionTypeString() {
        return "UnitOfWork";
    }

    /**
     * INTERNAL:
     * Called after transaction is completed (committed or rolled back)
     */
    public void afterTransaction(boolean committed, boolean isExternalTransaction) {
        if (!committed && isExternalTransaction) {
            // In case jts transaction was internally started but rolled back
            // directly by TransactionManager this flag may still be true during afterCompletion
            getParent().setWasJTSTransactionInternallyStarted(false);
            //bug#4699614 -- added a new life cycle status so we know if the external transaction was rolledback and we don't try to rollback again later
            setLifecycle(AfterExternalTransactionRolledBack);
        }
        if ((getMergeManager() != null) && (getMergeManager().getAcquiredLocks() != null) && (!getMergeManager().getAcquiredLocks().isEmpty())) {
            //may have unreleased cache locks because of a rollback...
            getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager());
            this.setMergeManager(null);
        }
        getParent().afterTransaction(committed, isExternalTransaction);
    }

    /**
     * INTERNAL:
     * Return any new object matching the expression.
     * Used for in-memory querying.
     */
    public Object getObjectFromNewObjects(Class theClass, Vector selectionKey) {
        // PERF: Avoid initialization of new objects if none.
        if (!hasNewObjects()) {
            return null;
        }
        ObjectBuilder objectBuilder = getDescriptor(theClass).getObjectBuilder();
        for (Enumeration newObjectsEnum = getNewObjectsOriginalToClone().elements();
                 newObjectsEnum.hasMoreElements();) {
            Object object = newObjectsEnum.nextElement();
            if (theClass.isInstance(object)) {
                // removed dead null check as this method is never called if selectionKey == null
                Vector primaryKey = objectBuilder.extractPrimaryKeyFromObject(object, this);
                if (new CacheKey(primaryKey).equals(new CacheKey(selectionKey))) {
                    return object;
                }
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Return any new object matching the expression.
     * Used for in-memory querying.
     */
    public Object getObjectFromNewObjects(Expression selectionCriteria, Class theClass, AbstractRecord translationRow, InMemoryQueryIndirectionPolicy valueHolderPolicy) {
        // PERF: Avoid initialization of new objects if none.
        if (!hasNewObjects()) {
            return null;
        }
        for (Enumeration newObjectsEnum = getNewObjectsOriginalToClone().elements();
                 newObjectsEnum.hasMoreElements();) {
            Object object = newObjectsEnum.nextElement();
            if (theClass.isInstance(object)) {
                if (selectionCriteria == null) {
                    return object;
                }
                if (selectionCriteria.doesConform(object, this, translationRow, valueHolderPolicy)) {
                    return object;
                }
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Returns all the objects which are deleted during root commit of unit of work.
     */
    public IdentityHashtable getObjectsDeletedDuringCommit() {
        // PERF: lazy-init (3286089)
        if (objectsDeletedDuringCommit == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            objectsDeletedDuringCommit = new HardIdentityHashtable();
        }
        return objectsDeletedDuringCommit;
    }

    protected boolean hasObjectsDeletedDuringCommit() {
        return ((objectsDeletedDuringCommit != null) && !objectsDeletedDuringCommit.isEmpty());
    }

    /**
     * INTERNAL:
     * Return optimistic read lock objects
     */
    public Hashtable getOptimisticReadLockObjects() {
        if (optimisticReadLockObjects == null) {
            optimisticReadLockObjects = new Hashtable(2);
        }
        return optimisticReadLockObjects;
    }

    /**
     * INTERNAL:
     * Return the original version of the new object (working clone).
     */
    public Object getOriginalVersionOfNewObject(Object workingClone) {
        // PERF: Avoid initialization of new objects if none.
        if (!hasNewObjects()) {
            return null;
        }
        return getNewObjectsCloneToOriginal().get(workingClone);
    }

    /**
     * ADVANCED:
     * Return the original version of the object(clone) from the parent's identity map.
     */
    public Object getOriginalVersionOfObject(Object workingClone) {
        // Can be null when called from the mappings.
        if (workingClone == null) {
            return null;
        }
        ClassDescriptor descriptor = getDescriptor(workingClone);
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(workingClone, this);

        Vector primaryKey = builder.extractPrimaryKeyFromObject(implementation, this);
        Object original = getParent().getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor);

        if (original == null) {
            // Check if it is a registered new object.
            original = getOriginalVersionOfNewObject(implementation);
        }

        if (original == null) {
            // For bug 3013948 looking in the cloneToOriginals mapping will not help
            // if the object was never registered.
            if (isClassReadOnly(implementation.getClass(), descriptor)) {
                return implementation;
            }

            // The object could have been removed from the cache even though it was in the unit of work.
            // fix for 2.5.1.3 PWK (1360)
            if (hasCloneToOriginals()) {
                original = getCloneToOriginals().get(workingClone);
            }
        }

        if (original == null) {
            // This means that it must be an unregistered new object, so register a new clone as its original.
            original = buildOriginal(implementation);
        }

        return original;
    }

    /**
     * ADVANCED:
     * Return the original version of the object(clone) from the parent's identity map.
     */
    public Object getOriginalVersionOfObjectOrNull(Object workingClone) {
        // Can be null when called from the mappings.
        if (workingClone == null) {
            return null;
        }
        ClassDescriptor descriptor = getDescriptor(workingClone);
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(workingClone, this);

        Vector primaryKey = builder.extractPrimaryKeyFromObject(implementation, this);
        Object original = getParent().getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor);

        if (original == null) {
            // Check if it is a registered new object.
            original = getOriginalVersionOfNewObject(implementation);
        }

        if (original == null) {
            // For bug 3013948 looking in the cloneToOriginals mapping will not help
            // if the object was never registered.
            if (isClassReadOnly(implementation.getClass(), descriptor)) {
                return implementation;
            }

            // The object could have been removed from the cache even though it was in the unit of work.
            // fix for 2.5.1.3 PWK (1360)
            if (hasCloneToOriginals()) {
                original = getCloneToOriginals().get(workingClone);
            }
        }
        return original;
    }

    /**
     * PUBLIC:
     * Return the parent.
     * This is a unit of work if nested, otherwise a database session or client session.
     */
    public AbstractSession getParent() {
        return parent;
    }

    /**
     * INTERNAL:
     * Return the platform for a particular class.
     */
    public Platform getPlatform(Class domainClass) {
        return getParent().getPlatform(domainClass);
    }
    
    /**
     * Search for and return the user defined property from this UOW, if it not found then search for the property
     * from parent.
     */
    public Object getProperty(String name){
        Object propertyValue = super.getProperties().get(name);
        if (propertyValue == null) {
           propertyValue = getParent().getProperty(name);
        }
        return propertyValue;
    }
    
    /**
     * INTERNAL:
     * Return whether to throw exceptions on conforming queries
     */
    public int getShouldThrowConformExceptions() {
        return shouldThrowConformExceptions;
    }

    /**
     * PUBLIC:
     * Return the query from the session pre-defined queries with the given name.
     * This allows for common queries to be pre-defined, reused and executed by name.
     */
    public DatabaseQuery getQuery(String name, Vector arguments) {
        DatabaseQuery query = super.getQuery(name, arguments);
        if (query == null) {
            query = getParent().getQuery(name, arguments);
        }

        return query;
    }

    /**
     * PUBLIC:
     * Return the query from the session pre-defined queries with the given name.
     * This allows for common queries to be pre-defined, reused and executed by name.
     */
    public DatabaseQuery getQuery(String name) {
        DatabaseQuery query = super.getQuery(name);
        if (query == null) {
            query = getParent().getQuery(name);
        }

        return query;
    }

    /**
     * INTERNAL:
     * Returns the set of read-only classes for the receiver.
     * Use this method with setReadOnlyClasses() to modify a UnitOfWork's set of read-only
     * classes before using the UnitOfWork.
     * @return Hashtable containing the Java Classes that are currently read-only.
     * @see #setReadOnlyClasses(Vector)
     */
    public Hashtable getReadOnlyClasses() {
        return readOnlyClasses;
    }

    /**
     * INTERNAL:
     * The removed objects stores any newly registered objects removed during the nested unit of work.
     * On commit they will all be removed from the parent unit of work.
     */
    protected IdentityHashtable getRemovedObjects() {
        // PERF: lazy-init (3286089)
        if (removedObjects == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            removedObjects = new HardIdentityHashtable();
        }
        return removedObjects;
    }

    protected boolean hasRemovedObjects() {
        return ((removedObjects != null) && !removedObjects.isEmpty());
    }

    protected boolean hasModifyAllQueries() {
        return ((modifyAllQueries != null) && !modifyAllQueries.isEmpty());
    }

    protected boolean hasDeferredModifyAllQueries() {
        return ((deferredModifyAllQueries != null) && !deferredModifyAllQueries.isEmpty());
    }

    /**
     * INTERNAL:
     * Find out what the lifecycle state of this UoW is in.
     */
    public int getState() {
        return lifecycle;
    }
    
    /**
     * INTERNAL:
     * PERF: Return the associated external transaction.
     * Used to optimize activeUnitOfWork lookup.
     */
    public Object getTransaction() {
        return transaction;
    }
    
    /**
     * INTERNAL:
     * PERF: Set the associated external transaction.
     * Used to optimize activeUnitOfWork lookup.
     */
    public void setTransaction(Object transaction) {
        this.transaction = transaction;
    }

    /**
     * ADVANCED:
     * Returns the currentChangeSet from the UnitOfWork.
     * This is only valid after the UnitOfWOrk has commited successfully
     */
    public oracle.toplink.essentials.changesets.UnitOfWorkChangeSet getUnitOfWorkChangeSet() {
        return unitOfWorkChangeSet;
    }

    /**
     * INTERNAL:
     * Used to lazy Initialize the unregistered existing Objects collection.
     * @return oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    public IdentityHashtable getUnregisteredExistingObjects() {
        if (this.unregisteredExistingObjects == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            this.unregisteredExistingObjects = new HardIdentityHashtable();
        }
        return unregisteredExistingObjects;
    }

    /**
     * INTERNAL:
     * This is used to store unregistred objects discovered in the parent so that the child
     * unit of work knows not to register them on commit.
     */
    protected IdentityHashtable getUnregisteredNewObjects() {
        if (unregisteredNewObjects == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            unregisteredNewObjects = new HardIdentityHashtable();
        }
        return unregisteredNewObjects;
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public int getValidationLevel() {
        return validationLevel;
    }

    /**
     * ADVANCED:
     * The Unit of work is capable of preprocessing to determine if any on the clone have been changed.
     * This is computationaly expensive and should be avoided on large object graphs.
     */
    public boolean hasChanges() {
        if (hasNewObjects()) {
            return true;
        }
        HardIdentityHashtable allObjects = collectAndPrepareObjectsForNestedMerge();

        //Using the nested merge prevent the UnitOfWork from assigning sequence numbers
        if (!getUnregisteredNewObjects().isEmpty()) {
            return true;
        }
        if (hasDeletedObjects()) {
            return true;
        }
        UnitOfWorkChangeSet changeSet = calculateChanges(allObjects, new UnitOfWorkChangeSet());
        return changeSet.hasChanges();
    }

    /**
     * INTERNAL:
     * Does this unit of work have any changes or anything that requires a write
     * to the database and a transaction to be started.
     * Should be called after changes are calculated internally by commit.
     * <p>
     * Note if a transaction was begun prematurely it still needs to be committed.
     */
    protected boolean hasModifications() {
        if (getUnitOfWorkChangeSet().hasChanges() || hasDeletedObjects() || hasModifyAllQueries() || hasDeferredModifyAllQueries() || ((oracle.toplink.essentials.internal.sessions.UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).hasForcedChanges()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * INTERNAL:
     * Set up the IdentityMapManager. This method allows subclasses of Session to override
     * the default IdentityMapManager functionality.
     */
    public void initializeIdentityMapAccessor() {
        this.identityMapAccessor = new UnitOfWorkIdentityMapAccessor(this, new IdentityMapManager(this));
    }

    /**
     * INTERNAL:
     * Return the results from exeucting the database query.
     * the arguments should be a database row with raw data values.
     */
    public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord databaseRow) throws DatabaseException, QueryException {
        if (!isActive()) {
            throw QueryException.querySentToInactiveUnitOfWork(query);
        }
        return query.executeInUnitOfWork(this, databaseRow);
    }

    /**
     * INTERNAL:
     * Register the object with the unit of work.
     * This does not perform wrapping or unwrapping.
     * This is used for internal registration in the merge manager.
     */
    public Object internalRegisterObject(Object object, ClassDescriptor descriptor) {
        if (object == null) {
            return null;
        }
        if (descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
            throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(object.getClass());
        }
        Object registeredObject = checkIfAlreadyRegistered(object, descriptor);
        if (registeredObject == null) {
            registeredObject = checkExistence(object);
            if (registeredObject == null) {
                // This means that the object is not in the parent im, so was created under this unit of work.
                // This means that it must be new.
                registeredObject = cloneAndRegisterNewObject(object);
            }
        }
        return registeredObject;
    }

    /**
     * PUBLIC:
     * Return if the unit of work is active. (i.e. has not been released).
     */
    public boolean isActive() {
        return !isDead();
    }

    /**
     * PUBLIC:
     * Checks to see if the specified class or descriptor is read-only or not in this UnitOfWork.
     *
     * @return boolean, true if the class is read-only, false otherwise.
     */
    public boolean isClassReadOnly(Class theClass, ClassDescriptor descriptor) {
        if ((descriptor != null) && (descriptor.shouldBeReadOnly())) {
            return true;
        }
        if ((theClass != null) && getReadOnlyClasses().containsKey(theClass)) {
            return true;
        }
        return false;
    }

    /**
     * INTERNAL:
     * Check if the object is already registered.
     */
    public boolean isCloneNewObject(Object clone) {
        // PERF: Avoid initialization of new objects if none.
        if (!hasNewObjects()) {
            return false;
        }
        return getNewObjectsCloneToOriginal().containsKey(clone);
    }

    /**
     * INTERNAL:
     * Return if the unit of work is waiting to be committed or in the process of being committed.
     */
    public boolean isCommitPending() {
        return getLifecycle() == CommitPending;
    }

    /**
     * INTERNAL:
     * Return if the unit of work is dead.
     */
    public boolean isDead() {
        return getLifecycle() == Death;
    }

    /**
     * PUBLIC:
     * Return whether the session currently has a database transaction in progress.
     */
    public boolean isInTransaction() {
        return getParent().isInTransaction();
    }

    /**
     * INTERNAL:
     * Return if the unit of work is waiting to be merged or in the process of being merged.
     */
    public boolean isMergePending() {
        return getLifecycle() == MergePending;
    }

    /**
     * INTERNAL:
     * Has writeChanges() been attempted on this UnitOfWork? It may have
     * either suceeded or failed but either way the UnitOfWork is in a highly
     * restricted state.
     */
    public boolean isAfterWriteChangesButBeforeCommit() {
        return ((getLifecycle() == CommitTransactionPending) || (getLifecycle() == WriteChangesFailed));
    }

    /**
     * INTERNAL:
     * Once writeChanges has failed all a user can do really is rollback.
     */
    protected boolean isAfterWriteChangesFailed() {
        return getLifecycle() == WriteChangesFailed;
    }

    /**
     * PUBLIC:
     * Return whether this session is a nested unit of work or not.
     */
    public boolean isNestedUnitOfWork() {
        return false;
    }

    /**
     * INTERNAL:
     * Return if the object has been deleted in this unit of work.
     */
    public boolean isObjectDeleted(Object object) {
        boolean isDeleted = false;
        if (hasDeletedObjects()) {
            isDeleted = getDeletedObjects().containsKey(object);
        }
        if (getParent().isUnitOfWork()) {
            return isDeleted || ((UnitOfWorkImpl)getParent()).isObjectDeleted(object);
        } else {
            return isDeleted;
        }
    }

    /**
     * INTERNAL:
     * This method is used to determine if the clone is a new Object in the UnitOfWork
     */
    public boolean isObjectNew(Object clone) {
        //CR3678 - ported from 4.0
        return (isCloneNewObject(clone) || (!isObjectRegistered(clone) && !getReadOnlyClasses().contains(clone.getClass()) && !getUnregisteredExistingObjects().contains(clone)));
    }

    /**
     * INTERNAL:
     * Return whether the clone object is already registered.
     */
    public boolean isObjectRegistered(Object clone) {
        if (getCloneMapping().containsKey(clone)) {
            return true;
        }

        // We do smart merge here
        if (isSmartMerge()){
            ClassDescriptor descriptor = getDescriptor(clone);
            if (getParent().getIdentityMapAccessorInstance().containsObjectInIdentityMap(keyFromObject(clone, descriptor), clone.getClass(), descriptor) ) {
                mergeCloneWithReferences(clone);

                // don't put clone in clone mapping since it would result in duplicate clone
                return true;
            }
        }
        return false;
    }

    /**
     * INTERNAL:
     * Return whether the original object is new.
     * It was either registered as new or discovered as a new aggregate
     * within another new object.
     */
    public boolean isOriginalNewObject(Object original) {
        return (hasNewObjects() && getNewObjectsOriginalToClone().containsKey(original)) || getNewAggregates().containsKey(original);
    }

    /**
     * INTERNAL:
     * Return the status of smart merge
     */
    public static boolean isSmartMerge() {
        return SmartMerge;
    }

    /**
     * INTERNAL:
     * For synchronized units of work, dump SQL to database.
     * For cases where writes occur before the end of the transaction don't commit
     */
    public void issueSQLbeforeCompletion() {
        issueSQLbeforeCompletion(true);
    }
    
    /**
     * INTERNAL:
     * For synchronized units of work, dump SQL to database.
     * For cases where writes occur before the end of the transaction don't commit
     */
    public void issueSQLbeforeCompletion(boolean commitTransaction) {
        if (getLifecycle() == CommitTransactionPending) {
            commitTransactionAfterWriteChanges();
            return;
        }
        // CR#... call event and log.
        log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");
        getEventManager().preCommitUnitOfWork();
        setLifecycle(CommitPending);
        commitToDatabaseWithChangeSet(commitTransaction);
    }

    /**
       * INTERNAL:
       * Will notify all the deferred ModifyAllQuery's (excluding UpdateAllQuery's) and deferred UpdateAllQuery's to execute.
       */
    protected void issueModifyAllQueryList() {
        if (deferredModifyAllQueries != null) {
            for (int i = 0; i < deferredModifyAllQueries.size(); i++) {
                Object[] queries = (Object[])deferredModifyAllQueries.get(i);
                ModifyAllQuery query = (ModifyAllQuery)queries[0];
                AbstractRecord translationRow = (AbstractRecord)queries[1];
                getParent().executeQuery(query, translationRow);
            }
        }
    }

    /**
     * INTERNAL:
     * Return if this session is a synchronized unit of work.
     */
    public boolean isSynchronized() {
        return isSynchronized;
    }

    /**
     * PUBLIC:
     * Return if this session is a unit of work.
     */
    public boolean isUnitOfWork() {
        return true;
    }

    /**
     * INTERNAL: Merge the changes to all objects to the parent.
     */
    protected void mergeChangesIntoParent() {
        UnitOfWorkChangeSet uowChangeSet = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet();
        if (uowChangeSet == null) {
            // may be using the old commit prosess usesOldCommit()
            setUnitOfWorkChangeSet(new UnitOfWorkChangeSet());
            uowChangeSet = (UnitOfWorkChangeSet)getUnitOfWorkChangeSet();
            calculateChanges(getAllClones(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet());
        }

        // 3286123 - if no work to be done, skip this part of uow.commit()
        if (hasModifications()) {
            setPendingMerge();
            startOperationProfile(SessionProfiler.Merge);
            // Ensure concurrency if cache isolation requires.
            getParent().getIdentityMapAccessorInstance().acquireWriteLock();
            MergeManager manager = getMergeManager();
            if (manager == null){
                // no MergeManager created for locks durring commit
                manager = new MergeManager(this);
            }
             
            try {
                if (!isNestedUnitOfWork()) {
                    preMergeChanges();
                }

                // Must clone the clone mapping because entries can be added to it during the merging,
                // and that can lead to concurrency problems.
                getParent().getEventManager().preMergeUnitOfWorkChangeSet(uowChangeSet);
                if (!isNestedUnitOfWork() && getDatasourceLogin().shouldSynchronizeObjectLevelReadWrite()) {
                    setMergeManager(manager);
                    //If we are merging into the shared cache acquire all required locks before merging.
                    getParent().getIdentityMapAccessorInstance().getWriteLockManager().acquireRequiredLocks(getMergeManager(), (UnitOfWorkChangeSet)getUnitOfWorkChangeSet());
                }
                Enumeration changeSetLists = ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).getObjectChanges().elements();
                while (changeSetLists.hasMoreElements()) {
                    Hashtable objectChangesList = (Hashtable)((Hashtable)changeSetLists.nextElement()).clone();
                    if (objectChangesList != null) {// may be no changes for that class type.
                        for (Enumeration pendingEnum = objectChangesList.elements();
                                 pendingEnum.hasMoreElements();) {
                            ObjectChangeSet changeSetToWrite = (ObjectChangeSet)pendingEnum.nextElement();
                            if (changeSetToWrite.hasChanges()) {
                                Object objectToWrite = changeSetToWrite.getUnitOfWorkClone();

                                //bug#4154455 -- only merge into the shared cache if the object is new or if it already exists in the shared cache
                                if (changeSetToWrite.isNew() || (getOriginalVersionOfObjectOrNull(objectToWrite) != null)) {
                                    manager.mergeChanges(objectToWrite, changeSetToWrite);
                                }
                            } else {
                                // if no 'real' changes to the object change set, remove it from the
                                // list so it won't be unnecessarily sent via cache sync.
                                uowChangeSet.removeObjectChangeSet(changeSetToWrite);
                            }
                        }
                    }
                }

                // Notify the queries to merge into the shared cache
                if (modifyAllQueries != null) {
                    for (int i = 0; i < modifyAllQueries.size(); i++) {
                        ModifyAllQuery query = (ModifyAllQuery)modifyAllQueries.get(i);
                        query.setSession(getParent());// ensure the query knows which cache to update
                        query.mergeChangesIntoSharedCache();
                    }
                }

                if (isNestedUnitOfWork()) {
                    changeSetLists = ((UnitOfWorkChangeSet)getUnitOfWorkChangeSet()).getNewObjectChangeSets().elements();
                    while (changeSetLists.hasMoreElements()) {
                        HardIdentityHashtable objectChangesList = (HardIdentityHashtable)((HardIdentityHashtable)changeSetLists.nextElement()).clone();
                        if (objectChangesList != null) {// may be no changes for that class type.
                            for (Enumeration pendingEnum = objectChangesList.elements();
                                     pendingEnum.hasMoreElements();) {
                                ObjectChangeSet changeSetToWrite = (ObjectChangeSet)pendingEnum.nextElement();
                                if (changeSetToWrite.hasChanges()) {
                                    Object objectToWrite = changeSetToWrite.getUnitOfWorkClone();
                                    manager.mergeChanges(objectToWrite, changeSetToWrite);
                                } else {
                                    // if no 'real' changes to the object change set, remove it from the
                                    // list so it won't be unnecessarily sent via cache sync.
                                    uowChangeSet.removeObjectChangeSet(changeSetToWrite);
                                }
                            }
                        }
                    }
                }
                if (!isNestedUnitOfWork()) {
                    //If we are merging into the shared cache release all of the locks that we acquired.
                    getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(manager);
                    setMergeManager(null);

                    postMergeChanges();
                }
            } finally {
                if (!isNestedUnitOfWork() && !manager.getAcquiredLocks().isEmpty()) {
                    // if the locks have not already been released (!acquiredLocks.empty)
                    // then there must have been an error, release all of the locks.
                    getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(manager);
                    setMergeManager(null);
                }
                getParent().getIdentityMapAccessorInstance().releaseWriteLock();
                getParent().getEventManager().postMergeUnitOfWorkChangeSet(uowChangeSet);
                endOperationProfile(SessionProfiler.Merge);
            }
        }
    }

    /**
     * PUBLIC:
     * Merge the attributes of the clone into the unit of work copy.
     * This can be used for objects that are returned from the client through
     * RMI serialization (or another serialization mechanism), because the RMI object
     * will be a clone this will merge its attributes correctly to preserve object
     * identity within the unit of work and record its changes.
     *
     * The object and its private owned parts are merged.
     *
     * @return the registered version for the clone being merged.
     * @see #shallowMergeClone(Object)
     * @see #deepMergeClone(Object)
     */
    public Object mergeClone(Object rmiClone) {
        return mergeClone(rmiClone, MergeManager.CASCADE_PRIVATE_PARTS);
    }

    /**
     * INTERNAL:
     * Merge the attributes of the clone into the unit of work copy.
     */
    public Object mergeClone(Object rmiClone, int cascadeDepth) {
        if (rmiClone == null) {
            return null;
        }

        //CR#2272
        logDebugMessage(rmiClone, "merge_clone");

        startOperationProfile(SessionProfiler.Merge);
        ObjectBuilder builder = getDescriptor(rmiClone).getObjectBuilder();
        Object implementation = builder.unwrapObject(rmiClone, this);

        MergeManager manager = new MergeManager(this);
        manager.mergeCloneIntoWorkingCopy();
        manager.setCascadePolicy(cascadeDepth);

        Object merged = null;
        try {
            merged = manager.mergeChanges(implementation, null);
        } catch (RuntimeException exception) {
            merged = handleException(exception);
        }
        endOperationProfile(SessionProfiler.Merge);

        return merged;
    }

    /**
     * INTERNAL:
     * for synchronized units of work, merge changes into parent
     */
    public void mergeClonesAfterCompletion() {
        mergeChangesIntoParent();
        // CR#... call event and log.
        getEventManager().postCommitUnitOfWork();
        log(SessionLog.FINER, SessionLog.TRANSACTION, "end_unit_of_work_commit");
    }

    /**
     * PUBLIC:
     * Merge the attributes of the clone into the unit of work copy.
     * This can be used for objects that are returned from the client through
     * RMI serialization (or another serialization mechanism), because the RMI object
     * will be a clone this will merge its attributes correctly to preserve object
     * identity within the unit of work and record its changes.
     *
     * The object and its private owned parts are merged. This will include references from
     * dependent objects to independent objects.
     *
     * @return the registered version for the clone being merged.
     * @see #shallowMergeClone(Object)
     * @see #deepMergeClone(Object)
     */
    public Object mergeCloneWithReferences(Object rmiClone) {
        return this.mergeCloneWithReferences(rmiClone, MergeManager.CASCADE_PRIVATE_PARTS);
    }
    

    /**
     * PUBLIC:
     * Merge the attributes of the clone into the unit of work copy.
     * This can be used for objects that are returned from the client through
     * RMI serialization (or another serialization mechanism), because the RMI object
     * will be a clone this will merge its attributes correctly to preserve object
     * identity within the unit of work and record its changes.
     *
     * The object and its private owned parts are merged. This will include references from
     * dependent objects to independent objects.
     *
     * @return the registered version for the clone being merged.
     * @see #shallowMergeClone(Object)
     * @see #deepMergeClone(Object)
     */
    public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy) {
        return mergeCloneWithReferences(rmiClone, cascadePolicy, false);
    }
    
    /**
     * INTERNAL:
     * Merge the attributes of the clone into the unit of work copy.
     * This can be used for objects that are returned from the client through
     * RMI serialization (or another serialization mechanism), because the RMI object
     * will be a clone this will merge its attributes correctly to preserve object
     * identity within the unit of work and record its changes.
     *
     * The object and its private owned parts are merged. This will include references from
     * dependent objects to independent objects.
     *
     * @return the registered version for the clone being merged.
     * @see #shallowMergeClone(Object)
     * @see #deepMergeClone(Object)
     */
    public Object mergeCloneWithReferences(Object rmiClone, int cascadePolicy, boolean forceCascade) {
       if (rmiClone == null) {
            return null;
        }
        ClassDescriptor descriptor = getDescriptor(rmiClone);
        if ((descriptor == null) || descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
            if (cascadePolicy == MergeManager.CASCADE_BY_MAPPING){
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[]{rmiClone}));
            }
            return rmiClone;
        }

        //CR#2272
        logDebugMessage(rmiClone, "merge_clone_with_references");

        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(rmiClone, this);

        MergeManager manager = new MergeManager(this);
        manager.mergeCloneWithReferencesIntoWorkingCopy();
        manager.setCascadePolicy(cascadePolicy);
        manager.setForceCascade(forceCascade);
        Object mergedObject = manager.mergeChanges(implementation, null);
        if (isSmartMerge()) {
            return builder.wrapObject(mergedObject, this);
        } else {
            return mergedObject;
        }
    }

    /**
     * PUBLIC:
     * Return a new instance of the class registered in this unit of work.
     * This can be used to ensure that new objects are registered correctly.
     */
    public Object newInstance(Class theClass) {
        //CR#2272
        logDebugMessage(theClass, "new_instance");

        ClassDescriptor descriptor = getDescriptor(theClass);
        Object newObject = descriptor.getObjectBuilder().buildNewInstance();
        return registerObject(newObject);
    }

    /**
     * INTERNAL:
     * This method will perform a delete operation on the provided objects pre-determing
     * the objects that will be deleted by a commit of the UnitOfWork including privately
     * owned objects. It does not execute a query for the deletion of these objects as the
     * normal deleteobject operation does. Mainly implemented to provide EJB 3.0 deleteObject
     * support.
     */
    public void performRemove(Object toBeDeleted, IdentityHashtable visitedObjects){
        try {
            if (toBeDeleted == null) {
                return;
            }
            ClassDescriptor descriptor = getDescriptor(toBeDeleted);
            if ((descriptor == null) || descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[]{toBeDeleted}));
            }
            logDebugMessage(toBeDeleted, "deleting_object");

            startOperationProfile(SessionProfiler.DeletedObject);
            //bug 4568370+4599010; fix EntityManager.remove() to handle new objects
            if (getDeletedObjects().contains(toBeDeleted)){
              return;
            }
            visitedObjects.put(toBeDeleted,toBeDeleted);
            Object registeredObject = checkIfAlreadyRegistered(toBeDeleted, descriptor);
            if (registeredObject == null) {
                Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(toBeDeleted, this);
                DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery();
                existQuery = (DoesExistQuery)existQuery.clone();
                existQuery.setObject(toBeDeleted);
                existQuery.setPrimaryKey(primaryKey);
                existQuery.setDescriptor(descriptor);
        
                existQuery.setCheckCacheFirst(true);
                if (((Boolean)executeQuery(existQuery)).booleanValue()){
                    throw new IllegalArgumentException(ExceptionLocalization.buildMessage("cannot_remove_detatched_entity", new Object[]{toBeDeleted}));
                }//else, it is a new or previously deleted object that should be ignored (and delete should cascade)
            }else{
                //fire events only if this is a managed object
                if (descriptor.getEventManager().hasAnyEventListeners()) {
                    oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(toBeDeleted);
                    event.setEventCode(DescriptorEventManager.PreRemoveEvent);
                    event.setSession(this);
                    descriptor.getEventManager().executeEvent(event);
                }
                if (hasNewObjects() && getNewObjectsOriginalToClone().contains(registeredObject)){
                    unregisterObject(registeredObject, DescriptorIterator.NoCascading);
                }else{
                    getDeletedObjects().put(toBeDeleted, toBeDeleted);
                }
            }
            descriptor.getObjectBuilder().cascadePerformRemove(toBeDeleted, this, visitedObjects);
        } finally {
            endOperationProfile(SessionProfiler.DeletedObject);
        }
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public void performFullValidation() {
        setValidationLevel(Full);

    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public void performPartialValidation() {
        setValidationLevel(Partial);
    }

    /**
     * INTERNAL:
     * This method is called from clone and register. It includes the processing
     * required to clone an object, including populating attributes, putting in
     * UOW identitymap and building a backupclone
     */
    protected void populateAndRegisterObject(Object original, Object workingClone, Vector primaryKey, ClassDescriptor descriptor, Object writeLockValue, long readTime) {
        // This must be registered before it is built to avoid cycles.
        getIdentityMapAccessorInstance().putInIdentityMap(workingClone, primaryKey, writeLockValue, readTime, descriptor);

        //Set ChangeListener for ObjectChangeTrackingPolicy and AttributeChangeTrackingPolicy,
        //but not DeferredChangeDetectionPolicy. Build backup clone for DeferredChangeDetectionPolicy
        //or ObjectChangeTrackingPolicy, but not for AttributeChangeTrackingPolicy.
        // - Set listener before populating attributes so aggregates can find the parent's listener
        descriptor.getObjectChangePolicy().setChangeListener(workingClone, this, descriptor);
        descriptor.getObjectChangePolicy().dissableEventProcessing(workingClone);

        ObjectBuilder builder = descriptor.getObjectBuilder();
        builder.populateAttributesForClone(original, workingClone, this);
        Object backupClone = descriptor.getObjectChangePolicy().buildBackupClone(workingClone, builder, this);
        getCloneMapping().put(workingClone, backupClone);
        
        descriptor.getObjectChangePolicy().enableEventProcessing(workingClone);
    }

    /**
     * INTERNAL:
     * Remove objects from parent's identity map.
     */
    protected void postMergeChanges() {
        //bug 4730595: objects removed during flush are not removed from the cache during commit
        if (!this.getUnitOfWorkChangeSet().getDeletedObjects().isEmpty()){
            IdentityHashtable deletedObjects = this.getUnitOfWorkChangeSet().getDeletedObjects();
            for (Enumeration removedObjects = deletedObjects.keys(); removedObjects.hasMoreElements(); ) {
                ObjectChangeSet removedObjectChangeSet = (ObjectChangeSet) removedObjects.nextElement();
                java.util.Vector primaryKeys = removedObjectChangeSet.getPrimaryKeys();
                getParent().getIdentityMapAccessor().removeFromIdentityMap(primaryKeys, removedObjectChangeSet.getClassType(this));
            }
        }
    }

    /**
     * INTERNAL:
     * Remove objects deleted during commit from clone and new object cache so that these are not merged
     */
    protected void preMergeChanges() {
        if (hasObjectsDeletedDuringCommit()) {
            for (Enumeration removedObjects = getObjectsDeletedDuringCommit().keys();
                     removedObjects.hasMoreElements();) {
                Object removedObject = removedObjects.nextElement();
                getCloneMapping().remove(removedObject);
                getAllClones().remove(removedObject);
                // PERF: Avoid initialization of new objects if none.
                if (hasNewObjects()) {
                    Object referenceObjectToRemove = getNewObjectsCloneToOriginal().get(removedObject);
                    if (referenceObjectToRemove != null) {
                        getNewObjectsCloneToOriginal().remove(removedObject);
                        getNewObjectsOriginalToClone().remove(referenceObjectToRemove);
                    }
                }
            }
        }
    }

    /**
     * PUBLIC:
     * Print the objects in the unit of work.
     * The output of this method will be logged to this unit of work's SessionLog at SEVERE level.
     */
    public void printRegisteredObjects() {
        if (shouldLog(SessionLog.SEVERE, SessionLog.CACHE)) {
            basicPrintRegisteredObjects();
        }
    }

    /**
     * INTERNAL:
     * This method is used to process delete queries that pass through the unitOfWork
     * It is extracted out of the internalExecuteQuery method to reduce duplication
     */
    public Object processDeleteObjectQuery(DeleteObjectQuery deleteQuery) {
        // We must ensure that we delete the clone not the original, (this can happen in the mappings update)
        if (deleteQuery.getObject() == null) {// Must validate.
            throw QueryException.objectToModifyNotSpecified(deleteQuery);
        }

        ClassDescriptor descriptor = getDescriptor(deleteQuery.getObject());
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(deleteQuery.getObject(), this);

        if (isClassReadOnly(implementation.getClass(), descriptor)) {
            throw QueryException.cannotDeleteReadOnlyObject(implementation);
        }

        if (isCloneNewObject(implementation)) {
            unregisterObject(implementation);
            return implementation;
        }
        Vector primaryKey = builder.extractPrimaryKeyFromObject(implementation, this);
        Object clone = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor);
        if (clone == null) {
            clone = implementation;
        }

        // Register will wrap so must unwrap again.
        clone = builder.unwrapObject(clone, this);

        deleteQuery.setObject(clone);
        if (!getCommitManager().isActive()) {
            getDeletedObjects().put(clone, primaryKey);
            return clone;
        } else {
            // If the object has already been deleted i.e. private-owned + deleted then don't do it twice.
            if (hasObjectsDeletedDuringCommit()) {
                if (getObjectsDeletedDuringCommit().containsKey(clone)) {
                    return clone;
                }
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Print the objects in the unit of work.
     */
    protected void basicPrintRegisteredObjects() {
        String cr = Helper.cr();
        StringWriter writer = new StringWriter();
        writer.write(LoggingLocalization.buildMessage("unitofwork_identity_hashcode", new Object[] { cr, String.valueOf(System.identityHashCode(this)) }));
        if (hasDeletedObjects()) {
            writer.write(cr + LoggingLocalization.buildMessage("deleted_objects"));
            for (Enumeration enumtr = getDeletedObjects().keys(); enumtr.hasMoreElements();) {
                Object object = enumtr.nextElement();
                writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, Helper.printVector(getDescriptor(object).getObjectBuilder().extractPrimaryKeyFromObject(object, this)), "\t", String.valueOf(System.identityHashCode(object)), object }));
            }
        }
        writer.write(cr + LoggingLocalization.buildMessage("all_registered_clones"));
        for (Enumeration enumtr = getCloneMapping().keys(); enumtr.hasMoreElements();) {
            Object object = enumtr.nextElement();
            writer.write(LoggingLocalization.buildMessage("key_identity_hash_code_object", new Object[] { cr, Helper.printVector(getDescriptor(object).getObjectBuilder().extractPrimaryKeyFromObject(object, this)), "\t", String.valueOf(System.identityHashCode(object)), object }));
        }
        log(SessionLog.SEVERE, SessionLog.TRANSACTION, writer.toString(), null, null, false);
    }

    /**
     * PUBLIC:
     * Register the objects with the unit of work.
     * All newly created root domain objects must be registered to be inserted on commit.
     * Also any existing objects that will be edited and were not read from this unit of work
     * must also be registered.
     * Once registered any changes to the objects will be commited to the database on commit.
     *
     * @return is the clones of the original objects, the return value must be used for editing.
     * Editing the original is not allowed in the unit of work.
     */
    public Vector registerAllObjects(Collection domainObjects) {
        Vector clones = new Vector(domainObjects.size());
        for (Iterator objectsEnum = domainObjects.iterator(); objectsEnum.hasNext();) {
            clones.addElement(registerObject(objectsEnum.next()));
        }
        return clones;
    }

    /**
     * PUBLIC:
     * Register the objects with the unit of work.
     * All newly created root domain objects must be registered to be inserted on commit.
     * Also any existing objects that will be edited and were not read from this unit of work
     * must also be registered.
     * Once registered any changes to the objects will be commited to the database on commit.
     *
     * @return is the clones of the original objects, the return value must be used for editing.
     * Editing the original is not allowed in the unit of work.
     */
    public Vector registerAllObjects(Vector domainObjects) throws DatabaseException, OptimisticLockException {
        Vector clones = new Vector(domainObjects.size());
        for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) {
            clones.addElement(registerObject(objectsEnum.nextElement()));
        }
        return clones;
    }

    /**
     * ADVANCED:
     * Register the existing object with the unit of work.
     * This is a advanced API that can be used if the application can guarentee the object exists on the database.
     * When registerObject is called the unit of work determines existence through the descriptor's doesExist setting.
     *
     * @return The clone of the original object, the return value must be used for editing.
     * Editing the original is not allowed in the unit of work.
     */
    public synchronized Object registerExistingObject(Object existingObject) {
        if (existingObject == null) {
            return null;
        }
        ClassDescriptor descriptor = getDescriptor(existingObject);
        if (descriptor == null) {
            throw DescriptorException.missingDescriptor(existingObject.getClass().toString());
        }
        if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) {
            return existingObject;
        }

        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(existingObject, this);
        Object registeredObject = this.registerExistingObject(implementation, descriptor);

        // Bug # 3212057 - workaround JVM bug (MWN)
        if (implementation != existingObject) {
            return builder.wrapObject(registeredObject, this);
        } else {
            return registeredObject;
        }
    }

    /**
     * INTERNAL:
     * Register the existing object with the unit of work.
     * This is a advanced API that can be used if the application can guarentee the object exists on the database.
     * When registerObject is called the unit of work determines existence through the descriptor's doesExist setting.
     *
     * @return The clone of the original object, the return value must be used for editing.
     * Editing the original is not allowed in the unit of work.
     */
    protected synchronized Object registerExistingObject(Object objectToRegister, ClassDescriptor descriptor) {
        if (isAfterWriteChangesButBeforeCommit()) {
            throw ValidationException.illegalOperationForUnitOfWorkLifecycle(getLifecycle(), "registerExistingObject");
        }
        if (descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
            throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(objectToRegister.getClass());
        }
        //CR#2272
        logDebugMessage(objectToRegister, "register_existing");
        Object registeredObject;
        try {
            startOperationProfile(SessionProfiler.Register);
            registeredObject = checkIfAlreadyRegistered(objectToRegister, descriptor);
            if (registeredObject == null) {
                // Check if object is existing, if it is it must be cloned into the unit of work
                // otherwise it is a new object
                Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(objectToRegister, this);

                // Always check the cache first.
                registeredObject = getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, objectToRegister.getClass(), descriptor);

                if (registeredObject == null) {
                    // This is a case where the object is not in the session cache,
                    // so a new cache-key is used as there is no original to use for locking.
                    registeredObject = cloneAndRegisterObject(objectToRegister, new CacheKey(primaryKey));
                }
            }
            
            //bug3659327
            //fetch group manager control fetch group support
            if (descriptor.hasFetchGroupManager()) {
                //if the object is already registered in uow, but it's partially fetched (fetch group case)
                if (descriptor.getFetchGroupManager().shouldWriteInto(objectToRegister, registeredObject)) {
                    //there might be cases when reverting/refreshing clone is needed.
                    descriptor.getFetchGroupManager().writePartialIntoClones(objectToRegister, registeredObject, this);
                }
            }
        } finally {
            endOperationProfile(SessionProfiler.Register);
        }
        return registeredObject;
    }

    /**
     * ADVANCED:
     * Register the new object with the unit of work.
     * This will register the new object without cloning.
     * Normally the registerObject method should be used for all registration of new and existing objects.
     * This version of the register method can only be used for new objects.
     * This method should only be used if a new object is desired to be registered without cloning.
     *
     * @see #registerObject(Object)
     */
    public synchronized Object registerNewObject(Object newObject) {
        if (newObject == null) {
            return null;
        }
        ClassDescriptor descriptor = getDescriptor(newObject);
        if (descriptor == null) {
            throw DescriptorException.missingDescriptor(newObject.getClass().toString());
        }

        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(newObject, this);

        this.registerNewObject(implementation, descriptor);

        if (implementation == newObject) {
            return newObject;
        } else {
            return builder.wrapObject(implementation, this);
        }
    }

    /**
     * INTERNAL:
     * Updated to allow passing in of the object's descriptor
     *
     * Register the new object with the unit of work.
     * This will register the new object without cloning.
     * Normally the registerObject method should be used for all registration of new and existing objects.
     * This version of the register method can only be used for new objects.
     * This method should only be used if a new object is desired to be registered without cloning.
     *
     * @see #registerObject(Object)
     */
    protected synchronized Object registerNewObject(Object implementation, ClassDescriptor descriptor) {
        if (isAfterWriteChangesButBeforeCommit()) {
            throw ValidationException.illegalOperationForUnitOfWorkLifecycle(getLifecycle(), "registerNewObject");
        }
        if (descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
            throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(implementation.getClass());
        }
        try {
            //CR#2272
            logDebugMessage(implementation, "register_new");

            startOperationProfile(SessionProfiler.Register);
            Object registeredObject = checkIfAlreadyRegistered(implementation, descriptor);
            if (registeredObject == null) {
                // Ensure that the registered object is the one from the parent cache.
                if (shouldPerformFullValidation()) {
                    Vector primaryKey = descriptor.getObjectBuilder().extractPrimaryKeyFromObject(implementation, this);
                    Object objectFromCache = getParent().getIdentityMapAccessorInstance().getFromIdentityMap(primaryKey, implementation.getClass(), descriptor);
                    if (objectFromCache != null) {
                        throw ValidationException.wrongObjectRegistered(implementation, objectFromCache);
                    }
                }
                ObjectBuilder builder = descriptor.getObjectBuilder();
                Object original = builder.buildNewInstance();

                registerNewObjectClone(implementation, original, descriptor);
                Object backupClone = builder.buildNewInstance();
                getCloneMapping().put(implementation, backupClone);

                // Check if the new objects should be cached.
                registerNewObjectInIdentityMap(implementation, implementation);
            }
        } finally {
            endOperationProfile(SessionProfiler.Register);
        }

        //as this is register new return the object passed in.
        return implementation;
    }

    /**
     * INTERNAL:
     *
     * Register the new object with the unit of work.
     * This will register the new object without cloning.
     * Checks based on existence will be completed and the create will be cascaded based on the
     * object's mappings cascade requirements. This is specific to EJB 3.0 support and is
     * @see #registerObject(Object)
     */
    public synchronized void registerNewObjectForPersist(Object newObject, IdentityHashtable visitedObjects) {
        try {
            if (newObject == null) {
                return;
            }
            if(visitedObjects.containsKey(newObject)) {
                return;
            }
            visitedObjects.put(newObject, newObject);
            ClassDescriptor descriptor = getDescriptor(newObject);
            if ((descriptor == null) || descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
                throw new IllegalArgumentException(ExceptionLocalization.buildMessage("not_an_entity", new Object[]{newObject}));
            }
            
            startOperationProfile(SessionProfiler.Register);
            
            Object registeredObject = checkIfAlreadyRegistered(newObject, descriptor);
            
            if (registeredObject == null) {
                registerNotRegisteredNewObjectForPersist(newObject, descriptor);
            } else if (this.isObjectDeleted(newObject)){
                //if object is deleted and a create is issued on the that object
                // then the object must be transitioned back to existing and not deleted
                this.undeleteObject(newObject);
            }
            descriptor.getObjectBuilder().cascadeRegisterNewForCreate(newObject, this, visitedObjects);
        } finally {
            endOperationProfile(SessionProfiler.Register);
        }
    }

    /**
     * INTERNAL:
     * Called only by registerNewObjectForPersist method,
     * and only if newObject is not already registered.
     * Could be overridden in subclasses.
     */
    protected void registerNotRegisteredNewObjectForPersist(Object newObject, ClassDescriptor descriptor) {
        // Ensure that the registered object is not detached.
        newObject.getClass();

        DoesExistQuery existQuery = descriptor.getQueryManager().getDoesExistQuery();
        existQuery = (DoesExistQuery)existQuery.clone();
        existQuery.setObject(newObject);
        existQuery.setDescriptor(descriptor);
        // only check the cache as we can wait until commit for the unique
        // constraint error to be thrown. This does ignore user's settings
        // on descriptor but calling persist() tells us the object is new.
        existQuery.checkCacheForDoesExist();
        if (((Boolean)executeQuery(existQuery)).booleanValue()) {
            throw ValidationException.cannotPersistExistingObject(newObject, this);
        }
        
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object original = builder.buildNewInstance();

        registerNewObjectClone(newObject, original, descriptor);
        Object backupClone = builder.buildNewInstance();
        getCloneMapping().put(newObject, backupClone);
        assignSequenceNumber(newObject);

        // Check if the new objects should be cached.
        registerNewObjectInIdentityMap(newObject, newObject);
    }
    
    /**
     * INTERNAL:
     * Register the working copy of a new object and its original.
     * The user must edit the working copy and the original is used to merge into the parent.
     * This mapping is kept both ways because lookup is required in both directions.
     */
    protected void registerNewObjectClone(Object clone, Object original, ClassDescriptor descriptor) {
        // Check if the new objects should be cached.
        registerNewObjectInIdentityMap(clone, original);

        getNewObjectsCloneToOriginal().put(clone, original);
        getNewObjectsOriginalToClone().put(original, clone);
        
        // run prePersist callbacks if any
        logDebugMessage(clone, "register_new_for_persist");
        
        if (descriptor.getEventManager().hasAnyEventListeners()) {
            oracle.toplink.essentials.descriptors.DescriptorEvent event = new oracle.toplink.essentials.descriptors.DescriptorEvent(clone);
            event.setEventCode(DescriptorEventManager.PrePersistEvent);
            event.setSession(this);
            descriptor.getEventManager().executeEvent(event);
        }
    }

    /**
     * INTERNAL:
     * Add the new object to the cache if set to.
     * This is useful for using mergeclone on new objects.
     */
    protected void registerNewObjectInIdentityMap(Object clone, Object original) {
        // CR 2728 Added check for sequencing to allow zero primitives for id's if the client
        //is not using sequencing.
        Class cls = clone.getClass();
        ClassDescriptor descriptor = getDescriptor(cls);
        boolean usesSequences = descriptor.usesSequenceNumbers();
        if (shouldNewObjectsBeCached()) {
            // Also put it in the cache if it has a valid primary key, this allows for double new object merges
            Vector key = keyFromObject(clone, descriptor);
            boolean containsNull = false;

            // begin CR#2041 Unit Of Work incorrectly put new objects with a primitive primary key in its cache
            Object pkElement;
            for (int index = 0; index < key.size(); index++) {
                pkElement = key.elementAt(index);
                if (pkElement == null) {
                    containsNull = true;
                } else if (usesSequences) {
                    containsNull = containsNull || getSequencing().shouldOverrideExistingValue(cls, pkElement);
                }
            }

            // end cr #2041
            if (!containsNull) {
                getIdentityMapAccessorInstance().putInIdentityMap(clone, key, null, 0, descriptor);
            }
        }
    }

    /**
     * PUBLIC:
     * Register the object with the unit of work.
     * All newly created root domain objects must be registered to be inserted on commit.
     * Also any existing objects that will be edited and were not read from this unit of work
     * must also be registered.
     * Once registered any changes to the objects will be commited to the database on commit.
     *
     * @return the clone of the original object, the return value must be used for editing,
     *
     * ** Editing the original is not allowed in the unit of work. **
     */
    public synchronized Object registerObject(Object object) {
        if (object == null) {
            return null;
        }
        ClassDescriptor descriptor = getDescriptor(object);
        if (descriptor == null) {
            throw DescriptorException.missingDescriptor(object.getClass().toString());
        }
        if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) {
            return object;
        }
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(object, this);
        boolean wasWrapped = implementation != object;
        Object registeredObject = this.registerObject(implementation, descriptor);
        if (wasWrapped) {
            return builder.wrapObject(registeredObject, this);
        } else {
            return registeredObject;
        }
    }

    /**
     * INTERNAL:
     * Allows for calling method to provide the descriptor information for this
     * object. Prevents double lookup of descriptor.
     *
     *
     * Register the object with the unit of work.
     * All newly created root domain objects must be registered to be inserted on commit.
     * Also any existing objects that will be edited and were not read from this unit of work
     * must also be registered.
     * Once registered any changes to the objects will be commited to the database on commit.
     *
     * calling this method will also sort the objects into different different groups
     * depending on if the object being registered is a bean or a regular Java
     * object and if its updates are deferred, non-deferred or if all modifications
     * are deferred.
     *
     * @return the clone of the original object, the return value must be used for editing,
     */
    protected synchronized Object registerObject(Object object, ClassDescriptor descriptor) {
        if (this.isClassReadOnly(descriptor.getJavaClass(), descriptor)) {
            return object;
        }
        if (isAfterWriteChangesButBeforeCommit()) {
            throw ValidationException.illegalOperationForUnitOfWorkLifecycle(getLifecycle(), "registerObject");
        }

        //CR#2272
        logDebugMessage(object, "register");

        Object registeredObject;
        try {
            startOperationProfile(SessionProfiler.Register);

            registeredObject = internalRegisterObject(object, descriptor);

        } finally {
            endOperationProfile(SessionProfiler.Register);
        }
        return registeredObject;
    }

    /**
     * INTERNAL:
     * Register this UnitOfWork against an external transaction controller
     */
    public void registerWithTransactionIfRequired() {
        if (getParent().hasExternalTransactionController() && ! isSynchronized()) {
            boolean hasAlreadyStarted = getParent().wasJTSTransactionInternallyStarted();
            getParent().getExternalTransactionController().registerSynchronizationListener(this, getParent());

            // CR#2998 - registerSynchronizationListener may toggle the wasJTSTransactionInternallyStarted
            // flag. As a result, we must compare the states and if the state is changed, then we must set the
            // setWasTransactionBegunPrematurely flag to ensure that we handle the transaction depth count
            // appropriately
            if (!hasAlreadyStarted && getParent().wasJTSTransactionInternallyStarted()) {
                // registerSynchronizationListener caused beginTransaction() called
                // and an external transaction internally started.
                this.setWasTransactionBegunPrematurely(true);
            }
        }
    }

    /**
     * PUBLIC:
     * Release the unit of work. This terminates this unit of work.
     * Because the unit of work operates on its own object space (clones) no work is required.
     * The unit of work should no longer be used or referenced by the application beyond this point
     * so that it can be garbage collected.
     *
     * @see #commit()
     */
    public void release() {
        log(SessionLog.FINER, SessionLog.TRANSACTION, "release_unit_of_work");
        getEventManager().preReleaseUnitOfWork();

        // If already succeeded at a writeChanges(), then transaction still open.
        // As already issued sql must at least mark the external transaction for rollback only.
        if (getLifecycle() == CommitTransactionPending) {
            if (hasModifications() || wasTransactionBegunPrematurely()) {
                rollbackTransaction(false);
                setWasTransactionBegunPrematurely(false);
            }
        } else if (wasTransactionBegunPrematurely() && (!isNestedUnitOfWork())) {
            rollbackTransaction();
            setWasTransactionBegunPrematurely(false);
        }
        if ((getMergeManager() != null) && (getMergeManager().getAcquiredLocks() != null) && (!getMergeManager().getAcquiredLocks().isEmpty())) {
            //may have unreleased cache locks because of a rollback... As some
            //locks may be acquired durring commit.
            getParent().getIdentityMapAccessorInstance().getWriteLockManager().releaseAllAcquiredLocks(getMergeManager());
            this.setMergeManager(null);
        }
        setDead();
        if(shouldClearForCloseOnRelease()) {
            clearForClose(true);
        }
        getParent().releaseUnitOfWork(this);
        getEventManager().postReleaseUnitOfWork();
    }

    /**
     * PUBLIC:
     * Empties the set of read-only classes.
     * It is illegal to call this method on nested UnitOfWork objects. A nested UnitOfWork
     * cannot have a subset of its parent's set of read-only classes.
     * Also removes classes which are read only because their descriptors are readonly
     */
    public void removeAllReadOnlyClasses() throws ValidationException {
        if (isNestedUnitOfWork()) {
            throw ValidationException.cannotRemoveFromReadOnlyClassesInNestedUnitOfWork();
        }
        getReadOnlyClasses().clear();
    }

    /**
     * ADVANCED:
     * Remove optimistic read lock from the object
     * See forceUpdateToVersionField(Object)
     */
    public void removeForceUpdateToVersionField(Object lockObject) {
        getOptimisticReadLockObjects().remove(lockObject);
    }

    /**
     * PUBLIC:
     * Removes a Class from the receiver's set of read-only classes.
     * It is illegal to try to send this method to a nested UnitOfWork.
     */
    public void removeReadOnlyClass(Class theClass) throws ValidationException {
        if (!canChangeReadOnlySet()) {
            throw ValidationException.cannotModifyReadOnlyClassesSetAfterUsingUnitOfWork();
        }
        if (isNestedUnitOfWork()) {
            throw ValidationException.cannotRemoveFromReadOnlyClassesInNestedUnitOfWork();
        }
        getReadOnlyClasses().remove(theClass);

    }

    /**
     * INTERNAL:
     * Used in the resume to reset the all clones collection
     */
    protected void resetAllCloneCollection() {
        this.allClones = null;
    }

    /**
     * PUBLIC:
     * Revert all changes made to any registered object.
     * Clear all deleted and new objects.
     * Revert should not be confused with release which it the normal compliment to commit.
     * Revert is more similar to commit and resume, however reverts all changes and resumes.
     * If you do not require to resume the unit of work release should be used instead.
     *
     * @see #commitAndResume()
     * @see #release()
     */
    public void revertAndResume() {
        if (isAfterWriteChangesButBeforeCommit()) {
            throw ValidationException.illegalOperationForUnitOfWorkLifecycle(getLifecycle(), "revertAndResume");
        }
        log(SessionLog.FINER, SessionLog.TRANSACTION, "revert_unit_of_work");

        MergeManager manager = new MergeManager(this);
        manager.mergeOriginalIntoWorkingCopy();
        manager.cascadeAllParts();
        for (Enumeration cloneEnum = getCloneMapping().keys(); cloneEnum.hasMoreElements();) {
            Object clone = cloneEnum.nextElement();

            // Revert each clone.
            manager.mergeChanges(clone, null);
            ClassDescriptor descriptor = this.getDescriptor(clone);

            //revert the tracking policy
            descriptor.getObjectChangePolicy().revertChanges(clone, descriptor, this, this.getCloneMapping());
        }

        // PERF: Avoid initialization of new objects if none.
        if (hasNewObjects()) {
            for (Enumeration cloneEnum = getNewObjectsCloneToOriginal().keys();
                     cloneEnum.hasMoreElements();) {
                Object clone = cloneEnum.nextElement();

                // De-register the object.
                getCloneMapping().remove(clone);
                
            }
            if (this.getUnitOfWorkChangeSet() != null){
                ((UnitOfWorkChangeSet)this.getUnitOfWorkChangeSet()).getNewObjectChangeSets().clear();
            }
        }

        // Clear new and deleted objects.
        setNewObjectsCloneToOriginal(null);
        setNewObjectsOriginalToClone(null);
        // Reset the all clones collection
        resetAllCloneCollection();
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        setObjectsDeletedDuringCommit(new HardIdentityHashtable());
        setDeletedObjects(new HardIdentityHashtable());
        setRemovedObjects(new HardIdentityHashtable());
        setUnregisteredNewObjects(new HardIdentityHashtable());
        log(SessionLog.FINER, SessionLog.TRANSACTION, "resume_unit_of_work");
    }

    /**
     * PUBLIC:
     * Revert the object's attributes from the parent.
     * This also reverts the object privately-owned parts.
     *
     * @return the object reverted.
     * @see #shallowRevertObject(Object)
     * @see #deepRevertObject(Object)
     */
    public Object revertObject(Object clone) {
        return revertObject(clone, MergeManager.CASCADE_PRIVATE_PARTS);
    }

    /**
     * INTERNAL:
     * Revert the object's attributes from the parent.
     * This uses merging to merge the object changes.
     */
    public Object revertObject(Object clone, int cascadeDepth) {
        if (clone == null) {
            return null;
        }

        //CR#2272
        logDebugMessage(clone, "revert");

        ClassDescriptor descriptor = getDescriptor(clone);
        ObjectBuilder builder = descriptor.getObjectBuilder();
        Object implementation = builder.unwrapObject(clone, this);

        MergeManager manager = new MergeManager(this);
        manager.mergeOriginalIntoWorkingCopy();
        manager.setCascadePolicy(cascadeDepth);
        try {
            manager.mergeChanges(implementation, null);
        } catch (RuntimeException exception) {
            return handleException(exception);
        }
        return clone;
    }

    /**
     * INTERNAL:
     * This is internal to the uow, transactions should not be used explictly in a uow.
     * The uow shares its parents transactions.
     */
    public void rollbackTransaction() throws DatabaseException {
        incrementProfile(SessionProfiler.UowRollbacks);
        getParent().rollbackTransaction();
    }

    /**
     * INTERNAL:
     * rollbackTransaction() with a twist for external transactions.
     * <p>
     * writeChanges() is called outside the JTA beforeCompletion(), so the
     * accompanying exception won't propogate up and cause a rollback by itself.
     * <p>
     * Instead must mark the transaction for rollback only here.
     * <p>
     * If internally started external transaction or no external transaction
     * can still rollback normally.
     * @param intendedToCommitTransaction whether we were inside a commit or just trying to
     * write out changes early.
     */
    protected void rollbackTransaction(boolean intendedToCommitTransaction) throws DatabaseException {
        if (!intendedToCommitTransaction && getParent().hasExternalTransactionController() && !getParent().wasJTSTransactionInternallyStarted()) {
            getParent().getExternalTransactionController().markTransactionForRollback();
        }
        rollbackTransaction();
    }

    /**
     * INTERNAL:
     * Scans the UnitOfWork identity map for conforming instances.
     * <p>
     * Later this method can be made recursive to check all parent units of
     * work also.
     * @param selectionCriteria must be cloned and specially prepared for conforming
     * @return HardIdentityHashtable to facilitate merging with conforming instances
     * returned from a query on the database.
     */
    public HardIdentityHashtable scanForConformingInstances(Expression selectionCriteria, Class referenceClass, AbstractRecord arguments, ObjectLevelReadQuery query) {
        // for bug 3568141 use the painstaking shouldTriggerIndirection if set
        InMemoryQueryIndirectionPolicy policy = query.getInMemoryQueryIndirectionPolicy();
        if (!policy.shouldTriggerIndirection()) {
            policy = new InMemoryQueryIndirectionPolicy(InMemoryQueryIndirectionPolicy.SHOULD_IGNORE_EXCEPTION_RETURN_NOT_CONFORMED);
        }
        HardIdentityHashtable indexedInterimResult = new HardIdentityHashtable();
        try {
            Vector fromCache = null;
            if (selectionCriteria != null) {
                // assume objects that have the compared relationship
                // untriggered do not conform as they have not been changed.
                // bug 2637555
                fromCache = getIdentityMapAccessor().getAllFromIdentityMap(selectionCriteria, referenceClass, arguments, policy);
                for (Enumeration fromCacheEnum = fromCache.elements();
                         fromCacheEnum.hasMoreElements();) {
                    Object object = fromCacheEnum.nextElement();
                    if (!isObjectDeleted(object)) {
                        indexedInterimResult.put(object, object);
                    }
                }
            }

            // Add any new objects that conform to the query.
            Vector newObjects = null;
            newObjects = getAllFromNewObjects(selectionCriteria, referenceClass, arguments, policy);
            for (Enumeration newObjectsEnum = newObjects.elements();
                     newObjectsEnum.hasMoreElements();) {
                Object object = newObjectsEnum.nextElement();
                if (!isObjectDeleted(object)) {
                    indexedInterimResult.put(object, object);
                }
            }
        } catch (QueryException exception) {
            if (getShouldThrowConformExceptions() == THROW_ALL_CONFORM_EXCEPTIONS) {
                throw exception;
            }
        }
        return indexedInterimResult;
    }

    /**
     * INTERNAL:
     * Used to set the collections of all objects in the UnitOfWork.
     * @param newUnregisteredExistingObjects oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected void setAllClonesCollection(HardIdentityHashtable objects) {
        this.allClones = objects;
    }

    /**
     * INTERNAL:
     * Set the clone mapping.
     * The clone mapping contains clone of all registered objects,
     * this is required to store the original state of the objects when registered

     * so that only what is changed will be commited to the database and the parent,
     * (this is required to support parralel unit of work).
     */
    protected void setCloneMapping(HardIdentityHashtable cloneMapping) {
        this.cloneMapping = cloneMapping;
    }

    /**
     * INTERNAL:
     * set UoW lifecycle state variable to DEATH
     */
    public void setDead() {
        setLifecycle(Death);
    }

    /**
     * INTERNAL:
     * The deleted objects stores any objects removed during the unit of work.
     * On commit they will all be removed from the database.
     */
    protected void setDeletedObjects(HardIdentityHashtable deletedObjects) {
        this.deletedObjects = deletedObjects;
    }

    /**
     * INTERNAL:
     * The life cycle tracks if the unit of work is active and is used for JTS.
     */
    protected void setLifecycle(int lifecycle) {
        this.lifecycle = lifecycle;
    }

    /**
     * INTERNAL:
     * A reference to the last used merge manager. This is used to track locked
     * objects.
     */
    public void setMergeManager(MergeManager mergeManager) {
        this.lastUsedMergeManager = mergeManager;
    }

    /**
     * INTERNAL:
     * The new objects stores any objects newly created during the unit of work.
     * On commit they will all be inserted into the database.
     */
    protected void setNewObjectsCloneToOriginal(HardIdentityHashtable newObjects) {
        this.newObjectsCloneToOriginal = newObjects;
    }

    /**
     * INTERNAL:
     * The new objects stores any objects newly created during the unit of work.
     * On commit they will all be inserted into the database.
     */
    protected void setNewObjectsOriginalToClone(HardIdentityHashtable newObjects) {
        this.newObjectsOriginalToClone = newObjects;
    }

    /**
     * INTERNAL:
     * Set the objects that have been deleted.
     */
    public void setObjectsDeletedDuringCommit(HardIdentityHashtable deletedObjects) {
        objectsDeletedDuringCommit = deletedObjects;
    }

    /**
     * INTERNAL:
     * Set the parent.
     * This is a unit of work if nested, otherwise a database session or client session.
     */
    public void setParent(AbstractSession parent) {
        this.parent = parent;
    }

    /**
     * INTERNAL:
     * set UoW lifecycle state variable to PENDING_MERGE
     */
    public void setPendingMerge() {
        setLifecycle(MergePending);
    }

    /**
     * INTERNAL:
     * Gives a new set of read-only classes to the receiver.
     * This set of classes given are checked that subclasses of a read-only class are also
     * in the read-only set provided.
     */
    public void setReadOnlyClasses(Vector classes) {
        this.readOnlyClasses = new Hashtable(classes.size() + 10);
        for (Enumeration enumtr = classes.elements(); enumtr.hasMoreElements();) {
            Class theClass = (Class)enumtr.nextElement();
            addReadOnlyClass(theClass);
        }
    }

    /**
     * INTERNAL:
     * The removed objects stores any newly registered objects removed during the nested unit of work.
     * On commit they will all be removed from the parent unit of work.
     */
    protected void setRemovedObjects(HardIdentityHashtable removedObjects) {
        this.removedObjects = removedObjects;
    }

    /**
     * INTERNAL:
     * Set if this UnitofWork should be resumed after the end of the transaction
     * Used when UnitOfWork is synchronized with external transaction control
     */
    public void setResumeUnitOfWorkOnTransactionCompletion(boolean resumeUnitOfWork){
        this.resumeOnTransactionCompletion = resumeUnitOfWork;
    }

    /**
     * INTERNAL:
     * True if the value holder for the joined attribute should be triggered.
     * Required by ejb30 fetch join.
     */
    public void setShouldCascadeCloneToJoinedRelationship(boolean shouldCascadeCloneToJoinedRelationship) {
        this.shouldCascadeCloneToJoinedRelationship = shouldCascadeCloneToJoinedRelationship;
    }

    /**
     * ADVANCED:
     * By default new objects are not cached until the exist on the database.
     * Occasionally if mergeClone is used on new objects and is required to allow multiple merges
     * on the same new object, then if the new objects are not cached, each mergeClone will be
     * interpretted as a different new object.
     * By setting new objects to be cached mergeClone can be performed multiple times before commit.
     * New objects cannot be cached unless they have a valid assigned primary key before being registered.
     * New object with non-null invalid primary keys such as 0 or '' can cause problems and should not be used with this option.
     */
    public void setShouldNewObjectsBeCached(boolean shouldNewObjectsBeCached) {
        this.shouldNewObjectsBeCached = shouldNewObjectsBeCached;
    }

    /**
     * ADVANCED:
     * By default deletes are performed last in a unit of work.
     * Sometimes you may want to have the deletes performed before other actions.
     */
    public void setShouldPerformDeletesFirst(boolean shouldPerformDeletesFirst) {
        this.shouldPerformDeletesFirst = shouldPerformDeletesFirst;
    }

    /**
     * ADVANCED:
     * Conforming queries can be set to provide different levels of detail about the
     * exceptions they encounter
     * There are three levels:
     * DO_NOT_THROW_CONFORM_EXCEPTIONS = 0;
     * THROW_ALL_CONFORM_EXCEPTIONS = 1;
     */
    public void setShouldThrowConformExceptions(int shouldThrowExceptions) {
        this.shouldThrowConformExceptions = shouldThrowExceptions;
    }

    /**
     * INTERNAL:
     * Set smart merge flag. This feature is used in WL to merge dependent values without SessionAccessor
     */
    public static void setSmartMerge(boolean option) {
        SmartMerge = option;
    }

    /**
     * INTERNAL:
     * Set isSynchronized flag to indicate that this session is a synchronized unit of work.
     */
    public void setSynchronized(boolean synched) {
        isSynchronized = synched;
    }

    /**
     * INTERNAL:
     * Sets the current UnitOfWork change set to be the one passed in.
     */
    public void setUnitOfWorkChangeSet(UnitOfWorkChangeSet unitOfWorkChangeSet) {
        this.unitOfWorkChangeSet = unitOfWorkChangeSet;
    }

    /**
     * INTERNAL:
     * Used to set the unregistered existing objects vector used when validation has been turned off.
     * @param newUnregisteredExistingObjects oracle.toplink.essentials.internal.helper.HardIdentityHashtable
     */
    protected void setUnregisteredExistingObjects(IdentityHashtable newUnregisteredExistingObjects) {
        unregisteredExistingObjects = newUnregisteredExistingObjects;
    }

    /**
     * INTERNAL:
     */
    protected void setUnregisteredNewObjects(IdentityHashtable newObjects) {
        unregisteredNewObjects = newObjects;
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public void setValidationLevel(int validationLevel) {
        this.validationLevel = validationLevel;
    }

    /**
     * INTERNAL:
     * Set a flag in the root UOW to indicate that a pess. locking or non-selecting SQL query was executed
     * and forced a transaction to be started.
     */
    public void setWasTransactionBegunPrematurely(boolean wasTransactionBegunPrematurely) {
        if (isNestedUnitOfWork()) {
            ((UnitOfWorkImpl)getParent()).setWasTransactionBegunPrematurely(wasTransactionBegunPrematurely);
        }
        this.wasTransactionBegunPrematurely = wasTransactionBegunPrematurely;
    }

    /**
     * PUBLIC:
     * Merge the attributes of the clone into the unit of work copy.
     * This can be used for objects that are returned from the client through
     * RMI serialization (or other serialization mechanisms), because the RMI object will
     * be a clone this will merge its attributes correctly to preserve object identity
     * within the unit of work and record its changes.
     *
     * Only direct attributes are merged.
     *
     * @return the registered version for the clone being merged.
     * @see #mergeClone(Object)
     * @see #deepMergeClone(Object)
     */
    public Object shallowMergeClone(Object rmiClone) {
        return mergeClone(rmiClone, MergeManager.NO_CASCADE);
    }

    /**
     * PUBLIC:
     * Revert the object's attributes from the parent.
     * This only reverts the object's direct attributes.
     *
     * @return the object reverted.
     * @see #revertObject(Object)
     * @see #deepRevertObject(Object)
     */
    public Object shallowRevertObject(Object clone) {
        return revertObject(clone, MergeManager.NO_CASCADE);
    }

    /**
     * ADVANCED:
     * Unregister the object with the unit of work.
     * This can be used to delete an object that was just created and is not yet persistent.
     * Delete object can also be used, but will result in inserting the object and then deleting it.
     * The method will only unregister the clone, none of its parts.
     */
    public void shallowUnregisterObject(Object clone) {
        unregisterObject(clone, DescriptorIterator.NoCascading);
    }

    /**
     * INTERNAL:
     * True if the value holder for the joined attribute should be triggered.
     * Required by ejb30 fetch join.
     */
    public boolean shouldCascadeCloneToJoinedRelationship() {
        return shouldCascadeCloneToJoinedRelationship;
    }

    /**
     * ADVANCED:
     * By default new objects are not cached until they exist on the database.
     * Occasionally if mergeClone is used on new objects and is required to allow multiple merges
     * on the same new object, then if the new objects are not cached, each mergeClone will be
     * interpretted as a different new object.
     * By setting new objects to be cached mergeClone can be performed multiple times before commit.
     * New objects cannot be cached unless they have a valid assigned primary key before being registered.
     * New object with non-null invalid primary keys such as 0 or '' can cause problems and should not be used with this option.
     */
    public boolean shouldNewObjectsBeCached() {
        return shouldNewObjectsBeCached;
    }

    /**
     * ADVANCED:
     * By default all objects are inserted and updated in the database before
     * any object is deleted. If this flag is set to true, deletes will be
     * performed before inserts and updates
     */
    public boolean shouldPerformDeletesFirst() {
        return shouldPerformDeletesFirst;
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public boolean shouldPerformFullValidation() {
        return getValidationLevel() == Full;
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public boolean shouldPerformNoValidation() {
        return getValidationLevel() == None;
    }

    /**
     * ADVANCED:
     * The unit of work performs validations such as,
     * ensuring multiple copies of the same object don't exist in the same unit of work,
     * ensuring deleted objects are not refered after commit,
     * ensures that objects from the parent cache are not refered in the unit of work cache.
     * The level of validation can be increased or decreased for debugging purposes or under
     * advanced situation where the application requires/desires to violate clone identity in the unit of work.
     * It is strongly suggested that clone identity not be violate in the unit of work.
     */
    public boolean shouldPerformPartialValidation() {
        return getValidationLevel() == Partial;
    }

    /**
     * INTERNAL:
     * Returns true if this UnitofWork should be resumed after the end of the transaction
     * Used when UnitOfWork is synchronized with external transaction control
     */
    public boolean shouldResumeUnitOfWorkOnTransactionCompletion(){
        return this.resumeOnTransactionCompletion;
    }
    
    /**
     * INTERNAL:
     * Store the ModifyAllQuery's from the UoW in the list. They are always
     * deferred to commit time
     */
    public void storeModifyAllQuery(DatabaseQuery query) {
        if (modifyAllQueries == null) {
            modifyAllQueries = new ArrayList();
        }

        modifyAllQueries.add(query);
    }

    /**
     * INTERNAL:
     * Store the deferred UpdateAllQuery's from the UoW in the list.
     */
    public void storeDeferredModifyAllQuery(DatabaseQuery query, AbstractRecord translationRow) {
        if (deferredModifyAllQueries == null) {
            deferredModifyAllQueries = new ArrayList();
        }
        deferredModifyAllQueries.add(new Object[]{query, translationRow});
    }

    /**
     * INTERNAL
     * Synchronize the clones and update their backup copies.
     * Called after commit and commit and resume.
     */
    public void synchronizeAndResume() {
        // For pessimistic locking all locks were released by commit.
        getPessimisticLockedObjects().clear();
        getProperties().remove(LOCK_QUERIES_PROPERTY);

        // find next power-of-2 size
        HardIdentityHashtable newCloneMapping = new HardIdentityHashtable(1 + getCloneMapping().size());

        for (Enumeration cloneEnum = getCloneMapping().keys(); cloneEnum.hasMoreElements();) {
            Object clone = cloneEnum.nextElement();

            // Do not add object that were deleted, what about private parts??
            if ((!isObjectDeleted(clone)) && (!getRemovedObjects().containsKey(clone))) {
                ClassDescriptor descriptor = getDescriptor(clone);
                ObjectBuilder builder = descriptor.getObjectBuilder();

                //Build backup clone for DeferredChangeDetectionPolicy or ObjectChangeTrackingPolicy,
                //but not for AttributeChangeTrackingPolicy
                descriptor.getObjectChangePolicy().revertChanges(clone, descriptor, this, newCloneMapping);
            }
        }
        setCloneMapping(newCloneMapping);

        if (hasObjectsDeletedDuringCommit()) {
            for (Enumeration removedObjects = getObjectsDeletedDuringCommit().keys();
                     removedObjects.hasMoreElements();) {
                Object removedObject = removedObjects.nextElement();
                getIdentityMapAccessor().removeFromIdentityMap((Vector)getObjectsDeletedDuringCommit().get(removedObject), removedObject.getClass());
            }
        }

        // New objects are not new anymore.
        // can not set multi clone for NestedUnitOfWork.CR#2015 - XC
        if (!isNestedUnitOfWork()) {
            //Need to move objects and clones from NewObjectsCloneToOriginal to CloneToOriginals for use in the continued uow
            if (hasNewObjects()) {
                for (Enumeration newClones = getNewObjectsCloneToOriginal().keys(); newClones.hasMoreElements();) {
                    Object newClone = newClones.nextElement();
                    getCloneToOriginals().put(newClone, getNewObjectsCloneToOriginal().get(newClone));
                }
            }
            setNewObjectsCloneToOriginal(null);
            setNewObjectsOriginalToClone(null);
        }

        //reset unitOfWorkChangeSet. Needed for ObjectChangeTrackingPolicy and DeferredChangeDetectionPolicy
        setUnitOfWorkChangeSet(null);

        // The collections of clones may change in the new UnitOfWork
        resetAllCloneCollection();
        // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
        setObjectsDeletedDuringCommit(new HardIdentityHashtable());
        setDeletedObjects(new HardIdentityHashtable());
        setRemovedObjects(new HardIdentityHashtable());
        setUnregisteredNewObjects(new HardIdentityHashtable());
        //Reset lifecycle
        this.lifecycle = Birth;
        this.isSynchronized = false;
    }

    /**
     * INTERNAL:
     * THis method is used to transition an object from the deleted objects list
     * to be simply be register.
     */
    protected void undeleteObject(Object object){
        getDeletedObjects().remove(object);
        if (getParent().isUnitOfWork()) {
            ((UnitOfWorkImpl)getParent()).undeleteObject(object);
        }
    }
    
    /**
     * PUBLIC:
     * Unregister the object with the unit of work.
     * This can be used to delete an object that was just created and is not yet persistent.
     * Delete object can also be used, but will result in inserting the object and then deleting it.
     * The method will only unregister the object and its privately owned parts
     */
    public void unregisterObject(Object clone) {
        unregisterObject(clone, DescriptorIterator.CascadePrivateParts);
    }

    /**
     * INTERNAL:
     * Unregister the object with the unit of work.
     * This can be used to delete an object that was just created and is not yet persistent.
     * Delete object can also be used, but will result in inserting the object and then deleting it.
     */
    public void unregisterObject(Object clone, int cascadeDepth) {
        // Allow register to be called with null and just return true
        if (clone == null) {
            return;
        }

        //CR#2272
        logDebugMessage(clone, "unregister");
        Object implementation = getDescriptor(clone).getObjectBuilder().unwrapObject(clone, this);

        // This define an inner class for process the itteration operation, don't be scared, its just an inner class.
        DescriptorIterator iterator = new DescriptorIterator() {
            public void iterate(Object object) {
                if (isClassReadOnly(object.getClass(), getCurrentDescriptor())) {
                    setShouldBreak(true);
                    return;
                }

                // Check if object exists in the IM.
                Vector primaryKey = getCurrentDescriptor().getObjectBuilder().extractPrimaryKeyFromObject(object, UnitOfWorkImpl.this);

                // If object exists in IM remove it from the IM and also from clone mapping.
                getIdentityMapAccessorInstance().removeFromIdentityMap(primaryKey, object.getClass(), getCurrentDescriptor());
                getCloneMapping().remove(object);

                // Remove object from the new object cache
                // PERF: Avoid initialization of new objects if none.
                if (hasNewObjects()) {
                    Object original = getNewObjectsCloneToOriginal().remove(object);
                    if (original != null) {
                        getNewObjectsOriginalToClone().remove(original);
                    }
                }
            }
        };

        iterator.setSession(this);
        iterator.setCascadeDepth(cascadeDepth);
        iterator.startIterationOn(implementation);
    }

    /**
     * INTERNAL:
     * This method is used internally to update the tracked objects if required
     */
    public void updateChangeTrackersIfRequired(Object objectToWrite, ObjectChangeSet changeSetToWrite, UnitOfWorkImpl uow, ClassDescriptor descriptor) {
        //this is a no op in this unitOfWork Class see subclasses for implementation.
    }

    /**
     * ADVANCED:
     * This can be used to help debugging an object-space corruption.
     * An object-space corruption is when your application has incorrectly related a clone to an original object.
     * This method will validate that all registered objects are in a correct state and throw
     * an error if not, it will contain the full stack of object references in the error message.
     * If you call this method after each register or change you perform it will pin-point where the error was made.
     */
    public void validateObjectSpace() {
        log(SessionLog.FINER, SessionLog.TRANSACTION, "validate_object_space");
        // This define an inner class for process the itteration operation, don't be scared, its just an inner class.
        DescriptorIterator iterator = new DescriptorIterator() {
            public void iterate(Object object) {
                try {
                    if (isClassReadOnly(object.getClass(), getCurrentDescriptor())) {
                        setShouldBreak(true);
                        return;
                    } else {
                        getBackupClone(object);
                    }
                } catch (TopLinkException exception) {
                    log(SessionLog.FINEST, SessionLog.TRANSACTION, "stack_of_visited_objects_that_refer_to_the_corrupt_object", getVisitedStack());
                    log(SessionLog.FINER, SessionLog.TRANSACTION, "corrupt_object_referenced_through_mapping", getCurrentMapping());
                    throw exception;
                }
            }
        };

        iterator.setSession(this);
        for (Enumeration clonesEnum = getCloneMapping().keys(); clonesEnum.hasMoreElements();) {
            iterator.startIterationOn(clonesEnum.nextElement());
        }
    }

    /**
     * INTERNAL:
     * Indicates if a transaction was begun by a pessimistic locking or non-selecting query.
     * Traverse to the root UOW to get value.
     */

    // * 2.5.1.8 Nov 17, 2000 JED
    // * Prs 25751 Changed to make this method public
    public boolean wasTransactionBegunPrematurely() {
        if (isNestedUnitOfWork()) {
            return ((UnitOfWorkImpl)getParent()).wasTransactionBegunPrematurely();
        }
        return wasTransactionBegunPrematurely;
    }

    /**
     * ADVANCED: Writes all changes now before commit().
     * The commit process will begin and all changes will be written out to the datastore, but the datastore transaction will not
     * be committed, nor will changes be merged into the global cache.
     * <p>
     * A subsequent commit (on UnitOfWork or global transaction) will be required to finalize the commit process.
     * <p>
     * As the commit process has begun any attempt to register objects, or execute object-level queries will
     * generate an exception. Report queries, non-caching queries, and data read/modify queries are allowed.
     * <p>
     * On exception any global transaction will be rolled back or marked rollback only. No recovery of this UnitOfWork will be possible.
     * <p>
     * Can only be called once. It can not be used to write out changes in an incremental fashion.
     * <p>
     * Use to partially commit a transaction outside of a JTA transaction's callbacks. Allows you to get back any exception directly.
     * <p>
     * Use to commit a UnitOfWork in two stages.
     */
    public void writeChanges() {
        if (!isActive()) {
            throw ValidationException.inActiveUnitOfWork("writeChanges");
        }
        if (isAfterWriteChangesButBeforeCommit()) {
            throw ValidationException.cannotWriteChangesTwice();
        }
        if (isNestedUnitOfWork()) {
            throw ValidationException.writeChangesOnNestedUnitOfWork();
        }
        log(SessionLog.FINER, SessionLog.TRANSACTION, "begin_unit_of_work_commit");
        getEventManager().preCommitUnitOfWork();
        setLifecycle(CommitPending);
        try {
            commitToDatabaseWithChangeSet(false);
        } catch (RuntimeException e) {
            setLifecycle(WriteChangesFailed);
            throw e;
        }
        setLifecycle(CommitTransactionPending);
    }

    /**
     * INTERNAL:
     * This method notifies the accessor that a particular sets of writes has
     * completed. This notification can be used for such thing as flushing the
     * batch mechanism
     */
    public void writesCompleted() {
        getParent().writesCompleted();
    }
    /**
     * Converts the configuration entry into the ReferenceMode enum.
     * @param parent the AbstractSession - needed for configuration.
     * @return the converted Enum (ReferenceMode).
     */
    private ReferenceMode getReferenceMode(AbstractSession parent) {
        String referenceModeAsString = PropertiesHandler.getSessionPropertyValueLogDebug(TopLinkProperties.PERSISTENCE_CONTEXT_REFERENCE_MODE, parent);
        if(referenceModeAsString == null)
            return ReferenceMode.HARD;
        return ReferenceMode.valueOf(referenceModeAsString.toUpperCase());
    }

    /**
     * log the message and debug info if option is set. (reduce the duplicate codes)
     */
    private void logDebugMessage(Object object, String debugMessage) {
        log(SessionLog.FINEST, SessionLog.TRANSACTION, debugMessage, object);
    }

    /**
     * INTERNAL:
     * Return the registered working copy from the unit of work identity map.
     * If not registered in the unit of work yet, return null
     */
    public Object getWorkingCopyFromUnitOfWorkIdentityMap(Object object, Vector primaryKey) {
        //return the descriptor of the passed object
        ClassDescriptor descriptor = getDescriptor(object);
        if (descriptor == null) {
            throw DescriptorException.missingDescriptor(object.getClass().toString());
        }

        //aggregated object cannot be registered directly, but through the parent owning object.
        if (descriptor.isAggregateDescriptor() || descriptor.isAggregateCollectionDescriptor()) {
            throw ValidationException.cannotRegisterAggregateObjectInUnitOfWork(object.getClass());
        }

        // Check if the working copy is again being registered in which case we return the same working copy
        Object registeredObject = getCloneMapping().get(object);
        if (registeredObject != null) {
            return object;
        }

        //check the unit of work cache first to see if already registered.
        Object objectFromUOWCache = getIdentityMapAccessorInstance().getIdentityMapManager().getFromIdentityMap(primaryKey, object.getClass(), descriptor);
        if (objectFromUOWCache != null) {
            // Has already been cloned, return the working clone from the IM rather than the passed object.
            return objectFromUOWCache;
        }

        //not found, return null
        return null;
    }

    /**
     * INTERNAL:
     */
    public IdentityHashtable getPessimisticLockedObjects() {
        if (pessimisticLockedObjects == null) {
            // 2612538 - the default size of HardIdentityHashtable (32) is appropriate
            pessimisticLockedObjects = new HardIdentityHashtable();
        }
        return pessimisticLockedObjects;
    }

    /**
     * INTERNAL:
     */
    public void addPessimisticLockedClone(Object clone) {
        log(SessionLog.FINEST, SessionLog.TRANSACTION, "tracking_pl_object", clone, new Integer(this.hashCode()));
        getPessimisticLockedObjects().put(clone, clone);
    }

    /**
      * INTERNAL:
      */
    public boolean isPessimisticLocked(Object clone) {
        return getPessimisticLockedObjects().containsKey(clone);
    }

    /**
     * INTERNAL:
     * True if either DataModifyQuery or ModifyAllQuery was executed.
     * In absense of transaction the query execution starts one, therefore
     * the flag may only be true in transaction, it's reset on commit or rollback.
     */
    public void setWasNonObjectLevelModifyQueryExecuted(boolean wasNonObjectLevelModifyQueryExecuted) {
        this.wasNonObjectLevelModifyQueryExecuted = wasNonObjectLevelModifyQueryExecuted;
    }
    
    /**
     * INTERNAL:
     * True if either DataModifyQuery or ModifyAllQuery was executed.
     */
    public boolean wasNonObjectLevelModifyQueryExecuted() {
        return wasNonObjectLevelModifyQueryExecuted;
    }
    
    /**
      * INTERNAL:
      * Indicates whether readObject should return the object read from the db
      * in case there is no object in uow cache (as opposed to fetching the object from
      * parent's cache). Note that wasNonObjectLevelModifyQueryExecuted()==true implies inTransaction()==true.
      */
    public boolean shouldReadFromDB() {
        return wasNonObjectLevelModifyQueryExecuted();
    }

    /**
     * INTERNAL:
     * This method will clear all registered objects from this UnitOfWork.
     * If parameter value is 'true' then the cache(s) are cleared, too.
     */
    public void clear(boolean shouldClearCache) {
        this.cloneToOriginals = null;
        this.cloneMapping = new HardIdentityHashtable();
        this.newObjectsCloneToOriginal = null;
        this.newObjectsOriginalToClone = null;
        this.deletedObjects = null;
        this.allClones = null;
        this.objectsDeletedDuringCommit = null;
        this.removedObjects = null;
        this.unregisteredNewObjects = null;
        this.unregisteredExistingObjects = null;
        this.newAggregates = null;
        this.unitOfWorkChangeSet = null;
        this.pessimisticLockedObjects = null;
        this.optimisticReadLockObjects = null;
        if(shouldClearCache) {
            this.getIdentityMapAccessor().initializeIdentityMaps();
            if (this.getParent() instanceof IsolatedClientSession) {
                this.getParent().getIdentityMapAccessor().initializeIdentityMaps();
            }
        }
    }
    
    /**
     * INTERNAL:
     * Call this method if the uow will no longer used for comitting transactions:
     * all the changes sets will be dereferenced, and (optionally) the cache cleared.
     * If the uow is not released, but rather kept around for ValueHolders, then identity maps shouldn't be cleared:
     * the parameter value should be 'false'. The lifecycle set to Birth so that uow ValueHolder still could be used.
     * Alternatively, if called from release method then everything should go and therefore parameter value should be 'true'.
     * In this case lifecycle won't change - uow.release (optionally) calls this method when it (uow) is already dead.
     * The reason for calling this method from release is to free maximum memory right away:
     * the uow might still be referenced by objects using UOWValueHolders (though they shouldn't be around
     * they still might).
     */
    public void clearForClose(boolean shouldClearCache) {
        clear(shouldClearCache);
        if(isActive()) {
            //Reset lifecycle
            this.lifecycle = Birth;
            this.isSynchronized = false;
        }
    }
    
    /**
     * INTERNAL:
     * Indicates whether clearForClose methor should be called by release method.
     */
    public boolean shouldClearForCloseOnRelease() {
        return false;
    }
 
    public ReferenceMode getReferenceMode() {
        return referenceMode;
    }
}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package oracle.toplink.essentials.internal.helper;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 *
 * @author adam-bien.com
 */
public class WeakKeyAndValueIdentityHashtable extends IdentityHashtable{
       static final long serialVersionUID = 1421746759512286392L;
    
    // the default initial capacity
    static final int DEFAULT_INITIAL_CAPACITY = 32;

    // the maximum capacity.
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // the loadFactor used when none specified in constructor.
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /** An "enum" of Enumeration types. */
    static final int KEYS = 0;

    /** An "enum" of Enumeration types. */
    static final int ELEMENTS = 1;
    private static EmptyEnumerator emptyEnumerator = new EmptyEnumerator();
    protected transient WeakKeyEntry[] entries;// internal array of Entry's
    protected transient int count = 0;
    protected int threshold = 0;
    protected float loadFactor = 0;

    /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with the given
     * initial capacity and the given loadFactor.
     *
     * @param initialCapacity the initial capacity of this
     * <tt>HardIdentityHashtable</tt>.
     * @param loadFactor the loadFactor of the <tt>HardIdentityHashtable</tt>.
     * @throws IllegalArgumentException if the initial capacity is less
     * than zero, or if the loadFactor is nonpositive.
     */
    public WeakKeyAndValueIdentityHashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal initialCapacity: " + initialCapacity);
        }
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;
        }
        if ((loadFactor <= 0) || Float.isNaN(loadFactor)) {
            throw new IllegalArgumentException("Illegal loadFactor: " + loadFactor);
        }

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity) {
            capacity <<= 1;
        }
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        entries = new WeakKeyEntry[capacity];
    }

   /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with the given
     * initial capacity and a default loadFactor of <tt>0.75</tt>.
     *
     * @param initialCapacity the initial capacity of the
     * <tt>HardIdentityHashtable</tt>.
     * @throws <tt>IllegalArgumentException</tt> if the initial capacity is less
     * than zero.
     */
    public WeakKeyAndValueIdentityHashtable(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with a default initial
     * capacity of <tt>32</tt> and a loadfactor of <tt>0.75</tt>.
     */
    public WeakKeyAndValueIdentityHashtable() {
        loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        entries = new WeakKeyEntry[DEFAULT_INITIAL_CAPACITY];
    }
    
    /**
     * Removes all of the mappings from this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized void clear() {
        if (count > 0) {
            WeakKeyEntry[] copyOfEntries = entries;
            for (int i = copyOfEntries.length; --i >= 0;) {
                copyOfEntries[i] = null;
            }
            count = 0;
        }
    }

    /**
     * Returns a shallow copy of this <tt>HardIdentityHashtable</tt> (the
     * elements are not cloned).
     *
     * @return a shallow copy of this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized Object clone() {
            WeakKeyEntry[] copyOfEntries = entries;
            WeakKeyAndValueIdentityHashtable clone = (WeakKeyAndValueIdentityHashtable)super.clone();
            clone.entries = new WeakKeyEntry[copyOfEntries.length];
            for (int i = copyOfEntries.length; i-- > 0;) {
                clone.entries[i] = (copyOfEntries[i] != null) ? (WeakKeyEntry)copyOfEntries[i].clone() : null;
            }
            return clone;
    }

    /**
     * Returns <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains
     * the given object. Equality is tested by the equals() method.
     *
     * @param obj the object to find.
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains
     * obj.
     * @throws <tt>NullPointerException</tt> if obj is null</tt>.
     */
    public synchronized boolean contains(Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }

        WeakKeyEntry[] copyOfEntries = entries;
        for (int i = copyOfEntries.length; i-- > 0;) {
            for (WeakKeyEntry e = copyOfEntries[i]; e != null; e = e.next) {
                if (e.getValue().equals(obj)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains a
     * mapping for the given referenceOrKey. Equality is tested by reference.
     *
     * @param referenceOrKey object to be used as a referenceOrKey into this
     * <tt>HardIdentityHashtable</tt>.
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains a
     * mapping for referenceOrKey.
     */
    public synchronized boolean containsKey(Object key) {
        WeakKeyEntry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyEntry e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.getKey() == key) {
                return true;
            }
        }
        return false;
    }

    public synchronized Enumeration elements() {
        if (count == 0) {
            return emptyEnumerator;
        } else {
            return new Enumerator(ELEMENTS);
        }
    }

    /**
     * Returns the value to which the given referenceOrKey is mapped in this
     * <tt>HardIdentityHashtable</tt>. Returns <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> contains no mapping for this referenceOrKey.
     *
     * @return the value to which this <tt>HardIdentityHashtable</tt> maps the
     * given referenceOrKey.
     * @param referenceOrKey referenceOrKey whose associated value is to be returned.
     */
    public synchronized Object get(Object key) {
        WeakKeyEntry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyEntry e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.getKey() == key) {
                return e.getValue();
            }
        }
        return null;
    }

    /**
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> is empty.
     */
    public boolean isEmpty() {
        return (count == 0);
    }

    public synchronized Enumeration keys() {
        if (count == 0) {
            return emptyEnumerator;
        } else {
            return new Enumerator(KEYS);
        }
    }

    /**
     * Associate the given object with the given referenceOrKey in this
     * <tt>HardIdentityHashtable</tt>, replacing any existing mapping.
     *
     * @param referenceOrKey referenceOrKey to map to given object.
     * @param obj object to be associated with referenceOrKey.
     * @return the previous object for referenceOrKey or <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> did not have one.
     * @throws <tt>NullPointerException</tt> if obj is null</tt>.
     */
    public synchronized Object put(Object key, Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }

        WeakKeyEntry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyEntry e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.getKey() == key) {
                Object old = e.getValue();
                e.setValue(obj);
                return old;
            }
        }

        if (count >= threshold) {
            rehash();
            copyOfEntries = entries;
            index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        }
        WeakKeyEntry e = new WeakKeyEntry(hash, key, obj, copyOfEntries[index]);
        copyOfEntries[index] = e;
        count++;
        return null;
    }

    /**
     * INTERNAL:
     * Re-builds the internal array of WeakKeyEntry's with a larger capacity.
     * This method is called automatically when the number of objects in this
     * HardIdentityHashtable exceeds its current threshold.
     */
    private void rehash() {
        int oldCapacity = entries.length;
        WeakKeyEntry[] oldEntries = entries;
        int newCapacity = (oldCapacity * 2) + 1;
        WeakKeyEntry[] newEntries = new WeakKeyEntry[newCapacity];
        threshold = (int)(newCapacity * loadFactor);
        entries = newEntries;
        for (int i = oldCapacity; i-- > 0;) {
            for (WeakKeyEntry old = oldEntries[i]; old != null;) {
                WeakKeyEntry e = old;
                old = old.next;
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newEntries[index];
                newEntries[index] = e;
            }
        }
    }

    /**
     * Removes the mapping (referenceOrKey and its corresponding value) from this
     * <tt>HardIdentityHashtable</tt>, if present.
     *
     * @param referenceOrKey referenceOrKey whose mapping is to be removed from the map.
     * @return the previous object for referenceOrKey or <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> did not have one.
     */
    public synchronized Object remove(Object key) {
        WeakKeyEntry[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyEntry e = copyOfEntries[index], prev = null; e != null; prev = e, e = e.next) {
            if (e.getKey() == key) {
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    copyOfEntries[index] = e.next;
                }
                count--;
                return e.getValue();
            }
        }
        return null;
    }

    /**
     * @return the size of this <tt>HardIdentityHashtable</tt>.
     */
    public int size() {
        return count;
    }

    /**
     * Return the string representation of this <tt>HardIdentityHashtable</tt>.
     *
     * @return the string representation of this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized String toString() {
        int max = size() - 1;
        StringBuffer buf = new StringBuffer();
        Enumeration k = keys();
        Enumeration e = elements();
        buf.append("{");
        for (int i = 0; i <= max; i++) {
            String s1 = k.nextElement().toString();
            String s2 = e.nextElement().toString();
            buf.append(s1 + "=" + s2);
            if (i < max) {
                buf.append(", ");
            }
        }
        buf.append("}");
        return buf.toString();
    }

    /**
     * Serialize the state of this <tt>HardIdentityHashtable</tt> to a stream.
     *
     * @serialData The <i>capacity</i> of the <tt>HardIdentityHashtable</tt>
     * (the length of the bucket array) is emitted (int), followed by the
     * <i>size</i> of the <tt>HardIdentityHashtable</tt>, followed by the
     * referenceOrKey-value mappings (in no particular order).
     */
    private void writeObject(ObjectOutputStream s) throws IOException {
        // Write out the threshold, loadfactor (and any hidden 'magic' stuff).
        s.defaultWriteObject();

        // Write out number of buckets
        s.writeInt(entries.length);
        // Write out count
        s.writeInt(count);
        // Write out contents
        for (int i = entries.length - 1; i >= 0; i--) {
            WeakKeyEntry entry = entries[i];
            while (entry != null) {
                s.writeObject(entry.getKey());
                s.writeObject(entry.getValue());
                entry = entry.next;
            }
        }
    }

    /**
     * Deserialize the <tt>HardIdentityHashtable</tt> from a stream.
     */
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        // Read in the threshold, loadfactor (and any hidden 'magic' stuff).
        s.defaultReadObject();

        // Read in number of buckets and allocate the bucket array;
        int numBuckets = s.readInt();
        entries = new WeakKeyEntry[numBuckets];
        // Read in size (count)
        int size = s.readInt();

        // Read the mappings and add to the TopLinkIdentityHashMap
        for (int i = 0; i < size; i++) {
            Object key = s.readObject();
            Object value = s.readObject();
            put(key, value);
        }
    }

    private static class EmptyEnumerator implements Enumeration {
        EmptyEnumerator() {
        }

        public boolean hasMoreElements() {
            return false;
        }

        public Object nextElement() {
            throw new NoSuchElementException();
        }
    }

    class Enumerator implements Enumeration {
        int enumeratorType;
        int index;
        WeakKeyEntry entry;

        Enumerator(int enumeratorType) {
            this.enumeratorType = enumeratorType;
            index = WeakKeyAndValueIdentityHashtable.this.entries.length;
        }

        public boolean hasMoreElements() {
            if (entry != null) {
                return true;
            }
            while (index-- > 0) {
                if ((entry = WeakKeyAndValueIdentityHashtable.this.entries[index]) != null) {
                    return true;
                }
            }
            return false;
        }

        public Object nextElement() {
            if (entry == null) {
                while ((index-- > 0) && ((entry = WeakKeyAndValueIdentityHashtable.this.entries[index]) == null)) {
                    ;
                }
            }
            if (entry != null) {
                WeakKeyEntry e = entry;
                entry = e.next;
                if (enumeratorType == KEYS) {
                    return e.getKey();
                } else {
                    return e.getValue();
                }
            }
            throw new NoSuchElementException("IdentityHashtable.Enumerator");
        }
    }

    
    private static class WeakKeyEntry{
        int hash;
        private Object referenceOrKey;
        Object value;
        WeakKeyEntry next;

        WeakKeyEntry(int hash, Object key, Object value, WeakKeyEntry next) {
            this.hash = hash;
            this.referenceOrKey = wrap(key);
            this.value = value;
            this.next = next;
        }

        protected Object clone() {
            return new WeakKeyEntry(hash, unwrap(referenceOrKey), value, ((next == null) ? null : (WeakKeyEntry)next.clone()));
        }

        public Object getKey() {
            return unwrap(referenceOrKey);
        }

        public Object getValue() {
            return value;
        }

        public Object setValue(Object value) {
            Object oldValue = this.value;
            this.value = value;
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof WeakKeyEntry)) {
                return false;
            }

            WeakKeyEntry e = (WeakKeyEntry)o;
            return (referenceOrKey == unwrap(e.getKey()) && (value == null) ? (e.getValue() == null) : value.equals(e.getValue()));
        }

        public int hashCode() {
            return hash ^ ((value == null) ? 0 : value.hashCode());
        }

        public String toString() {
            return referenceOrKey + "=" + unwrap(value);
        }
        
        private Object unwrap(Object referent){
            WeakReference wrapper = (WeakReference) referent;
            return wrapper.get();
        }
        
        private Object wrap(Object referent){
            //already wrapped, nothing to do...
            if(referent instanceof WeakReference){
                return referent;
            }
            return new WeakReference(referent);
        }
    }
    

}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package oracle.toplink.essentials.internal.helper;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.ref.WeakReference;
import java.util.Enumeration;
import java.util.NoSuchElementException;

/**
 *
 * @author adam-bien.com
 */
public class WeakKeyIdentityHashtable extends IdentityHashtable{
       static final long serialVersionUID = 1421746759512286392L;
    
    // the default initial capacity
    static final int DEFAULT_INITIAL_CAPACITY = 32;

    // the maximum capacity.
    static final int MAXIMUM_CAPACITY = 1 << 30;

    // the loadFactor used when none specified in constructor.
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /** An "enum" of Enumeration types. */
    static final int KEYS = 0;

    /** An "enum" of Enumeration types. */
    static final int ELEMENTS = 1;
    private static EmptyEnumerator emptyEnumerator = new EmptyEnumerator();
    protected transient WeakKeyAndValue[] entries;// internal array of Entry's
    protected transient int count = 0;
    protected int threshold = 0;
    protected float loadFactor = 0;

    /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with the given
     * initial capacity and the given loadFactor.
     *
     * @param initialCapacity the initial capacity of this
     * <tt>HardIdentityHashtable</tt>.
     * @param loadFactor the loadFactor of the <tt>HardIdentityHashtable</tt>.
     * @throws IllegalArgumentException if the initial capacity is less
     * than zero, or if the loadFactor is nonpositive.
     */
    public WeakKeyIdentityHashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0) {
            throw new IllegalArgumentException("Illegal initialCapacity: " + initialCapacity);
        }
        if (initialCapacity > MAXIMUM_CAPACITY) {
            initialCapacity = MAXIMUM_CAPACITY;
        }
        if ((loadFactor <= 0) || Float.isNaN(loadFactor)) {
            throw new IllegalArgumentException("Illegal loadFactor: " + loadFactor);
        }

        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity) {
            capacity <<= 1;
        }
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        entries = new WeakKeyAndValue[capacity];
    }

   /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with the given
     * initial capacity and a default loadFactor of <tt>0.75</tt>.
     *
     * @param initialCapacity the initial capacity of the
     * <tt>HardIdentityHashtable</tt>.
     * @throws <tt>IllegalArgumentException</tt> if the initial capacity is less
     * than zero.
     */
    public WeakKeyIdentityHashtable(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    /**
     * Constructs a new <tt>HardIdentityHashtable</tt> with a default initial
     * capacity of <tt>32</tt> and a loadfactor of <tt>0.75</tt>.
     */
    public WeakKeyIdentityHashtable() {
        loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        entries = new WeakKeyAndValue[DEFAULT_INITIAL_CAPACITY];
    }
    
    /**
     * Removes all of the mappings from this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized void clear() {
        if (count > 0) {
            WeakKeyAndValue[] copyOfEntries = entries;
            for (int i = copyOfEntries.length; --i >= 0;) {
                copyOfEntries[i] = null;
            }
            count = 0;
        }
    }

    /**
     * Returns a shallow copy of this <tt>HardIdentityHashtable</tt> (the
     * elements are not cloned).
     *
     * @return a shallow copy of this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized Object clone() {
            WeakKeyAndValue[] copyOfEntries = entries;
            WeakKeyIdentityHashtable clone = (WeakKeyIdentityHashtable)super.clone();
            clone.entries = new WeakKeyAndValue[copyOfEntries.length];
            for (int i = copyOfEntries.length; i-- > 0;) {
                clone.entries[i] = (copyOfEntries[i] != null) ? (WeakKeyAndValue)copyOfEntries[i].clone() : null;
            }
            return clone;
    }

    /**
     * Returns <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains
     * the given object. Equality is tested by the equals() method.
     *
     * @param obj the object to find.
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains
     * obj.
     * @throws <tt>NullPointerException</tt> if obj is null</tt>.
     */
    public synchronized boolean contains(Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }

        WeakKeyAndValue[] copyOfEntries = entries;
        for (int i = copyOfEntries.length; i-- > 0;) {
            for (WeakKeyAndValue e = copyOfEntries[i]; e != null; e = e.next) {
                if (e.getValue().equals(obj)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Returns <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains a
     * mapping for the given referenceOrKey. Equality is tested by reference.
     *
     * @param referenceOrKey object to be used as a referenceOrKey into this
     * <tt>HardIdentityHashtable</tt>.
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> contains a
     * mapping for referenceOrKey.
     */
    public synchronized boolean containsKey(Object key) {
        WeakKeyAndValue[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyAndValue e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.getKey() == key) {
                return true;
            }
        }
        return false;
    }

    public synchronized Enumeration elements() {
        if (count == 0) {
            return emptyEnumerator;
        } else {
            return new Enumerator(ELEMENTS);
        }
    }

    /**
     * Returns the referenceOrValue to which the given referenceOrKey is mapped in this
     * <tt>HardIdentityHashtable</tt>. Returns <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> contains no mapping for this referenceOrKey.
     *
     * @return the referenceOrValue to which this <tt>HardIdentityHashtable</tt> maps the
     * given referenceOrKey.
     * @param referenceOrKey referenceOrKey whose associated referenceOrValue is to be returned.
     */
    public synchronized Object get(Object key) {
        WeakKeyAndValue[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyAndValue e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.getKey() == key) {
                return e.getValue();
            }
        }
        return null;
    }

    /**
     * @return <tt>true</tt> if this <tt>HardIdentityHashtable</tt> is empty.
     */
    public boolean isEmpty() {
        return (count == 0);
    }

    public synchronized Enumeration keys() {
        if (count == 0) {
            return emptyEnumerator;
        } else {
            return new Enumerator(KEYS);
        }
    }

    /**
     * Associate the given object with the given referenceOrKey in this
     * <tt>HardIdentityHashtable</tt>, replacing any existing mapping.
     *
     * @param referenceOrKey referenceOrKey to map to given object.
     * @param obj object to be associated with referenceOrKey.
     * @return the previous object for referenceOrKey or <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> did not have one.
     * @throws <tt>NullPointerException</tt> if obj is null</tt>.
     */
    public synchronized Object put(Object key, Object obj) {
        if (obj == null) {
            throw new NullPointerException();
        }

        WeakKeyAndValue[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyAndValue e = copyOfEntries[index]; e != null; e = e.next) {
            if (e.getKey() == key) {
                Object old = e.getValue();
                e.setValue(obj);
                return old;
            }
        }

        if (count >= threshold) {
            rehash();
            copyOfEntries = entries;
            index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        }
        WeakKeyAndValue e = new WeakKeyAndValue(hash, key, obj, copyOfEntries[index]);
        copyOfEntries[index] = e;
        count++;
        return null;
    }

    /**
     * INTERNAL:
     * Re-builds the internal array of WeakKeyEntry's with a larger capacity.
     * This method is called automatically when the number of objects in this
     * HardIdentityHashtable exceeds its current threshold.
     */
    private void rehash() {
        int oldCapacity = entries.length;
        WeakKeyAndValue[] oldEntries = entries;
        int newCapacity = (oldCapacity * 2) + 1;
        WeakKeyAndValue[] newEntries = new WeakKeyAndValue[newCapacity];
        threshold = (int)(newCapacity * loadFactor);
        entries = newEntries;
        for (int i = oldCapacity; i-- > 0;) {
            for (WeakKeyAndValue old = oldEntries[i]; old != null;) {
                WeakKeyAndValue e = old;
                old = old.next;
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;
                e.next = newEntries[index];
                newEntries[index] = e;
            }
        }
    }

    /**
     * Removes the mapping (referenceOrKey and its corresponding referenceOrValue) from this
     * <tt>HardIdentityHashtable</tt>, if present.
     *
     * @param referenceOrKey referenceOrKey whose mapping is to be removed from the map.
     * @return the previous object for referenceOrKey or <tt>null</tt> if this
     * <tt>HardIdentityHashtable</tt> did not have one.
     */
    public synchronized Object remove(Object key) {
        WeakKeyAndValue[] copyOfEntries = entries;
        int hash = System.identityHashCode(key);
        int index = (hash & 0x7FFFFFFF) % copyOfEntries.length;
        for (WeakKeyAndValue e = copyOfEntries[index], prev = null; e != null; prev = e, e = e.next) {
            if (e.getKey() == key) {
                if (prev != null) {
                    prev.next = e.next;
                } else {
                    copyOfEntries[index] = e.next;
                }
                count--;
                return e.getValue();
            }
        }
        return null;
    }

    /**
     * @return the size of this <tt>HardIdentityHashtable</tt>.
     */
    public int size() {
        return count;
    }

    /**
     * Return the string representation of this <tt>HardIdentityHashtable</tt>.
     *
     * @return the string representation of this <tt>HardIdentityHashtable</tt>.
     */
    public synchronized String toString() {
        int max = size() - 1;
        StringBuffer buf = new StringBuffer();
        Enumeration k = keys();
        Enumeration e = elements();
        buf.append("{");
        for (int i = 0; i <= max; i++) {
            String s1 = k.nextElement().toString();
            String s2 = e.nextElement().toString();
            buf.append(s1 + "=" + s2);
            if (i < max) {
                buf.append(", ");
            }
        }
        buf.append("}");
        return buf.toString();
    }

    /**
     * Serialize the state of this <tt>HardIdentityHashtable</tt> to a stream.
     *
     * @serialData The <i>capacity</i> of the <tt>HardIdentityHashtable</tt>
     * (the length of the bucket array) is emitted (int), followed by the
     * <i>size</i> of the <tt>HardIdentityHashtable</tt>, followed by the
     * referenceOrKey-referenceOrValue mappings (in no particular order).
     */
    private void writeObject(ObjectOutputStream s) throws IOException {
        // Write out the threshold, loadfactor (and any hidden 'magic' stuff).
        s.defaultWriteObject();

        // Write out number of buckets
        s.writeInt(entries.length);
        // Write out count
        s.writeInt(count);
        // Write out contents
        for (int i = entries.length - 1; i >= 0; i--) {
            WeakKeyAndValue entry = entries[i];
            while (entry != null) {
                s.writeObject(entry.getKey());
                s.writeObject(entry.getValue());
                entry = entry.next;
            }
        }
    }

    /**
     * Deserialize the <tt>HardIdentityHashtable</tt> from a stream.
     */
    private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
        // Read in the threshold, loadfactor (and any hidden 'magic' stuff).
        s.defaultReadObject();

        // Read in number of buckets and allocate the bucket array;
        int numBuckets = s.readInt();
        entries = new WeakKeyAndValue[numBuckets];
        // Read in size (count)
        int size = s.readInt();

        // Read the mappings and add to the TopLinkIdentityHashMap
        for (int i = 0; i < size; i++) {
            Object key = s.readObject();
            Object value = s.readObject();
            put(key, value);
        }
    }

    private static class EmptyEnumerator implements Enumeration {
        EmptyEnumerator() {
        }

        public boolean hasMoreElements() {
            return false;
        }

        public Object nextElement() {
            throw new NoSuchElementException();
        }
    }

    class Enumerator implements Enumeration {
        int enumeratorType;
        int index;
        WeakKeyAndValue entry;

        Enumerator(int enumeratorType) {
            this.enumeratorType = enumeratorType;
            index = WeakKeyIdentityHashtable.this.entries.length;
        }

        public boolean hasMoreElements() {
            if (entry != null) {
                return true;
            }
            while (index-- > 0) {
                if ((entry = WeakKeyIdentityHashtable.this.entries[index]) != null) {
                    return true;
                }
            }
            return false;
        }

        public Object nextElement() {
            if (entry == null) {
                while ((index-- > 0) && ((entry = WeakKeyIdentityHashtable.this.entries[index]) == null)) {
                    ;
                }
            }
            if (entry != null) {
                WeakKeyAndValue e = entry;
                entry = e.next;
                if (enumeratorType == KEYS) {
                    return e.getKey();
                } else {
                    return e.getValue();
                }
            }
            throw new NoSuchElementException("IdentityHashtable.Enumerator");
        }
    }

    
    private static class WeakKeyAndValue{
        int hash;
        private Object referenceOrKey;
        private Object referenceOrValue;
        WeakKeyAndValue next;

        WeakKeyAndValue(int hash, Object key, Object value, WeakKeyAndValue next) {
            this.hash = hash;
            this.referenceOrKey = wrap(key);
            this.referenceOrValue = wrap(value);
            this.next = next;
        }

        protected Object clone() {
            return new WeakKeyAndValue(hash, unwrap(referenceOrKey), unwrap(referenceOrValue), ((next == null) ? null : (WeakKeyAndValue)next.clone()));
        }

        public Object getKey() {
            return unwrap(referenceOrKey);
        }

        public Object getValue() {
            return unwrap(referenceOrValue);
        }

        public Object setValue(Object value) {
            Object oldValue = this.referenceOrValue;
            this.referenceOrValue = wrap(value);
            return oldValue;
        }

        public boolean equals(Object o) {
            if (!(o instanceof WeakKeyAndValue)) {
                return false;
            }

            WeakKeyAndValue e = (WeakKeyAndValue)o;
            return (referenceOrKey == unwrap(e.getKey()) && (unwrap(referenceOrValue) == null) ? (e.getValue() == null) : referenceOrValue.equals(e.getValue()));
        }

        public int hashCode() {
            return hash ^ ((unwrap(referenceOrValue) == null) ? 0 : unwrap(referenceOrValue).hashCode());
        }

        public String toString() {
            return unwrap(referenceOrKey) + "=" + unwrap(referenceOrValue);
        }
        
        private Object unwrap(Object referent){
            WeakReference wrapper = (WeakReference) referent;
            return wrapper.get();
        }
        
        private Object wrap(Object referent){
            //already wrapped, nothing to do...
            if(referent instanceof WeakReference){
                return referent;
            }
            return new WeakReference(referent);
        }
    }
    

}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings.foundation;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.mappings.DatabaseMapping;
import oracle.toplink.essentials.mappings.converters.*;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.sessions.ObjectCopyingPolicy;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.internal.queryframework.JoinedAttributeManager;

/**
 * <b>Purpose</b>: Maps an attribute to the corresponding database field type.
 * The list of field types that are supported by TopLink's direct to field mapping
 * is dependent on the relational database being used.
 * A converter can be used to convert between the object and data type if they do not match.
 *
 * @see Converter
 * @see ObjectTypeConverter
 * @see TypeConversionConverter
 * @see SerializedObjectConverter
 *
 * @author Sati
 * @since TopLink/Java 1.0
 */
public abstract class AbstractDirectMapping extends DatabaseMapping {

    /** DatabaseField which this mapping represents. */
    protected DatabaseField field;

    /** To specify the conversion type */
    protected transient Class attributeClassification;
    protected transient String attributeClassificationName;
    
    /** PERF: Also store object class of attribute in case of primitive. */
    protected transient Class attributeObjectClassification;

    /** Allows user defined conversion between the object attribute value and the database value. */
    protected Converter converter;

    /** Support specification of the value to use for null. */
    protected transient Object nullValue;

    /**
     * PERF: Indicates if this mapping's attribute is a simple atomic value and cannot be modified, only replaced.
     * This is a tri-state to allow user to set to true or false, as default is false but
     * some data-types such as Calendar or byte[] or converter types may be desired to be used as mutable.
     */
    protected Boolean isMutable;

    /**
     * Default constructor.
     */
    public AbstractDirectMapping() {
        super();
        this.setWeight(WEIGHT_1);
    }

    /**
     * PUBLIC:
     * Return the converter on the mapping.
     * A converter can be used to convert between the object's value and database value of the attribute.
     */
    public Converter getConverter() {
        return converter;
    }

    /**
     * PUBLIC:
     * Set the converter on the mapping.
     * A converter can be used to convert between the object's value and database value of the attribute.
     */
    public void setConverter(Converter converter) {
        this.converter = converter;
    }

    /**
     * PUBLIC:
     * Return true if the attribute for this mapping is a simple atomic value that cannot be modified,
     * only replaced.
     * This is false by default unless a mutable converter is used such as the SerializedObjectConverter.
     * This can be set to false in this case, or if a Calendar or byte[] is desired to be used as a mutable value it can be set to true.
     */
    public boolean isMutable() {
        if (isMutable == null) {
            return false;
        }
        return isMutable.booleanValue();
    }

    /**
     * PUBLIC:
     * Return true if the attribute for this mapping is a simple atomic value that cannot be modified,
     * only replaced.
     * This is false by default unless a mutable converter is used such as the SerializedObjectConverter.
     * This can be set to false in this case, or if a Calendar or byte[] is desired to be used as a mutable value it can be set to true.
     */
    public void setIsMutable(boolean isMutable) {
        if (isMutable == true) {
            this.isMutable = Boolean.TRUE;
        } else {
            this.isMutable = Boolean.FALSE;
        }
    }

    /**
     * INTERNAL:
     * Clone the attribute from the clone and assign it to the backup.
     */
    public void buildBackupClone(Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
        buildClone(clone, backup, unitOfWork);
    }

    /**
     * INTERNAL:
     * Clone the attribute from the original and assign it to the clone.
     */
    public void buildClone(Object original, Object clone, UnitOfWorkImpl unitOfWork) {
        buildCloneValue(original, clone, unitOfWork);
    }

    /**
     * INTERNAL:
     * Clone the attribute from the original and assign it to the clone.
     */
    public void buildCloneValue(Object original, Object clone, AbstractSession session) {
        // Optimized for clone copy policy, setting of the value is not required.
        if (isCloningRequired()) {
            Object attributeValue = getAttributeValueFromObject(original);
            if (isMutable()) {
                attributeValue = getAttributeValue(getFieldValue(attributeValue, session), session);
            }
            setAttributeValueInObject(clone, attributeValue);
        }
    }

    /**
     * INTERNAL:
     * Copy of the attribute of the object.
     * This is NOT used for unit of work but for templatizing an object.
     */
    public void buildCopy(Object copy, Object original, ObjectCopyingPolicy policy) {
        buildCloneValue(original, copy, policy.getSession());
    }

    /**
     * INTERNAL:
     * Cascade perform delete through mappings that require the cascade
     */
    public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects) {
        //objects referenced by this mapping are not registered as they have
        // no identity, this is a no-op.
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, IdentityHashtable visitedObjects) {
        //objects referenced by this mapping are not registered as they have
        // no identity, this is a no-op.
    }

    /**
     * INTERNAL:
     * The mapping clones itself to create deep copy.
     */
    public Object clone() {
        AbstractDirectMapping clone = (AbstractDirectMapping)super.clone();

        // Field must be cloned so aggregates do not share fields.
        clone.setField((DatabaseField)getField().clone());

        return clone;
    }

    /**
     * Returns the field this mapping represents.
     */
    protected Vector<DatabaseField> collectFields() {
        Vector databaseField = new Vector(1);

        databaseField.addElement(getField());
        return databaseField;
    }

    /**
     * INTERNAL:
     * Compare the clone and backup clone values and return a change record if the value changed.
     */
    public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
        // same code as write from object into row for update
        if ((owner.isNew()) || (!compareObjects(backUp, clone, session))) {
            return buildChangeRecord(clone, owner, session);
        }
        return null;
    }

    /**
     * INTERNAL:
     * Directly build a change record without comparison
     */
    public ChangeRecord buildChangeRecord(Object clone, ObjectChangeSet owner, AbstractSession session) {
        return internalBuildChangeRecord(getAttributeValueFromObject(clone), owner);
    }

    /**
     * INTERNAL:
     * Build a change record
     */
    public ChangeRecord internalBuildChangeRecord(Object newValue, ObjectChangeSet owner) {
        DirectToFieldChangeRecord changeRecord = new DirectToFieldChangeRecord(owner);
        changeRecord.setAttribute(getAttributeName());
        changeRecord.setMapping(this);
        changeRecord.setNewValue(newValue);
        return changeRecord;
    }

    /**
     * INTERNAL:
     * Compare the attributes belonging to this mapping for the objects.
     */
    public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
        Object one = getAttributeValueFromObject(firstObject);
        Object two = getAttributeValueFromObject(secondObject);

        // PERF: Check identity before conversion.
        if (one == two) {
            return true;
        }

        // CR2114 - following two lines modified; getFieldValue() needs class as an argument
        one = getFieldValue(one, session);
        two = getFieldValue(two, session);
        // PERF: Check identity/nulls before special type comparison.
        if (one == two) {
            return true;
        }

        if ((one == null) || (two == null)) {
            return false;
        }

        // Arrays must be checked for equality because default does identity
        if ((one.getClass() == ClassConstants.APBYTE) && (two.getClass() == ClassConstants.APBYTE)) {
            return Helper.compareByteArrays((byte[])one, (byte[])two);
        }
        if ((one.getClass() == ClassConstants.APCHAR) && (two.getClass() == ClassConstants.APCHAR)) {
            return Helper.compareCharArrays((char[])one, (char[])two);
        }
        if ((one.getClass().isArray()) && (two.getClass().isArray())) {
            return Helper.compareArrays((Object[])one, (Object[])two);
        }

        // BigDecimals equals does not consider the precision correctly
        if (one instanceof java.math.BigDecimal && two instanceof java.math.BigDecimal) {
            return Helper.compareBigDecimals((java.math.BigDecimal)one, (java.math.BigDecimal)two);
        }

        return one.equals(two);
    }
    
    /**
     * INTERNAL:
     * Convert all the class-name-based settings in this mapping to actual class-based
     * settings
     * This method is implemented by subclasses as necessary.
     * @param classLoader
     */
    public void convertClassNamesToClasses(ClassLoader classLoader){
        super.convertClassNamesToClasses(classLoader);
        
        if (converter != null) {
            if (converter instanceof TypeConversionConverter) {
                ((TypeConversionConverter)converter).convertClassNamesToClasses(classLoader);
            } else if (converter instanceof ObjectTypeConverter) {
                // To avoid 1.5 dependencies with the EnumTypeConverter check
                // against ObjectTypeConverter.
                ((ObjectTypeConverter) converter).convertClassNamesToClasses(classLoader);
            }
        }
    };

    /**
     * PUBLIC:
     * Some databases do not properly support all of the base data types. For these databases,
     * the base data type must be explicitly specified in the mapping to tell TopLink to force
     * the instance variable value to that data type
     */
    public Class getAttributeClassification() {
        return attributeClassification;
    }

    public String getAttributeClassificationName() {
        return attributeClassificationName;
    }

    /**
     * INTERNAL:
     * Allows for subclasses to convert the attribute value.
     */
    public Object getAttributeValue(Object fieldValue, AbstractSession session) {
        // PERF: Direct variable access.
        Object attributeValue = fieldValue;
        if ((fieldValue == null) && (getNullValue() != null)) {// Translate default null value
            return this.nullValue;
        }

        // Allow for user defined conversion to the object value.
        if (this.converter != null) {
            attributeValue = this.converter.convertDataValueToObjectValue(attributeValue, session);
        } else {
            // PERF: Avoid conversion check when not required.
            if ((attributeValue == null) || (attributeValue.getClass() != this.attributeObjectClassification)) {
                try {
                    attributeValue = session.getDatasourcePlatform().convertObject(attributeValue, this.attributeClassification);
                } catch (ConversionException e) {
                    throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
                }
            }
        }
        if (attributeValue == null) {// Translate default null value, conversion may have produced null.
            attributeValue = this.nullValue;
        }

        return attributeValue;
    }

    /**
     * INTERNAL:
     * Returns the field which this mapping represents.
     */
    public DatabaseField getField() {
        return field;
    }

    /**
     * INTERNAL:
     */
    public boolean isAbstractDirectMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Return the classifiction for the field contained in the mapping.
     * This is used to convert the row value to a consistent Java value.
     */
    public Class getFieldClassification(DatabaseField fieldToClassify) {
        // PERF: This method is a major performance code point,
        // so has been micro optimized and uses direct variable access.
        if (fieldToClassify.type != null) {
            return fieldToClassify.type;
        } else {
            if (this.converter != null) {
                return null;
            } else {
                return this.attributeClassification;
            }
        }
    }

    /**
     * ADVANCED:
     * Return the class type of the field value.
     * This can be used if field value differs from the object value,
     * has specific typing requirements such as usage of java.sql.Blob or NChar.
     */
    public Class getFieldClassification() {
        if (getField() == null) {
            return null;
        }
        return getField().getType();
    }

    /**
     * ADVANCED:
     * Set the class type of the field value.
     * This can be used if field value differs from the object value,
     * has specific typing requirements such as usage of java.sql.Blob or NChar.
     * This must be called after the field name has been set.
     */
    public void setFieldClassification(Class fieldType) {
        getField().setType(fieldType);
    }

    /**
     * ADVANCED:
     * Set the JDBC type of the field value.
     * This can be used if field type does not corespond directly to a Java class type,
     * such as MONEY.
     * This is used for binding.
     */
    public void setFieldType(int jdbcType) {
        getField().setSqlType(jdbcType);
    }

    /**
     * PUBLIC:
     * Name of the field this mapping represents.
     */
    public String getFieldName() {
        return getField().getQualifiedName();
    }

    /**
     * INTERNAL:
     * Convert the attribute value to a field value.
     * Process any converter if defined, and check for null values.
     */
    public Object getFieldValue(Object attributeValue, AbstractSession session) {
        // PERF: This method is a major performance code point,
        // so has been micro optimized and uses direct variable access.
        Object fieldValue = attributeValue;
        if ((this.nullValue != null) && (this.nullValue.equals(fieldValue))) {
            return null;
        }

        // Allow for user defined conversion to the object value.
        if (this.converter != null) {
            fieldValue = this.converter.convertObjectValueToDataValue(fieldValue, session);
        }
        Class fieldClassification = getFieldClassification(getField());
        // PERF: Avoid conversion if not required.
        if ((fieldValue == null) || (fieldClassification != fieldValue.getClass())) {
            try {
                fieldValue = session.getPlatform(getDescriptor().getJavaClass()).convertObject(fieldValue, fieldClassification);
            } catch (ConversionException exception) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), exception);
            }
        }
        return fieldValue;
    }

    /**
     * PUBLIC:
     * Allow for the value used for null to be specified.
     * This can be used to convert database null values to application specific values, when null values
     * are not allowed by the application (such as in primitives).
     * Note: the default value for NULL is used on reads, writes, and query SQL generation
     */
    public Object getNullValue() {
        return nullValue;
    }

    /**
     * INTERNAL:
     * Return the weight of the mapping, used to sort mappings to ensure that
     * DirectToField Mappings get merged first
     */
    public Integer getWeight() {
        return this.weight;
    }
    
    /**
     * INTERNAL:
     * Initialize the attribute classification.
     */
    public void preInitialize(AbstractSession session) throws DescriptorException {
        super.preInitialize(session);
        this.attributeClassification = getAttributeAccessor().getAttributeClass();
        this.attributeObjectClassification = Helper.getObjectClass(this.attributeClassification);
    }
    
    /**
     * INTERNAL:
     * The mapping is initialized with the given session.
     * This mapping is fully initialized after this.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        super.initialize(session);

        // Initialize isMutable if not specified, default is false (assumes atomic).
        if (this.isMutable == null) {
            if (getConverter() != null) {
                setIsMutable(getConverter().isMutable());
            } else {
                setIsMutable(false);
            }
        }

        if (getField() == null) {
            session.getIntegrityChecker().handleError(DescriptorException.fieldNameNotSetInMapping(this));
        }

        getDescriptor().buildField(getField());
        setFields(collectFields());

        if (getConverter() != null) {
            getConverter().initialize(this, session);
        }
    }

    /**
     * INTERNAL:
     */
    public boolean isDirectToFieldMapping() {
        return true;
    }

    /**
     * INTERNAL:
     * Iterate on the appropriate attribute.
     */
    public void iterate(DescriptorIterator iterator) {
        // PERF: Only iterate when required.
        if (iterator.shouldIterateOnPrimitives()) {
            iterator.iteratePrimitiveForMapping(getAttributeValueFromObject(iterator.getVisitedParent()), this);
        }
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     */
    public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager) {
        setAttributeValueInObject(target, ((DirectToFieldChangeRecord)changeRecord).getNewValue());
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object. This merge is only called when a changeSet for the target
     * does not exist or the target is uninitialized
     */
    public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) {
        Object attributeValue = getAttributeValueFromObject(source);
        if ( (this.getDescriptor().getObjectChangePolicy().isObjectChangeTrackingPolicy()) && (!compareObjects(target, source, mergeManager.getSession())) ) {
            // if it didn't change then there will be no event
            Object targetAttribute = getAttributeValueFromObject(target);
            setAttributeValueInObject(target, attributeValue);
            //set the value first, if the owner is new ( or aggregate) the change set may be created directly
            //from the target.
            this.getDescriptor().getObjectChangePolicy().raiseInternalPropertyChangeEvent(target, getAttributeName(), targetAttribute, attributeValue);
        }else{
            //just set the value and continue
            setAttributeValueInObject(target, attributeValue);
        }
    }

    /**
     * PUBLIC:
     * Some databases do not properly support all of the base data types. For these databases,
     * the base data type must be explicitly specified in the mapping to tell TopLink to force
     * the instance variable value to that data type
     */
    public void setAttributeClassification(Class attributeClassification) {
        this.attributeClassification = attributeClassification;
    }

    /**
     * INTERNAL:
     * Set the name of the class for MW usage.
     */
    public void setAttributeClassificationName(String attributeClassificationName) {
        this.attributeClassificationName = attributeClassificationName;
    }

    /**
     * ADVANCED:
     * Set the field in the mapping.
     * This can be used for advanced field types, such as XML nodes, or to set the field type.
     */
    public void setField(DatabaseField theField) {
        field = theField;
    }

    /**
     * PUBLIC:
     * Allow for the value used for null to be specified.
     * This can be used to convert database null values to application specific values, when null values
     * are not allowed by the application (such as in primitives).
     * Note: the default value for NULL is used on reads, writes, and query SQL generation
     */
    public void setNullValue(Object nullValue) {
        this.nullValue = nullValue;
    }

    /**
     * INTERNAL:
     */
    public String toString() {
        return getClass().getName() + "[" + getAttributeName() + "-->" + getField() + "]";
    }

    /**
     * INTERNAL:
     * Either create a new change record or update with the new value. This is used
     * by attribute change tracking.
     */
    public void updateChangeRecord(Object clone, Object newValue, Object oldValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) {
        DirectToFieldChangeRecord changeRecord = (DirectToFieldChangeRecord)objectChangeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (changeRecord == null) {
            objectChangeSet.addChange(internalBuildChangeRecord(newValue, objectChangeSet));
        } else {
            changeRecord.setNewValue(newValue);
        }
    }

    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return !isMutable();
    }
    
    /**
     * INTERNAL:
     * Return if this mapping requires its attribute value to be cloned.
     */
    public boolean isCloningRequired() {
        return isMutable() || getDescriptor().getCopyPolicy().buildsNewInstance();
    }

    /**
     * INTERNAL:
     * Allow for subclasses to perform validation.
     */
    public void validateBeforeInitialization(AbstractSession session) throws DescriptorException {
        if ((getFieldName() == null) || (getFieldName().length() == 0)) {
            session.getIntegrityChecker().handleError(DescriptorException.noFieldNameForMapping(this));
        }
    }

    /**
     * INTERNAL:
     * Get the value from the object for this mapping.
     */
    public Object valueFromObject(Object object, DatabaseField field, AbstractSession session) throws DescriptorException {
        return getFieldValue(getAttributeValueFromObject(object), session);
    }

    /**
     * INTERNAL:
     * Extract value from the row and set the attribute to this value in the
     * working copy clone.
     * In order to bypass the shared cache when in transaction a UnitOfWork must
     * be able to populate working copies directly from the row.
     */
    public void buildCloneFromRow(AbstractRecord databaseRow, JoinedAttributeManager joinManager, Object clone, ObjectBuildingQuery sourceQuery, UnitOfWorkImpl unitOfWork, AbstractSession executionSession) {
        // Even though the correct value may exist on the original, we can't
        // make that assumption. It is easy to just build it again from the
        // row even if copy policy already copied it.
        // That optimization is lost.
        Object attributeValue = valueFromRow(databaseRow, joinManager, sourceQuery, executionSession);

        setAttributeValueInObject(clone, attributeValue);
    }

    /**
     * INTERNAL:
     * Builds a shallow original object. Only direct attributes and primary
     * keys are populated. In this way the minimum original required for
     * instantiating a working copy clone can be built without placing it in
     * the shared cache (no concern over cycles).
     * @parameter original later the input to buildCloneFromRow
     */
    public void buildShallowOriginalFromRow(AbstractRecord databaseRow, Object original, ObjectBuildingQuery query, AbstractSession executionSession) {
        readFromRowIntoObject(databaseRow, null, original, query, executionSession);
    }

    /**
     * INTERNAL:
     * In the case of building a UnitOfWork clone directly from a row, the
     * session set in the query will not know which database platform to use
     * for converting the value. Allows the correct session to be passed in.
     * @param row
     * @param query
     * @param executionSession
     * @return
     */
    public Object valueFromRow(AbstractRecord row, JoinedAttributeManager joinManager, ObjectBuildingQuery query, AbstractSession executionSession) {
        // PERF: Direct variable access.
        Object fieldValue = row.get(this.field);
        Object attributeValue = getAttributeValue(fieldValue, executionSession);

        return attributeValue;
    }

    /**
     * INTERNAL:
     * Get a value from the object and set that in the respective field of the row.
     */
    public void writeFromObjectIntoRow(Object object, AbstractRecord row, AbstractSession session) {
        if (isReadOnly()) {
            return;
        }

        Object attributeValue = getAttributeValueFromObject(object);
        Object fieldValue = getFieldValue(attributeValue, session);

        writeValueIntoRow(row, getField(), fieldValue);

    }

    protected abstract void writeValueIntoRow(AbstractRecord row, DatabaseField field, Object value);

    /**
     * INTERNAL:
     * Get a value from the object and set that in the respective field of the row.
     * Validation preventing primary key updates is implemented here.
     */
    public void writeFromObjectIntoRowWithChangeRecord(ChangeRecord changeRecord, AbstractRecord row, AbstractSession session) {
        if (isReadOnly()) {
            return;
        }

        if (isPrimaryKeyMapping() && !changeRecord.getOwner().isNew()) {
           throw ValidationException.primaryKeyUpdateDisallowed(changeRecord.getOwner().getClassName(), changeRecord.getAttribute());
        }
        
        Object attributeValue = ((DirectToFieldChangeRecord)changeRecord).getNewValue();
        Object fieldValue = getFieldValue(attributeValue, session);

        row.add(getField(), fieldValue);
    }

    /**
     * INTERNAL:
     * Write the attribute value from the object to the row for update.
     */
    public void writeFromObjectIntoRowForUpdate(WriteObjectQuery query, AbstractRecord aDatabaseRow) {
        if (query.getSession().isUnitOfWork()) {
            if (compareObjects(query.getBackupClone(), query.getObject(), query.getSession())) {
                return;
            }
        }

        super.writeFromObjectIntoRowForUpdate(query, aDatabaseRow);
    }

    /**
     * INTERNAL:
     * Write fields needed for insert into the template for with null values.
     */
    public void writeInsertFieldsIntoRow(AbstractRecord databaseRow, AbstractSession session) {
        if (isReadOnly()) {
            return;
        }

        databaseRow.add(getField(), null);
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.internal.sessions;

import java.util.*;
import java.io.*;
import oracle.toplink.essentials.descriptors.ClassDescriptor;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.platform.server.ServerPlatform;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.databaseaccess.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.sessions.*;
import oracle.toplink.essentials.internal.sequencing.Sequencing;
import oracle.toplink.essentials.logging.*;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.helper.ConcurrencyManager;
import oracle.toplink.essentials.internal.helper.HardIdentityHashtable;

/**
 * Implementation of oracle.toplink.essentials.sessions.Session
 * The public interface should be used by public API and testing, the implementation should be used internally.
 * @see oracle.toplink.essentials.sessions.Session
 *
 * <p>
 * <b>Purpose</b>: Define the interface and common protocol of a TopLink compliant session.
 * <p>
 * <b>Description</b>: The session is the primary interface into TopLink,
 * the application should do all of its reading and writing of objects through the session.
 * The session also manages transactions and units of work. Normally the session
 * is passed and used by the application controler objects. Controler objects normally
 * sit behind the GUI and perform the buiness processes required for the application,
 * they should perform all explict database access and database access should be avoided from
 * the domain object model. Do not use a globally accessable session instance, doing so does
 * not allow for multiple sessions. Multiple sessions may required when performing things like
 * data migration or multiple database access, as well the unit of work feature requires the usage
 * of multiple session instances. Although session is abstract, any users of its subclasses
 * should only cast the variables to Session to allow usage of any of its subclasses.
 * <p>
 * <b>Responsibilities</b>:
 * <ul>
 * <li> Connecting/disconnecting.
 * <li> Reading and writing objects.
 * <li> Transaction and unit of work support.
 * <li> Identity maps and caching.
 * </ul>
 * @see DatabaseSession
 */
public abstract class AbstractSession implements oracle.toplink.essentials.sessions.Session, java.io.Serializable, java.lang.Cloneable {

    /** ExceptionHandler handles database exceptions. */
    transient protected ExceptionHandler exceptionHandler;

    /** IntegrityChecker catch all the descriptor Exceptions. */
    transient protected IntegrityChecker integrityChecker;

    /** The project stores configuration information, such as the descriptors and login. */
    transient protected oracle.toplink.essentials.sessions.Project project;

    /** Ensure mutual exclusion of the session's transaction state across multiple threads.*/
    transient protected ConcurrencyManager transactionMutex;

    /** Manages the live object cache.*/
    protected oracle.toplink.essentials.internal.sessions.IdentityMapAccessor identityMapAccessor;

    /** If Transactions were externally started */
    protected boolean wasJTSTransactionInternallyStarted;

    /** The connection to the data store. */
    transient protected Accessor accessor;
    
    /** Allow the datasource platform to be cached. */
    transient protected Platform platform;

    /** Stores predefine reusable queries.*/
    transient protected Map queries;
    
    /** Stores predefined not yet parsed EJBQL queries.*/
    transient protected List ejbqlPlaceHolderQueries;

    /** Resolves referencial integrity on commits. */
    transient protected CommitManager commitManager;

    /** Tool that log performance information. */
    transient protected SessionProfiler profiler;

    /** Support being owned by a session broker. */
    transient protected AbstractSession broker;

    /** Used to identify a session when using the session broker. */
    protected String name;

    /** Keep track of active units of work. */
    transient protected int numberOfActiveUnitsOfWork;

    /** Destination for logged messages and SQL. */
    transient protected SessionLog sessionLog;

    /** When logging the name of the session is typed: class name + system hashcode. */
    transient protected String logSessionString;

    /** Stores the event listeners for this session. */
    transient protected SessionEventManager eventManager;

    /** Allow for user defined properties. */
    protected Map properties;

    /** Delegate that handles synchronizing a UnitOfWork with an external transaction. */
    transient protected ExternalTransactionController externalTransactionController;

    /** Last descriptor accessed, use to optimize descriptor lookup. */
    transient protected ClassDescriptor lastDescriptorAccessed;

    /** Used to determine If a session is in a profile or not */
    public boolean isInProfile;

    /**
     * INTERNAL:
     * Create and return a new session.
     * This should only be called if the database login information is not know at the time of creation.
     * Normally it is better to call the constructor that takes the login information as an argument
     * so that the session can initialize itself to the platform information given in the login.
     */
    protected AbstractSession() {
        this.name = "";
        initializeIdentityMapAccessor();
        // PERF - move to lazy init (3286091)
        this.numberOfActiveUnitsOfWork = 0;
    }

    /**
     * INTERNAL:
     * Create a blank session, used for proxy session.
     */
    protected AbstractSession(int nothing) {
    }

    /**
     * PUBLIC:
     * Create and return a new session.
     * By giving the login information on creation this allows the session to initialize itself
     * to the platform given in the login. This constructor does not return a connected session.
     * To connect the session to the database login() must be sent to it. The login(userName, password)
     * method may also be used to connect the session, this allows for the user name and password
     * to be given at login but for the other database information to be provided when the session is created.
     */
    public AbstractSession(Login login) {
        this(new oracle.toplink.essentials.sessions.Project(login));
    }

    /**
     * PUBLIC:
     * Create and return a new session.
     * This constructor does not return a connected session.
     * To connect the session to the database login() must be sent to it. The login(userName, password)
     * method may also be used to connect the session, this allows for the user name and password
     * to be given at login but for the other database information to be provided when the session is created.
     */
    public AbstractSession(oracle.toplink.essentials.sessions.Project project) {
        this();
        this.project = project;
        if (project.getDatasourceLogin() == null) {
            throw ValidationException.projectLoginIsNull(this);
        }
    }

    /**
     * INTERNAL:
     * Called by a sessions queries to obtain individual query ids.
     * CR #2698903
     */
    public long getNextQueryId() {
        return QueryCounter.getCount();
    }

    /**
     * INTERNAL:
     * Return a unit of work for this session not registered with the JTS transaction.
     */
    public UnitOfWorkImpl acquireNonSynchronizedUnitOfWork() {
        setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() + 1);
        UnitOfWorkImpl unitOfWork = new UnitOfWorkImpl(this);
        if (shouldLog(SessionLog.FINER, SessionLog.TRANSACTION)) {
            log(SessionLog.FINER, SessionLog.TRANSACTION, "acquire_unit_of_work_with_argument", String.valueOf(System.identityHashCode(unitOfWork)));
        }
        return unitOfWork;
    }

    /**
     * PUBLIC:
     * Return a unit of work for this session.
     * The unit of work is an object level transaction that allows
     * a group of changes to be applied as a unit.
     *
     * @see UnitOfWork
     */
    public UnitOfWork acquireUnitOfWork() {
        UnitOfWorkImpl unitOfWork = acquireNonSynchronizedUnitOfWork();
        unitOfWork.registerWithTransactionIfRequired();

        return unitOfWork;
    }

    /**
     * PUBLIC:
     * Add an alias for the descriptor
     */
    public void addAlias(String alias, ClassDescriptor descriptor) {
        project.addAlias(alias, descriptor);
    }

    /**
     * PUBLIC:
     * Add the query to the session queries with the given name.
     * This allows for common queries to be pre-defined, reused and executed by name.
     */
    public void addQuery(String name, DatabaseQuery query) {
        query.setName(name);
        addQuery(query);
    }

    /**
     * INTERNAL:
     * Return all pre-defined not yet parsed EJBQL queries.
     * @see #getAllQueries()
     */
    public void addEjbqlPlaceHolderQuery(DatabaseQuery query) {
        getEjbqlPlaceHolderQueries().add(query);
    }

    /**
     * INTERNAL:
     * Add the query to the session queries.
     */
    protected void addQuery(DatabaseQuery query) {
        Vector queriesByName = (Vector)getQueries().get(query.getName());
        if (queriesByName == null) {
            // lazily create Vector in Hashtable.
            queriesByName = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            getQueries().put(query.getName(), queriesByName);
        }

        // Check that we do not already have a query that matched it
        for (Enumeration enumtr = queriesByName.elements(); enumtr.hasMoreElements();) {
            DatabaseQuery existingQuery = (DatabaseQuery)enumtr.nextElement();
            if (Helper.areTypesAssignable(query.getArgumentTypes(), existingQuery.getArgumentTypes())) {
                throw ValidationException.existingQueryTypeConflict(query, existingQuery);
            }
        }
        queriesByName.add(query);
    }

    /**
     * INTERNAL:
     * Called by beginTransaction() to start a transaction.
     * This starts a real database transaction.
     */
    protected void basicBeginTransaction() throws DatabaseException {
        try {
            getAccessor().beginTransaction(this);
        } catch (RuntimeException exception) {
            handleException(exception);
        }
    }

    /**
     * INTERNAL:
     * Called after transaction is completed (committed or rolled back)
     */
    public void afterTransaction(boolean committed, boolean isExternalTransaction) {
    }

    /**
     * INTERNAL:
     * Called by commitTransaction() to commit a transaction.
     * This commits the active transaction.
     */
    protected void basicCommitTransaction() throws DatabaseException {
        try {
            getAccessor().commitTransaction(this);
        } catch (RuntimeException exception) {
            handleException(exception);
        }
    }

    /**
     * INTERNAL:
     * Called by rollbackTransaction() to rollback a transaction.
     * This rollsback the active transaction.
     */
    protected void basicRollbackTransaction() throws DatabaseException {
        try {
            getAccessor().rollbackTransaction(this);
        } catch (RuntimeException exception) {
            handleException(exception);
        }
    }

    /**
     * INTERNAL:
     * Attempts to begin an external transaction.
     * Returns true only in one case -
     * extenal transaction has been internally started during this method call:
     * wasJTSTransactionInternallyStarted()==false in the beginning of this method and
     * wasJTSTransactionInternallyStarted()==true in the end of this method.
     */
    public boolean beginExternalTransaction() {
        boolean externalTransactionHasBegun = false;
        if (hasExternalTransactionController() && !wasJTSTransactionInternallyStarted()) {
            try {
                getExternalTransactionController().beginTransaction(this);
            } catch (RuntimeException exception) {
                handleException(exception);
            }
            if (wasJTSTransactionInternallyStarted()) {
                externalTransactionHasBegun = true;
                log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_begun_internally");
            }
        }
        return externalTransactionHasBegun;
    }

    /**
     * PUBLIC:
     * Begin a transaction on the database.
     * This allows a group of database modification to be commited or rolledback as a unit.
     * All writes/deletes will be sent to the database be will not be visible to other users until commit.
     * Although databases do not allow nested transaction,
     * TopLink supports nesting through only committing to the database on the outer commit.
     *
     * @exception DatabaseException if the database connection is lost or the begin is rejected.
     * @exception ConcurrencyException if this session's transaction is aquired by another thread and a timeout occurs.
     *
     * @see #isInTransaction()
     */
    public void beginTransaction() throws DatabaseException, ConcurrencyException {
        // If there is no db transaction in progress
        // beginExternalTransaction() starts an external transaction -
        // provided externalTransactionController is used, and there is
        // no active external transaction - so we have to start one internally.
        if (!isInTransaction()) {
            beginExternalTransaction();
        }

        // For unit of work and client session multi threading is allowed as they are a context,
        // this is required for JTS/RMI/CORBA/EJB stuff where the server thread can be different across calls.
        if (isUnitOfWork() || isClientSession()) {
            getTransactionMutex().setActiveThread(Thread.currentThread());
        }

        // Ensure mutual exclusion and call subclass specific begin.
        getTransactionMutex().acquire();
        if (!getTransactionMutex().isNested()) {
            getEventManager().preBeginTransaction();
            basicBeginTransaction();
            getEventManager().postBeginTransaction();
        }
    }

    /**
     * PUBLIC:
     * clear the integrityChecker. IntegrityChecker holds all the Descriptor Exceptions.
     */
    public void clearIntegrityChecker() {
        setIntegrityChecker(null);
    }

    /**
     * INTERNAL:
     * clear the lastDescriptorAccessed.
     */
    public void clearLastDescriptorAccessed() {
        lastDescriptorAccessed = null;
    }

    /**
     * PUBLIC:
     * Clear the profiler, this will end the current profile opperation.
     */
    public void clearProfile() {
        setProfiler(null);
    }

    /**
     * INTERNAL:
     * Clones the descriptor
     */
    public Object clone() {
        // An alternative to this process should be found
        try {
            return super.clone();
        } catch (Exception exception) {
            return null;
        }
    }

    /**
     * INTERNAL:
     * Attempts to commit the running internally started external transaction.
     * Returns true only in one case -
     * extenal transaction has been internally committed during this method call:
     * wasJTSTransactionInternallyStarted()==true in the beginning of this method and
     * wasJTSTransactionInternallyStarted()==false in the end of this method.
     */
    public boolean commitExternalTransaction() {
        boolean externalTransactionHasCommitted = false;
        if (hasExternalTransactionController() && wasJTSTransactionInternallyStarted()) {
            try {
                getExternalTransactionController().commitTransaction(this);
            } catch (RuntimeException exception) {
                handleException(exception);
            }
            if (!wasJTSTransactionInternallyStarted()) {
                externalTransactionHasCommitted = true;
                log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_committed_internally");
            }
        }
        return externalTransactionHasCommitted;
    }

    /**
     * PUBLIC:
     * Commit the active database transaction.
     * This allows a group of database modification to be commited or rolledback as a unit.
     * All writes/deletes will be sent to the database be will not be visible to other users until commit.
     * Although databases do not allow nested transaction,
     * TopLink supports nesting through only committing to the database on the outer commit.
     *
     * @exception DatabaseException most databases validate changes as they are done,
     * normally errors do not occur on commit unless the disk fails or the connection is lost.
     * @exception ConcurrencyException if this session is not within a transaction.
     */
    public void commitTransaction() throws DatabaseException, ConcurrencyException {
        // Release mutex and call subclass specific commit.
        if (!getTransactionMutex().isNested()) {
            getEventManager().preCommitTransaction();
            basicCommitTransaction();
            getEventManager().postCommitTransaction();
        }

        // This MUST not be in a try catch or finally as if the commit failed the transaction is still open.
        getTransactionMutex().release();

        // If there is no db transaction in progress
        // if there is an active external transaction
        // which was started internally - it should be committed internally, too.
        if (!isInTransaction()) {
            commitExternalTransaction();
        }
    }

    /**
     * INTERNAL:
     * Return if the two object match completely.
     * This checks the objects attributes and their private parts.
     */
    public boolean compareObjects(Object firstObject, Object secondObject) {
        if ((firstObject == null) && (secondObject == null)) {
            return true;
        }

        if ((firstObject == null) || (secondObject == null)) {
            return false;
        }

        if (!(firstObject.getClass().equals(secondObject.getClass()))) {
            return false;
        }

        ObjectBuilder builder = getDescriptor(firstObject.getClass()).getObjectBuilder();

        return builder.compareObjects(builder.unwrapObject(firstObject, this), builder.unwrapObject(secondObject, this), this);
    }

    /**
     * TESTING:
     * Return true if the object do not match.
     * This checks the objects attributes and their private parts.
     */
    public boolean compareObjectsDontMatch(Object firstObject, Object secondObject) {
        return !this.compareObjects(firstObject, secondObject);
    }

    /**
     * PUBLIC:
     * Return true if the pre-defined query is defined on the session.
     */
    public boolean containsQuery(String queryName) {
        return getQueries().containsKey(queryName);
    }

    /**
     * PUBLIC:
     * Return a complete copy of the object.
     * This can be used to obtain a scatch copy of an object,
     * or for templatizing an existing object into another new object.
     * The object and all of its privately owned parts will be copied, the object's primary key will be reset to null.
     *
     * @see #copyObject(Object, ObjectCopyingPolicy)
     */
    public Object copyObject(Object original) {
        return copyObject(original, new ObjectCopyingPolicy());
    }

    /**
     * PUBLIC:
     * Return a complete copy of the object.
     * This can be used to obtain a scatch copy of an object,
     * or for templatizing an existing object into another new object.
     * The object copying policy allow for the depth, and reseting of the primary key to null, to be specified.
     */
    public Object copyObject(Object original, ObjectCopyingPolicy policy) {
        if (original == null) {
            return null;
        }

        ClassDescriptor descriptor = getDescriptor(original);
        if (descriptor == null) {
            return original;
        }

        policy.setSession(this);
        return descriptor.getObjectBuilder().copyObject(original, policy);
    }

    /**
     * INTERNAL:
     * Copy the read only classes from the unit of work
     *
     * Added Nov 8, 2000 JED for Patch 2.5.1.8
     * Ref: Prs 24502
     */
    public Vector copyReadOnlyClasses() {
        return getDefaultReadOnlyClasses();
    }

    /**
     * PUBLIC:
     * delete all of the objects and all of their privately owned parts in the database.
     * The allows for a group of objects to be deleted as a unit.
     * The objects will be deleted through a single transactions.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     */
    public void deleteAllObjects(Collection domainObjects) throws DatabaseException, OptimisticLockException {
        for (Iterator objectsEnum = domainObjects.iterator(); objectsEnum.hasNext();) {
            deleteObject(objectsEnum.next());
        }
    }

    /**
     * PUBLIC:
     * delete all of the objects and all of their privately owned parts in the database.
     * The allows for a group of objects to be deleted as a unit.
     * The objects will be deleted through a single transactions.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     */
    public void deleteAllObjects(Vector domainObjects) throws DatabaseException, OptimisticLockException {
        for (Enumeration objectsEnum = domainObjects.elements(); objectsEnum.hasMoreElements();) {
            deleteObject(objectsEnum.nextElement());
        }
    }

    /**
     * PUBLIC:
     * Delete the object and all of its privately owned parts from the database.
     * The delete operation can be customized through using a delete query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * An database error is not raised if the object is already deleted or no rows are effected.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     *
     * @see DeleteObjectQuery
     */
    public Object deleteObject(Object domainObject) throws DatabaseException, OptimisticLockException {
        DeleteObjectQuery query = new DeleteObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Return if the object exists on the database or not.
     * This always checks existence on the database.
     */
    public boolean doesObjectExist(Object object) throws DatabaseException {
        DoesExistQuery query = new DoesExistQuery();
        query.setObject(object);
        query.checkDatabaseForDoesExist();
        return ((Boolean)executeQuery(query)).booleanValue();
    }

    /**
     * PUBLIC:
     * Turn off logging
     */
    public void dontLogMessages() {
        setLogLevel(SessionLog.OFF);
    }

    /**
     * INTERNAL:
     * End the operation timing.
     */
    public void endOperationProfile(String operationName) {
        if (isInProfile()) {
            getProfiler().endOperationProfile(operationName);
        }
    }

    /**
     * INTERNAL:
     * Updates the value of SessionProfiler state
     */
    public void updateProfile(String operationName, Object value) {
        if (isInProfile()) {
            getProfiler().update(operationName, value);
        }
    }

    /**
     * INTERNAL:
     * Updates the count of SessionProfiler event
     */
    public void incrementProfile(String operationName) {
        if (isInProfile()) {
            getProfiler().occurred(operationName);
        }
    }

    /**
     * INTERNAL:
     * Overridden by subclasses that do more than just execute the call.
     * Executes the call directly on this session and does not check which
     * session it should have executed on.
     */
    public Object executeCall(Call call, AbstractRecord translationRow, DatabaseQuery query) throws DatabaseException {
        //** sequencing refactoring
        if (query.getAccessor() == null) {
            query.setAccessor(getAccessor());
        }
        try {
            return query.getAccessor().executeCall(call, translationRow, this);
        } finally {
            if (call.isFinished()) {
                query.setAccessor(null);
            }
        }
    }

    /**
     * PUBLIC:
     * Execute the call on the database.
     * The row count is returned.
     * The call can be a stored procedure call, SQL call or other type of call.
     * <p>Example:
     * <p>session.executeNonSelectingCall(new SQLCall("Delete from Employee");
     *
     * @see #executeSelectingCall(Call)
     */
    public int executeNonSelectingCall(Call call) throws DatabaseException {
        DataModifyQuery query = new DataModifyQuery();
        query.setCall(call);
        Integer value = (Integer)executeQuery(query);
        if (value == null) {
            return 0;
        } else {
            return value.intValue();
        }
    }

    /**
     * PUBLIC:
     * Execute the sql on the database.
     * <p>Example:
     * <p>session.executeNonSelectingSQL("Delete from Employee");
     * @see #executeNonSelectingCall(Call)
     */
    public void executeNonSelectingSQL(String sqlString) throws DatabaseException {
        executeNonSelectingCall(new SQLCall(sqlString));
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName) throws DatabaseException {
        DatabaseQuery query = getQuery(queryName);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName);
        }

        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass) throws DatabaseException {
        ClassDescriptor descriptor = getDescriptor(domainClass);

        if (descriptor == null) {
            throw QueryException.descriptorIsMissingForNamedQuery(domainClass, queryName);
        }

        DatabaseQuery query = (DatabaseQuery)descriptor.getQueryManager().getQuery(queryName);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName, domainClass);
        }

        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Object arg1) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        return executeQuery(queryName, domainClass, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Object arg1, Object arg2) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        return executeQuery(queryName, domainClass, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Object arg1, Object arg2, Object arg3) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        argumentValues.addElement(arg3);
        return executeQuery(queryName, domainClass, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     * The class is the descriptor in which the query was pre-defined.
     *
     * @see DescriptorQueryManager#addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Class domainClass, Vector argumentValues) throws DatabaseException {
        ClassDescriptor descriptor = getDescriptor(domainClass);

        if (descriptor == null) {
            throw QueryException.descriptorIsMissingForNamedQuery(domainClass, queryName);
        }

        DatabaseQuery query = (DatabaseQuery)descriptor.getQueryManager().getQuery(queryName, argumentValues);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName, domainClass);
        }

        return executeQuery(query, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Object arg1) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        return executeQuery(queryName, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Object arg1, Object arg2) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        return executeQuery(queryName, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Object arg1, Object arg2, Object arg3) throws DatabaseException {
        Vector argumentValues = new Vector();
        argumentValues.addElement(arg1);
        argumentValues.addElement(arg2);
        argumentValues.addElement(arg3);
        return executeQuery(queryName, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the pre-defined query by name and return the result.
     * Queries can be pre-defined and named to allow for their reuse.
     *
     * @see #addQuery(String, DatabaseQuery)
     */
    public Object executeQuery(String queryName, Vector argumentValues) throws DatabaseException {
        DatabaseQuery query = getQuery(queryName, argumentValues);

        if (query == null) {
            throw QueryException.queryNotDefined(queryName);
        }

        return executeQuery(query, argumentValues);
    }

    /**
     * PUBLIC:
     * Execute the database query.
     * A query is a database operation such as reading or writting.
     * The query allows for the operation to be customized for such things as,
     * performance, depth, caching, etc.
     *
     * @see DatabaseQuery
     */
    public Object executeQuery(DatabaseQuery query) throws DatabaseException {
        return executeQuery(query, new DatabaseRecord(1));
    }

    /**
     * PUBLIC:
     * Return the results from exeucting the database query.
     * the arguments are passed in as a vector
     */
    public Object executeQuery(DatabaseQuery query, Vector argumentValues) throws DatabaseException {
        if (query == null) {
            throw QueryException.queryNotDefined();
        }

        AbstractRecord row = query.rowFromArguments(argumentValues);

        return executeQuery(query, row);
    }

    /**
     * INTERNAL:
     * Return the results from exeucting the database query.
     * the arguments should be a database row with raw data values.
     */
    public Object executeQuery(DatabaseQuery query, AbstractRecord row) throws DatabaseException {
        if (hasBroker()) {
            if (!((query.isDataModifyQuery() || query.isDataReadQuery()) && (query.getSessionName() == null))) {
                return getBroker().executeQuery(query, row);
            }
        }

        if (query == null) {
            throw QueryException.queryNotDefined();
        }

        //CR#2272
        log(SessionLog.FINEST, SessionLog.QUERY, "execute_query", query);

        try {
            getEventManager().preExecuteQuery(query);
            Object result;
            if (isInProfile()) {
                result = getProfiler().profileExecutionOfQuery(query, row, this);
            } else {
                result = internalExecuteQuery(query, row);
            }
            getEventManager().postExecuteQuery(query, result);
            return result;
        } catch (RuntimeException exception) {
            if (exception instanceof QueryException) {
                QueryException queryException = (QueryException)exception;
                if (queryException.getQuery() == null) {
                    queryException.setQuery(query);
                }
                if (queryException.getQueryArgumentsRecord() == null) {
                    queryException.setQueryArguments(row);
                }
                if (queryException.getSession() == null) {
                    queryException.setSession(this);
                }
            } else if (exception instanceof DatabaseException) {
                DatabaseException databaseException = (DatabaseException)exception;
                if (databaseException.getQuery() == null) {
                    databaseException.setQuery(query);
                }
                if (databaseException.getQueryArgumentsRecord() == null) {
                    databaseException.setQueryArguments(row);
                }
                if (databaseException.getSession() == null) {
                    databaseException.setSession(this);
                }
            }
            return handleException(exception);
        }
    }

    /**
     * PUBLIC:
     * Execute the call on the database and return the result.
     * The call must return a value, if no value is return executeNonSelectCall must be used.
     * The call can be a stored procedure call, SQL call or other type of call.
     * A vector of database rows is returned, database row implements Java 2 Map which should be used to access the data.
     * <p>Example:
     * <p>session.executeSelectingCall(new SQLCall("Select * from Employee");
     *
     * @see #executeNonSelectingCall(Call)
     */
    public Vector executeSelectingCall(Call call) throws DatabaseException {
        DataReadQuery query = new DataReadQuery();
        query.setCall(call);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Execute the sql on the database and return the result.
     * It must return a value, if no value is return executeNonSelectingSQL must be used.
     * A vector of database rows is returned, database row implements Java 2 Map which should be used to access the data.
     * <p>Example:
     * <p>session.executeSelectingCall("Select * from Employee");
     *
     * @see #executeSelectingCall(Call)
     */
    public Vector executeSQL(String sqlString) throws DatabaseException {
        return executeSelectingCall(new SQLCall(sqlString));
    }

    /**
     * INTERNAL:
     * Return the lowlevel database accessor.
     * The database accesor is used for direct database access.
     */
    public synchronized Accessor getAccessor() {
        if ((accessor == null) && (project != null) && (project.getDatasourceLogin() != null)) {
            // PERF: lazy init, not always required.
            accessor = project.getDatasourceLogin().buildAccessor();
        }
        return accessor;
    }

    /**
     * INTERNAL:
     * Return the lowlevel database accessor.
     * The database accesor is used for direct database access.
     * If sessionBroker is used, the right accessor for this
     * broker will be returned.
     */
    public Accessor getAccessor(Class domainClass) {
        return getAccessor();
    }

    /**
     * INTERNAL:
     * Return the lowlevel database accessor.
     * The database accesor is used for direct database access.
     * If sessionBroker is used, the right accessor for this
     * broker will be returned based on the session name.
     */
    public Accessor getAccessor(String sessionName) {
        return getAccessor();
    }

    /**
     * PUBLIC:
     * Return the active session for the current active external (JTS) transaction.
     * This should only be used with JTS and will return the session if no external transaction exists.
     */
    public oracle.toplink.essentials.sessions.Session getActiveSession() {
        oracle.toplink.essentials.sessions.Session activeSession = getActiveUnitOfWork();
        if (activeSession == null) {
            activeSession = this;
        }

        return activeSession;
    }

    /**
     * PUBLIC:
     * Return the active unit of work for the current active external (JTS) transaction.
     * This should only be used with JTS and will return null if no external transaction exists.
     */
    public oracle.toplink.essentials.sessions.UnitOfWork getActiveUnitOfWork() {
        if (hasExternalTransactionController()) {
            return getExternalTransactionController().getActiveUnitOfWork();
        }

        /* Steven Vo: CR# 2517
           Get from the server session since the external transaction controller could be
           null out from the client session by TL WebLogic 5.1 to provide non-jts transaction
           operations
          */
        if (isClientSession()) {
            return ((oracle.toplink.essentials.threetier.ClientSession)this).getParent().getActiveUnitOfWork();
        }

        return null;
    }

    /**
     * INTERNAL:
     * Returns the alias descriptors hashtable.
     */
    public Map getAliasDescriptors() {
        return project.getAliasDescriptors();
    }

    /**
     * INTERNAL:
     * Allow the session to be used from a session broker.
     */
    public AbstractSession getBroker() {
        return broker;
    }

    /**
     * INTERNAL:
     * The session that this query is executed against when not in transaction.
     * The session containing the shared identity map.
     * <p>
     * In most cases this is the root ServerSession or DatabaseSession.
     * <p>
     * In cases where objects are not to be cached in the global identity map
     * an alternate session may be returned:
     * <ul>
     * <li>A ClientSession if in transaction
     * <li>An isolated ClientSession or HistoricalSession
     * <li>A registered session of a root SessionBroker
     * </ul>
     */
    public AbstractSession getRootSession(DatabaseQuery query) {
        return getParentIdentityMapSession(query, false, true);
    }

    /**
     * INTERNAL:
     * Gets the parent session.
     */
    public AbstractSession getParent() {
        return null;
    }

    /**
     * INTERNAL:
     * Gets the next link in the chain of sessions followed by a query's check
     * early return, the chain of sessions with identity maps all the way up to
     * the root session.
     */
    public AbstractSession getParentIdentityMapSession(DatabaseQuery query) {
        return getParentIdentityMapSession(query, false, false);
    }

    /**
     * INTERNAL:
     * Gets the next link in the chain of sessions followed by a query's check
     * early return, the chain of sessions with identity maps all the way up to
     * the root session.
     * <p>
     * Used for session broker which delegates to registered sessions, or UnitOfWork
     * which checks parent identity map also.
     * @param canReturnSelf true when method calls itself. If the path
     * starting at <code>this</code> is acceptable. Sometimes true if want to
     * move to the first valid session, i.e. executing on ClientSession when really
     * should be on ServerSession.
     * @param terminalOnly return the session we will execute the call on, not
     * the next step towards it.
     * @return this if there is no next link in the chain
     */
    public AbstractSession getParentIdentityMapSession(DatabaseQuery query, boolean canReturnSelf, boolean terminalOnly) {
        return this;
    }

    /**
     * INTERNAL:
     * Gets the session which this query will be executed on.
     * Generally will be called immediately before the call is translated,
     * which is immediately before session.executeCall.
     * <p>
     * Since the execution session also knows the correct datasource platform
     * to execute on, it is often used in the mappings where the platform is
     * needed for type conversion, or where calls are translated.
     * <p>
     * Is also the session with the accessor. Will return a ClientSession if
     * it is in transaction and has a write connection.
     * @return a session with a live accessor
     * @param query may store session name or reference class for brokers case
     */
    public AbstractSession getExecutionSession(DatabaseQuery query) {
        return this;
    }

    /**
     * INTERNAL:
     * The commit manager is used to resolve referncial integrity on commits of multiple objects.
     * All brokered sessions share the same commit manager.
     */
    public CommitManager getCommitManager() {
        if (hasBroker()) {
            return getBroker().getCommitManager();
        }

        // PERF: lazy init, not always required, not required for client sessions
        if (commitManager == null) {
            commitManager = new CommitManager(this);
        }
        return commitManager;
    }

    /**
     * INTERNAL:
     * Returns the set of read-only classes that gets assigned to each newly created UnitOfWork.
     *
     * @see oracle.toplink.essentials.sessions.Project#setDefaultReadOnlyClasses(Vector)
     */
    public Vector getDefaultReadOnlyClasses() {
        //Bug#3911318 All brokered sessions share the same DefaultReadOnlyClasses.
        if (hasBroker()) {
            return getBroker().getDefaultReadOnlyClasses();
        }
        return getProject().getDefaultReadOnlyClasses();
    }

    /**
     * ADVANCED:
     * Return the descriptor specified for the class.
     * If the class does not have a descriptor but implements an interface that is also implemented
     * by one of the classes stored in the hashtable, that descriptor will be stored under the
     * new class.
     */
    public ClassDescriptor getClassDescriptor(Class theClass) {
                ClassDescriptor desc = getDescriptor(theClass);
                if (desc instanceof ClassDescriptor) {
                        return (ClassDescriptor)desc;
                } else {
                        throw ValidationException.cannotCastToClass(desc, desc.getClass(), ClassDescriptor.class);
                }
        }

    /**
     * ADVANCED:
     * Return the descriptor specified for the object's class.
     */
    public ClassDescriptor getClassDescriptor(Object domainObject) {
                ClassDescriptor desc = getDescriptor(domainObject);
                if (desc instanceof ClassDescriptor) {
                        return (ClassDescriptor)desc;
                } else {
                        throw ValidationException.cannotCastToClass(desc, desc.getClass(), ClassDescriptor.class);
                }
        }

    /**
     * PUBLIC:
     * Return the descriptor for the alias.
     * UnitOfWork delegates this to the parent
     */
    public ClassDescriptor getClassDescriptorForAlias(String alias) {
        return project.getClassDescriptorForAlias(alias);
        }

    /**
     * ADVANCED:
     * Return the descriptor specified for the class.
     * If the class does not have a descriptor but implements an interface that is also implemented
     * by one of the classes stored in the hashtable, that descriptor will be stored under the
     * new class.
     */
    public ClassDescriptor getDescriptor(Class theClass) {
        if (theClass == null) {
            return null;
        }

        // Optimize descriptor lookup through caching the last one accessed.
        ClassDescriptor lastDescriptor = this.lastDescriptorAccessed;
        if ((lastDescriptor != null) && (lastDescriptor.getJavaClass().equals(theClass))) {
            return lastDescriptor;
        }

        ClassDescriptor descriptor = (ClassDescriptor)getDescriptors().get(theClass);

        if ((descriptor == null) && hasBroker()) {
            // Also check the broker
            descriptor = getBroker().getDescriptor(theClass);
        }
        if (descriptor == null) {
            // Allow for an event listener to lazy register the descriptor for a class.
            getEventManager().missingDescriptor(theClass);
            descriptor = (ClassDescriptor)getDescriptors().get(theClass);
        }

        if (descriptor == null) {
            // This allows for the correct descriptor to be found if the class implements an interface,
            // or extends a class that a descriptor is register for.
            // This is used by EJB to find the descriptor for a stub and remote to unwrap it,
            // and by inheritance to allow for subclasses that have no additional state to not require a descriptor.
            if (!theClass.isInterface()) {
                Class[] interfaces = theClass.getInterfaces();
                for (int index = 0; index < interfaces.length; ++index) {
                    Class interfaceClass = (Class)interfaces[index];
                    descriptor = getDescriptor(interfaceClass);
                    if (descriptor != null) {
                        getDescriptors().put(interfaceClass, descriptor);
                        break;
                    }
                }
                if (descriptor == null) {
                    descriptor = getDescriptor(theClass.getSuperclass());
                }
            }
        }

        // Cache for optimization.
        this.lastDescriptorAccessed = descriptor;

        return descriptor;
    }

    /**
     * ADVANCED:
     * Return the descriptor specified for the object's class.
     */
    public ClassDescriptor getDescriptor(Object domainObject) {
        return getDescriptor(domainObject.getClass());
    }

    /**
     * PUBLIC:
     * Return the descriptor for the alias
     */
    public ClassDescriptor getDescriptorForAlias(String alias) {
        return project.getDescriptorForAlias(alias);
    }

    /**
     * ADVANCED:
     * Return all registered descriptors.
     */
    public Map getDescriptors() {
        return getProject().getDescriptors();
    }

    /**
     * ADVANCED:
     * Return all pre-defined not yet parsed EJBQL queries.
     * @see #getAllQueries()
     */
    public List getEjbqlPlaceHolderQueries() {
        // PERF: lazy init, not normally required.
        if (ejbqlPlaceHolderQueries == null) {
            ejbqlPlaceHolderQueries = new Vector();
        }
        return ejbqlPlaceHolderQueries;
    }

    /**
     * PUBLIC:
     * Return the event manager.
     * The event manager can be used to register for various session events.
     */
    public synchronized SessionEventManager getEventManager() {
        if (eventManager == null) {
            // PERF: lazy init.
            eventManager = new SessionEventManager(this);
        }
        return eventManager;
    }

    /**
     * INTERNAL:
     * Return a string which represents my ExceptionHandler's class
     * Added for F2104: Properties.xml
     * - gn
     */
    public String getExceptionHandlerClass() {
        String className = null;
        try {
            className = getExceptionHandler().getClass().getName();
        } catch (Exception exception) {
            return null;
        }
        return className;
    }

    /**
     * PUBLIC:
     * Return the ExceptionHandler.Exception handler can catch errors that occur on queries or during database access.
     */
    public ExceptionHandler getExceptionHandler() {
        return exceptionHandler;
    }

    /**
     * PUBLIC:
     * Used for JTS integration. If your application requires to have JTS control transactions instead of TopLink an
     * external transaction controler must be specified.
     * TopLink provides JTS controlers for several JTS implementations including JTS 1.0, Weblogic 5.1 and WebSphere 3.0.
     *
     * @see oracle.toplink.essentials.transaction.JTATransactionController
     */
    public ExternalTransactionController getExternalTransactionController() {
        return externalTransactionController;
    }

    /**
     * PUBLIC:
     * The IdentityMapAccessor is the preferred way of accessing IdentityMap funcitons
     * This will return an object which implements an interface which exposes all public
     * IdentityMap functions.
     */
    public oracle.toplink.essentials.sessions.IdentityMapAccessor getIdentityMapAccessor() {
        return identityMapAccessor;
    }

    /**
     * INTERNAL:
     * Return the internally available IdentityMapAccessor instance.
     */
    public oracle.toplink.essentials.internal.sessions.IdentityMapAccessor getIdentityMapAccessorInstance() {
        return identityMapAccessor;
    }

    /**
     * PUBLIC:
     * Returns the integrityChecker.IntegrityChecker holds all the Descriptor Exceptions.
     */
    public IntegrityChecker getIntegrityChecker() {
        // BUG# 2700595 - Lazily create an IntegrityChecker if one has not already been created.
        if (integrityChecker == null) {
            integrityChecker = new IntegrityChecker();
        }

        return integrityChecker;
    }

    /**
     * PUBLIC:
     * Return the writer to which an accessor writes logged messages and SQL.
     * If not set, this reference defaults to a writer on System.out.
     *
     * @see #getSessionLog()
     */
    public Writer getLog() {
        return getSessionLog().getWriter();
    }

    /**
     * INTERNAL:
     * Return the name of the session: class name + system hashcode.
     * <p>
     * This should be the implementation of toString(), and also the
     * value should be calculated in the constructor for it is used all the
     * time. However everything is lazily initialized now and the value is
     * transient for the system hashcode could vary?
     */
    public String getLogSessionString() {
        if (logSessionString == null) {
            StringWriter writer = new StringWriter();
            writer.write(getSessionTypeString());
            writer.write("(");
            writer.write(String.valueOf(System.identityHashCode(this)));
            writer.write(")");
            logSessionString = writer.toString();
        }
        return logSessionString;
    }

    /**
     * INTERNAL:
     * Returns the type of session, its class.
     * <p>
     * Override to hide from the user when they are using an internal subclass
     * of a known class.
     * <p>
     * A user does not need to know that their UnitOfWork is a
     * non-deferred UnitOfWork, or that their ClientSession is an
     * IsolatedClientSession.
     */
    public String getSessionTypeString() {
        return Helper.getShortClassName(getClass());
    }

    /**
     * INTERNAL:
     * Return the login, the login holds any database connection information given.
     * This has been replaced by getDatasourceLogin to make use of the Login interface
     * to support non-relational datasources,
     * if DatabaseLogin API is required it will need to be cast.
     */
    public DatabaseLogin getLogin() {
        try {
            return (DatabaseLogin)getDatasourceLogin();
        } catch (ClassCastException wrongType) {
            throw ValidationException.notSupportedForDatasource();
        }
    }

    /**
     * PUBLIC:
     * Return the login, the login holds any database connection information given.
     * This return the Login interface and may need to be cast to the datasource specific implementation.
     */
    public Login getDatasourceLogin() {
        return getProject().getDatasourceLogin();
    }

    /**
     * PUBLIC:
     * Return the name of the session.
     * This is used with the session broker, or to give the session a more meaningful name.
     */
    public String getName() {
        return name;
    }

    /**
     * ADVANCED:
     * Return the sequnce number from the database
     */
    public Number getNextSequenceNumberValue(Class domainClass) {
        return (Number)getSequencing().getNextValue(domainClass);
    }

    /**
     * INTERNAL:
     * Return the number of units of work connected.
     */
    public int getNumberOfActiveUnitsOfWork() {
        return numberOfActiveUnitsOfWork;
    }

    /**
     * PUBLIC:
     * Return the database platform currently connected to.
     * The platform is used for database specific behavoir.
     * NOTE: this must only be used for relational specific usage,
     * it will fail for non-relational datasources.
     */
    public DatabasePlatform getPlatform() {
        // PERF: Cache the platform.
        if (platform == null) {
            platform = getDatasourceLogin().getPlatform();
        }
        return (DatabasePlatform)platform;
    }

    /**
     * PUBLIC:
     * Return the database platform currently connected to.
     * The platform is used for database specific behavoir.
     */
    public Platform getDatasourcePlatform() {
        // PERF: Cache the platform.
        if (platform == null) {
            platform = getDatasourceLogin().getDatasourcePlatform();
        }
        return platform;
    }
    
    /**
     * INTERNAL:
     * Marked internal as this is not customer API but helper methods for
     * accessing the server platform from within TopLink's other sessions types
     * (ie not DatabaseSession)
     */
    public ServerPlatform getServerPlatform(){
        return null;
    }

    /**
     * INTERNAL:
     * Return the database platform currently connected to
     * for specified class.
     * The platform is used for database specific behavoir.
     */
    public Platform getPlatform(Class domainClass) {
        // PERF: Cache the platform.
        if (platform == null) {
            platform = getDatasourcePlatform();
        }
        return platform;
    }

    /**
     * PUBLIC:
     * Return the profiler.
     * The profiler is a tool that can be used to determine performance bottlenecks.
     * The profiler can be queries to print summaries and configure for logging purposes.
     */
    public SessionProfiler getProfiler() {
        return profiler;
    }

    /**
     * PUBLIC:
     * Return the project, the project holds configuartion information including the descriptors.
     */
    public oracle.toplink.essentials.sessions.Project getProject() {
        return project;
    }

    /**
     * ADVANCED:
     * Allow for user defined properties.
     */
    public Map getProperties() {
        if (properties == null) {
            properties = new HashMap(5);
        }
        return properties;
    }

    /**
     * INTERNAL:
     * Allow to check for user defined properties.
     */
    public boolean hasProperties() {
        return ((properties != null) && !properties.isEmpty());
    }

    /**
     * ADVANCED:
     * Returns the user defined property.
     */
    public Object getProperty(String name) {
        if(this.properties==null){
            return null;
        }
        return getProperties().get(name);
    }

    /**
     * ADVANCED:
     * Return all pre-defined queries.
     * @see #getAllQueries()
     */
    public Map getQueries() {
        // PERF: lazy init, not normally required.
        if (queries == null) {
            queries = new HashMap(5);
        }
        return queries;
    }

    /**
     * PUBLIC:
     * Return the pre-defined queries in this session.
     * A single vector containing all the queries is returned.
     *
     * @see #getQueries()
     */
    public Vector getAllQueries() {
        Vector allQueries = new Vector();
        for (Iterator vectors = getQueries().values().iterator(); vectors.hasNext();) {
            allQueries.addAll((Vector)vectors.next());
        }
        return allQueries;
    }

    /**
     * PUBLIC:
     * Return the query from the session pre-defined queries with the given name.
     * This allows for common queries to be pre-defined, reused and executed by name.
     */
    public DatabaseQuery getQuery(String name) {
        return getQuery(name, null);
    }

    /**
     * PUBLIC:
     * Return the query from the session pre-defined queries with the given name and argument types.
     * This allows for common queries to be pre-defined, reused and executed by name.
     * This method should be used if the Session has multiple queries with the same name but
     * different arguments.
     *
     * @see #getQuery(String)
     */
    public DatabaseQuery getQuery(String name, Vector arguments) {
        Vector queries = (Vector)getQueries().get(name);
        if ((queries == null) || queries.isEmpty()) {
            return null;
        }

        // Short circuit the simple, most common case of only one query.
        if (queries.size() == 1) {
            return (DatabaseQuery)queries.firstElement();
        }

        // CR#3754; Predrag; mar 19/2002;
        // We allow multiple named queries with the same name but
        // different argument set; we can have only one query with
        // no arguments; Vector queries is not sorted;
        // When asked for the query with no parameters the
        // old version did return the first query - wrong:
        // return (DatabaseQuery) queries.firstElement();
        int argumentTypesSize = 0;
        if (arguments != null) {
            argumentTypesSize = arguments.size();
        }
        Vector argumentTypes = new Vector(argumentTypesSize);
        for (int i = 0; i < argumentTypesSize; i++) {
            argumentTypes.addElement(arguments.elementAt(i).getClass());
        }
        for (Enumeration queriesEnum = queries.elements(); queriesEnum.hasMoreElements();) {
            DatabaseQuery query = (DatabaseQuery)queriesEnum.nextElement();
            if (Helper.areTypesAssignable(argumentTypes, query.getArgumentTypes())) {
                return query;
            }
        }
        return null;
    }

    /**
     * INTERNAL:
     * Return the Sequencing object used by the session.
     */
    public Sequencing getSequencing() {
        return null;
    }

    /**
     * INTERNAL:
     * Return the session to be used for the class.
     * Used for compatibility with the session broker.
     */
    public AbstractSession getSessionForClass(Class domainClass) {
        if (hasBroker()) {
            return getBroker().getSessionForClass(domainClass);
        }
        return this;
    }

    /**
     * PUBLIC:
     * Return the session log to which an accessor logs messages and SQL.
     * If not set, this will default to a session log on a writer on System.out.
     */
    public SessionLog getSessionLog() {
        if (sessionLog == null) {
            setSessionLog(new DefaultSessionLog());
        }
        return sessionLog;
    }

    /**
     * INTERNAL:
     * The transaction mutex ensure mutual exclusion on transaction across multiple threads.
     */
    public synchronized ConcurrencyManager getTransactionMutex() {
        // PERF: not always required, defer.
        if (transactionMutex == null) {
            transactionMutex = new ConcurrencyManager();
        }
        return transactionMutex;
    }

    /**
     * PUBLIC:
     * Allow any WARNING level exceptions that occur within TopLink to be logged and handled by the exception handler.
     */
    public Object handleException(RuntimeException exception) throws RuntimeException {
        if ((exception instanceof TopLinkException)) {
            TopLinkException topLinkException = (TopLinkException)exception;
            if (topLinkException.getSession() == null) {
                topLinkException.setSession(this);
            }
            //Bug#3559280 Avoid logging an exception twice
            if (!topLinkException.hasBeenLogged()) {
                logThrowable(SessionLog.WARNING, null, exception);
                topLinkException.setHasBeenLogged(true);
            }
        } else {
            logThrowable(SessionLog.WARNING, null, exception);
        }
        if (hasExceptionHandler()) {
            return getExceptionHandler().handleException(exception);
        } else {
            throw exception;
        }
    }

    /**
     * INTERNAL:
     * Allow the session to be used from a session broker.
     */
    public boolean hasBroker() {
        return broker != null;
    }

    /**
     * ADVANCED:
     * Return true if a descriptor exists for the given class.
     */
    public boolean hasDescriptor(Class theClass) {
        if (theClass == null) {
            return false;
        }

        return getDescriptors().get(theClass) != null;
    }

    /**
     * PUBLIC:
     * Return if an exception handler is present.
     */
    public boolean hasExceptionHandler() {
        if (exceptionHandler == null) {
            return false;
        }
        return true;
    }

    /**
     * PUBLIC:
     * Used for JTA integration. If your application requires to have JTA control transactions instead of TopLink an
     * external transaction controler must be specified. TopLink provides JTA controlers for JTA 1.0 and application
     * servers.
     * @see oracle.toplink.essentials.transaction.JTATransactionController
     */
    public boolean hasExternalTransactionController() {
        return externalTransactionController != null;
    }

    /**
     * INTERNAL:
     * Set up the IdentityMapManager. This method allows subclasses of Session to override
     * the default IdentityMapManager functionality.
     */
    public void initializeIdentityMapAccessor() {
        this.identityMapAccessor = new oracle.toplink.essentials.internal.sessions.IdentityMapAccessor(this, new IdentityMapManager(this));
    }

    /**
     * PUBLIC:
     * Insert the object and all of its privately owned parts into the database.
     * Insert should only be used if the application knows that the object is new,
     * otherwise writeObject should be used.
     * The insert operation can be customized through using an insert query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     *
     * @see InsertObjectQuery
     * @see #writeObject(Object)
     */
    public Object insertObject(Object domainObject) throws DatabaseException {
        InsertObjectQuery query = new InsertObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * INTERNAL:
     * Return the results from exeucting the database query.
     * The arguments should be a database row with raw data values.
     * This method is provided to allow subclasses to change the default querying behavoir.
     * All querying goes through this method.
     */
    public Object internalExecuteQuery(DatabaseQuery query, AbstractRecord databaseRow) throws DatabaseException {
        return query.execute(this, databaseRow);
    }

    /**
     * INTERNAL:
     * Returns true if the session is a session Broker.
     */
    public boolean isBroker() {
        return false;
    }

    /**
     * INTERNAL:
     * Returns true if the session is in a session Broker.
     */
    public boolean isInBroker() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if the class is defined as read-only.
     */
    public boolean isClassReadOnly(Class theClass) {
        ClassDescriptor descriptor = getDescriptor(theClass);
        return isClassReadOnly(theClass, descriptor);
    }

    /**
     * INTERNAL:
     * Return if the class is defined as read-only.
     * PERF: Pass descriptor to avoid re-lookup.
     */
    public boolean isClassReadOnly(Class theClass, ClassDescriptor descriptor) {
        if ((descriptor != null) && descriptor.shouldBeReadOnly()) {
            return true;
        }
        if (theClass != null) {
            return getDefaultReadOnlyClasses().contains(theClass);
        }
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a client session.
     */
    public boolean isClientSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is connected to the database.
     */
    public boolean isConnected() {
        if (getAccessor() == null) {
            return false;
        }

        return getAccessor().isConnected();
    }

    /**
     * PUBLIC:
     * Return if this session is a database session.
     */
    public boolean isDatabaseSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a distributed session.
     */
    public boolean isDistributedSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if a profiler is being used.
     */
    public boolean isInProfile() {
        return isInProfile;
    }

    /**
     * PUBLIC:
     * Allow for user deactive a profiler
     */
    public void setIsInProfile(boolean inProfile) {
        this.isInProfile = inProfile;
    }

    /**
     * PUBLIC:
     * Return if the session is currently in the progress of a database transaction.
     * Because nested transactions are allowed check if the transaction mutex has been aquired.
     */
    public boolean isInTransaction() {
        return getTransactionMutex().isAcquired();
    }

    /**
     * PUBLIC:
     * Return if this session is remote.
     */
    public boolean isRemoteSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a unit of work.
     */
    public boolean isRemoteUnitOfWork() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a server session.
     */
    public boolean isServerSession() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a session broker.
     */
    public boolean isSessionBroker() {
        return false;
    }

    /**
     * PUBLIC:
     * Return if this session is a unit of work.
     */
    public boolean isUnitOfWork() {
        return false;
    }

    /**
     * ADVANCED:
     * Extract and return the primary key from the object.
     */
    public Vector keyFromObject(Object domainObject) throws ValidationException {
        ClassDescriptor descriptor = getDescriptor(domainObject);
        return keyFromObject(domainObject, descriptor);
    }

    /**
     * ADVANCED:
     * Extract and return the primary key from the object.
     */
    public Vector keyFromObject(Object domainObject, ClassDescriptor descriptor) throws ValidationException {
        if (descriptor == null) {
            throw ValidationException.missingDescriptor(domainObject.getClass().getName());
        }
        Object implemention = descriptor.getObjectBuilder().unwrapObject(domainObject, this);
        if (implemention == null) {
            return null;
        }
        return descriptor.getObjectBuilder().extractPrimaryKeyFromObject(implemention, this);
    }

    /**
     * PUBLIC:
     * Log the log entry.
     */
    public void log(SessionLogEntry entry) {
        if (shouldLog(entry.getLevel(), entry.getNameSpace())) {
            if (entry.getSession() == null) {// Used for proxy session.
                entry.setSession(this);
            }
            getSessionLog().log(entry);
        }
    }

    /**
     * Log a untranslated message to the TopLink log at FINER level.
     */
    public void logMessage(String message) {
        log(SessionLog.FINER, message, (Object[])null, null, false);
    }

    /**
     * INTERNAL:
     * A call back to do session specific preparation of a query.
     * <p>
     * The call back occurs soon before we clone the query for execution,
     * meaning that if this method needs to clone the query then the caller will
     * determine that it doesn't need to clone the query itself.
     */
    public DatabaseQuery prepareDatabaseQuery(DatabaseQuery query) {
        if (!isUnitOfWork() && query.isObjectLevelReadQuery()) {
            return ((ObjectLevelReadQuery)query).prepareOutsideUnitOfWork(this);
        } else {
            return query;
        }
    }
    
    /**
     * INTERNAL:
     * Allows for EJBQL strings to be parsed and added as named queries. Should
     * be called after descriptors have been initialized to ensure all mappings
     * exist.
     */
    public void processEJBQLQueries() {
        List queries = getEjbqlPlaceHolderQueries();
        processEJBQLQueries(queries);
        queries.clear();
    }
    
        /**
     * INTERNAL:
     * Allows for EJBQL strings to be parsed and added as named queries. Should
     * be called after descriptors have been initialized to ensure all mappings
     * exist.
     */
    public void processEJBQLQueries(List queries) {
        for (Iterator iterator = queries.iterator(); iterator.hasNext();) {
            EJBQLPlaceHolderQuery existingQuery = (EJBQLPlaceHolderQuery)iterator.next();
            this.addQuery(existingQuery.processEjbQLQuery(this));
        }
    }

    /**
     * PUBLIC:
     * Read all of the instances of the class from the database.
     * This operation can be customized through using a ReadAllQuery,
     * or through also passing in a selection criteria.
     *
     * @see ReadAllQuery
     * @see #readAllObjects(Class, Expression)
     */
    public Vector readAllObjects(Class domainClass) throws DatabaseException {
        ReadAllQuery query = new ReadAllQuery();
        query.setReferenceClass(domainClass);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read all of the instances of the class from the database return through execution the SQL string.
     * The SQL string must be a valid SQL select statement or selecting stored procedure call.
     * This operation can be customized through using a ReadAllQuery.
     *
     * @see ReadAllQuery
     */
    public Vector readAllObjects(Class domainClass, String sqlString) throws DatabaseException {
        ReadAllQuery query = new ReadAllQuery();
        query.setReferenceClass(domainClass);
        query.setSQLString(sqlString);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read all the instances of the class from the database returned through execution the Call string.
     * The Call can be an SQLCall or EJBQLCall.
     *
     * example: session.readAllObjects(Employee.class, new SQLCall("SELECT * FROM EMPLOYEE"));
     * @see Call
     */
    public Vector readAllObjects(Class referenceClass, Call aCall) throws DatabaseException {
        ReadAllQuery raq = new ReadAllQuery();
        raq.setReferenceClass(referenceClass);
        raq.setCall(aCall);
        return (Vector)executeQuery(raq);
    }

    /**
     * PUBLIC:
     * Read all of the instances of the class from the database matching the given expression.
     * This operation can be customized through using a ReadAllQuery.
     *
     * @see ReadAllQuery
     */
    public Vector readAllObjects(Class domainClass, Expression expression) throws DatabaseException {
        ReadAllQuery query = new ReadAllQuery();
        query.setReferenceClass(domainClass);
        query.setSelectionCriteria(expression);
        return (Vector)executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database.
     * This operation can be customized through using a ReadObjectQuery,
     * or through also passing in a selection criteria.
     *
     * @see ReadObjectQuery
     * @see #readAllObjects(Class, Expression)
     */
    public Object readObject(Class domainClass) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database return through execution the SQL string.
     * The SQL string must be a valid SQL select statement or selecting stored procedure call.
     * This operation can be customized through using a ReadObjectQuery.
     *
     * @see ReadObjectQuery
     */
    public Object readObject(Class domainClass, String sqlString) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        query.setSQLString(sqlString);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database returned through execution the Call string.
     * The Call can be an SQLCall or EJBQLCall.
     *
     * example: session.readObject(Employee.class, new SQLCall("SELECT * FROM EMPLOYEE"));
     * @see SQLCall
     * @see EJBQLCall
     */
    public Object readObject(Class domainClass, Call aCall) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        query.setCall(aCall);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Read the first instance of the class from the database matching the given expression.
     * This operation can be customized through using a ReadObjectQuery.
     *
     * @see ReadObjectQuery
     */
    public Object readObject(Class domainClass, Expression expression) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setReferenceClass(domainClass);
        query.setSelectionCriteria(expression);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Use the example object to consruct a read object query by the objects primary key.
     * This will read the object from the database with the same primary key as the object
     * or null if no object is found.
     */
    public Object readObject(Object object) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSelectionObject(object);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object and of all of its private parts from the database.
     * The object will be pessimisticly locked on the database for the duration of the transaction.
     * If the object is already locked this method will wait until the lock is released.
     * A no wait option is available through setting the lock mode.
     * @see #refreshAndLockObject(Object, lockMode)
     */
    public Object refreshAndLockObject(Object object) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSelectionObject(object);
        query.refreshIdentityMapResult();
        query.cascadePrivateParts();
        query.setLockMode(ObjectBuildingQuery.LOCK);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object and of all of its private parts from the database.
     * The object will be pessimisticly locked on the database for the duration of the transaction.
     * <p>Lock Modes: ObjectBuildingQuery.NO_LOCK, LOCK, LOCK_NOWAIT
     */
    public Object refreshAndLockObject(Object object, short lockMode) throws DatabaseException {
        ReadObjectQuery query = new ReadObjectQuery();
        query.setSelectionObject(object);
        query.refreshIdentityMapResult();
        query.cascadePrivateParts();
        query.setLockMode(lockMode);
        return executeQuery(query);
    }

    /**
     * PUBLIC:
     * Refresh the attributes of the object and of all of its private parts from the database.
     * This can be used to ensure the object is up to date with the database.
     * Caution should be used when using this to make sure the application has no un commited
     * changes to the object.
     */
    public Object refreshObject(Object object) throws DatabaseException {
        return refreshAndLockObject(object, ObjectBuildingQuery.NO_LOCK);
    }

    /**
     * PUBLIC:
     * Release the session.
     * This does nothing by default, but allows for other sessions such as the ClientSession to do something.
     */
    public void release() {
    }

    /**
     * INTERNAL:
     * Release the unit of work, if lazy release the connection.
     */
    public void releaseUnitOfWork(UnitOfWorkImpl unitOfWork) {
        // Nothing is required by default, allow subclasses to do cleanup.
        setNumberOfActiveUnitsOfWork(getNumberOfActiveUnitsOfWork() - 1);
    }


    /**
     * PUBLIC:
     * Remove the user defined property.
     */
    public void removeProperty(String property) {
        getProperties().remove(property);
    }

    /**
     * PUBLIC:
     * Remove all queries with the given queryName regardless of the argument types.
     *
     * @see #removeQuery(String, Vector)
     */
    public void removeQuery(String queryName) {
        getQueries().remove(queryName);
    }

    /**
     * PUBLIC:
     * Remove the specific query with the given queryName and argumentTypes.
     */
    public void removeQuery(String queryName, Vector argumentTypes) {
        Vector queries = (Vector)getQueries().get(queryName);
        if (queries == null) {
            return;
        } else {
            DatabaseQuery query = null;
            for (Enumeration enumtr = queries.elements(); enumtr.hasMoreElements();) {
                query = (DatabaseQuery)enumtr.nextElement();
                if (Helper.areTypesAssignable(argumentTypes, query.getArgumentTypes())) {
                    break;
                }
            }
            if (query != null) {
                queries.remove(query);
            }
        }
    }

    /**
     * PROTECTED:
     * Attempts to rollback the running internally started external transaction.
     * Returns true only in one case -
     * extenal transaction has been internally rolled back during this method call:
     * wasJTSTransactionInternallyStarted()==true in the beginning of this method and
     * wasJTSTransactionInternallyStarted()==false in the end of this method.
     */
    protected boolean rollbackExternalTransaction() {
        boolean externalTransactionHasRolledBack = false;
        if (hasExternalTransactionController() && wasJTSTransactionInternallyStarted()) {
            try {
                getExternalTransactionController().rollbackTransaction(this);
            } catch (RuntimeException exception) {
                handleException(exception);
            }
            if (!wasJTSTransactionInternallyStarted()) {
                externalTransactionHasRolledBack = true;
                log(SessionLog.FINER, SessionLog.TRANSACTION, "external_transaction_has_rolled_back_internally");
            }
        }
        return externalTransactionHasRolledBack;
    }

    /**
     * PUBLIC:
     * Rollback the active database transaction.
     * This allows a group of database modification to be commited or rolledback as a unit.
     * All writes/deletes will be sent to the database be will not be visible to other users until commit.
     * Although databases do not allow nested transaction,
     * TopLink supports nesting through only committing to the database on the outer commit.
     *
     * @exception DatabaseException if the database connection is lost or the rollback fails.
     * @exception ConcurrencyException if this session is not within a transaction.
     */
    public void rollbackTransaction() throws DatabaseException, ConcurrencyException {
        // Ensure release of mutex and call subclass specific release.
        try {
            if (!getTransactionMutex().isNested()) {
                getEventManager().preRollbackTransaction();
                basicRollbackTransaction();
                getEventManager().postRollbackTransaction();
            }
        } finally {
            getTransactionMutex().release();

            // If there is no db transaction in progress
            // if there is an active external transaction
            // which was started internally - it should be rolled back internally, too.
            if (!isInTransaction()) {
                rollbackExternalTransaction();
            }
        }
    }

    /**
     * INTERNAL:
     * Set the accessor.
     */
    public void setAccessor(Accessor accessor) {
        this.accessor = accessor;
    }

    /**
     * INTERNAL:
     * Allow the session to be used from a session broker.
     */
    public void setBroker(AbstractSession broker) {
        this.broker = broker;
    }

    /**
     * INTERNAL:
     * The commit manager is used to resolve referncial integrity on commits of multiple objects.
     */
    public void setCommitManager(CommitManager commitManager) {
        this.commitManager = commitManager;
    }

    /**
     * INTERNAL:
     * Set the event manager.
     * The event manager can be used to register for various session events.
     */
    public void setEventManager(SessionEventManager eventManager) {
        if (eventManager != null) {
            this.eventManager = eventManager;
        } else {
            this.eventManager = new SessionEventManager();
        }
        this.eventManager.setSession(this);
    }

    /**
     * PUBLIC:
     * Set the exceptionHandler.
     * Exception handler can catch errors that occur on queries or during database access.
     */
    public void setExceptionHandler(ExceptionHandler exceptionHandler) {
        this.exceptionHandler = exceptionHandler;
    }

    /**
     * Used for JTS integration internally by ServerPlatform.
     */
    public void setExternalTransactionController(ExternalTransactionController externalTransactionController) {
        this.externalTransactionController = externalTransactionController;
        if (externalTransactionController == null) {
            return;
        }
        externalTransactionController.setSession(this);
    }

    /**
     * PUBLIC:
     * set the integrityChecker. IntegrityChecker holds all the Descriptor Exceptions.
     */
    public void setIntegrityChecker(IntegrityChecker integrityChecker) {
        this.integrityChecker = integrityChecker;
    }

    /**
     * PUBLIC:
     * Set the writer to which an accessor writes logged messages and SQL.
     * If not set, this reference defaults to a writer on System.out.
     *
     * @see #setSessionLog(SessionLog)
     */
    public void setLog(Writer log) {
        getSessionLog().setWriter(log);
    }

    /**
     * PUBLIC:
     * Set the login.
     */
    public void setLogin(DatabaseLogin login) {
        setDatasourceLogin(login);
    }

    /**
     * PUBLIC:
     * Set the login.
     */
    public void setLogin(Login login) {
        setDatasourceLogin(login);
    }

    /**
     * PUBLIC:
     * Set the login.
     */
    public void setDatasourceLogin(Login login) {
        getProject().setDatasourceLogin(login);
    }

    /**
     * PUBLIC:
     * Set the name of the session.
     * This is used with the session broker.
     */
    public void setName(String name) {
        this.name = name;
    }

    protected void setNumberOfActiveUnitsOfWork(int numberOfActiveUnitsOfWork) {
        this.numberOfActiveUnitsOfWork = numberOfActiveUnitsOfWork;
    }

    /**
     * PUBLIC:
     * Set the profiler for the session.
     * This allows for performance operations to be profiled.
     */
    public void setProfiler(SessionProfiler profiler) {
        this.profiler = profiler;
        if (profiler != null) {
            profiler.setSession(this);
            setIsInProfile(getProfiler().getProfileWeight() != SessionProfiler.NONE);
            // Clear cached flag that bybasses the profiler check.
            getIdentityMapAccessorInstance().getIdentityMapManager().clearCacheAccessPreCheck();
        } else {
            setIsInProfile(false);
        }
    }

    /**
     * INTERNAL:
     * Set the project, the project holds configuartion information including the descriptors.
     */
    public void setProject(oracle.toplink.essentials.sessions.Project project) {
        this.project = project;
    }

    /**
     * INTERNAL:
     * Set the user defined properties.
     */
    public void setProperties(Map properties) {
        this.properties = properties;
    }

    /**
     * PUBLIC:
     * Allow for user defined properties.
     */
    public void setProperty(String propertyName, Object propertyValue) {
        getProperties().put(propertyName, propertyValue);
    }

    protected void setQueries(Hashtable queries) {
        this.queries = queries;
    }

    /**
     * PUBLIC:
     * Set the session log to which an accessor logs messages and SQL.
     * If not set, this will default to a session log on a writer on System.out.
     * To enable logging, log level can not be OFF.
     * Also set a backpointer to this session in SessionLog. To avoid a sessionLog
     * being shared by more than one session, it needs to be cloned.
     *
     * @see #logMessage(String)
     */
    public void setSessionLog(SessionLog sessionLog) {
        this.sessionLog = (SessionLog)((AbstractSessionLog)sessionLog).clone();
        if (this.sessionLog != null) {
            this.sessionLog.setSession(this);
        }
    }

    protected void setTransactionMutex(ConcurrencyManager transactionMutex) {
        this.transactionMutex = transactionMutex;
    }

    /**
     * INTERNAL:
     * Return if a JTS transaction was started by the session.
     * The session will start a JTS transaction if a unit of work or transaction is begun without a JTS transaction present.
     */
    public void setWasJTSTransactionInternallyStarted(boolean wasJTSTransactionInternallyStarted) {
        this.wasJTSTransactionInternallyStarted = wasJTSTransactionInternallyStarted;
    }

    /**
     * PUBLIC:
     * Return if logging is enabled (false if log level is OFF)
     */
    public boolean shouldLogMessages() {
        if (getLogLevel(null) == SessionLog.OFF) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * INTERNAL:
     * Start the operation timing.
     */
    public void startOperationProfile(String operationName) {
        if (isInProfile()) {
            getProfiler().startOperationProfile(operationName);
        }
    }

    /**
     * Print the connection status with the session.
     */
    public String toString() {
        StringWriter writer = new StringWriter();
        writer.write(getSessionTypeString() + "(" + Helper.cr() + "\t" + getAccessor() + Helper.cr() + "\t" + getDatasourcePlatform() + ")");
        return writer.toString();
    }

    /**
     * INTERNAL:
     * Unwrap the object if required.
     * This is used for the wrapper policy support and EJB.
     */
    public Object unwrapObject(Object proxy) {
        return getDescriptor(proxy).getObjectBuilder().unwrapObject(proxy, this);
    }

    /**
     * PUBLIC:
     * Update the object and all of its privately owned parts in the database.
     * Update should only be used if the application knows that the object is new,
     * otherwise writeObject should be used.
     * The update operation can be customized through using an update query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     *
     * @see UpdateObjectQuery
     * @see #writeObject(Object)
     */
    public Object updateObject(Object domainObject) throws DatabaseException, OptimisticLockException {
        UpdateObjectQuery query = new UpdateObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * INTERNAL:
     * This method will be used to update the query with any settings required
     * For this session. It can also be used to validate execution.
     */
    public void validateQuery(DatabaseQuery query) {
        // a no-op for this class
    }

    /**
     * TESTING:
     * This is used by testing code to ensure that a deletion was successful.
     */
    public boolean verifyDelete(Object domainObject) {
        ObjectBuilder builder = getDescriptor(domainObject).getObjectBuilder();
        Object implementation = builder.unwrapObject(domainObject, this);

        return builder.verifyDelete(implementation, this);
    }

    /**
     * INTERNAL:
     * Return if a JTS transaction was started by the session.
     * The session will start a JTS transaction if a unit of work or transaction is begun without a JTS transaction present.
     */
    public boolean wasJTSTransactionInternallyStarted() {
        return wasJTSTransactionInternallyStarted;
    }

    /**
     * INTERNAL:
     * Wrap the object if required.
     * This is used for the wrapper policy support and EJB.
     */
    public Object wrapObject(Object implementation) {
        return getDescriptor(implementation).getObjectBuilder().wrapObject(implementation, this);
    }

    /**
     * INTERNAL:
     * Write all of the objects and all of their privately owned parts in the database.
     * The allows for a group of new objects to be commited as a unit.
     * The objects will be commited through a single transactions and any
     * foreign keys/circular references between the objects will be resolved.
     */
    protected void writeAllObjects(HardIdentityHashtable domainObjects) throws DatabaseException, OptimisticLockException {
        getCommitManager().commitAllObjects(domainObjects);
    }

    /**
     * INTERNAL:
     * Write all of the objects and all of their privately owned parts in the database.
     * The allows for a group of new objects to be commited as a unit.
     * The objects will be commited through a single transactions and any
     * foreign keys/circular references between the objects will be resolved.
     */
    protected void writeAllObjectsWithChangeSet(UnitOfWorkChangeSet uowChangeSet) throws DatabaseException, OptimisticLockException {
        getCommitManager().commitAllObjectsWithChangeSet(uowChangeSet);
    }

    /**
     * PUBLIC:
     * Write the object and all of its privately owned parts in the database.
     * Write will determine if an insert or an update should be done,
     * it may go to the database to determine this (by default will check the identity map).
     * The write operation can be customized through using an write query.
     *
     * @exception DatabaseException if an error occurs on the database,
     * these include constraint violations, security violations and general database erros.
     * @exception OptimisticLockException if the object's descriptor is using optimistic locking and
     * the object has been updated or deleted by another user since it was last read.
     *
     * @see WriteObjectQuery
     * @see #insertObject(Object)
     * @see #updateObject(Object)
     */
    public Object writeObject(Object domainObject) throws DatabaseException, OptimisticLockException {
        WriteObjectQuery query = new WriteObjectQuery();
        query.setObject(domainObject);
        return executeQuery(query);
    }

    /**
     * INTERNAL:
     * This method notifies the accessor that a particular sets of writes has
     * completed. This notification can be used for such thing as flushing the
     * batch mechanism
     */
    public void writesCompleted() {
        getAccessor().writesCompleted(this);
    }

    /**
     * PUBLIC:
     * <p>
     * Return the log level
     * </p><p>
     *
     * @return the log level
     * </p><p>
     * @param category the string representation of a TopLink category, e.g. "sql", "transaction" ...
     * </p>
     */
    public int getLogLevel(String category) {
        return getSessionLog().getLevel(category);
    }

    /**
     * PUBLIC:
     * <p>
     * Return the log level
     * </p><p>
     * @return the log level
     * </p>
     */
    public int getLogLevel() {
        return getSessionLog().getLevel();
    }

    /**
     * PUBLIC:
     * <p>
     * Set the log level
     * </p><p>
     *
     * @param level the new log level
     * </p>
     */
    public void setLogLevel(int level) {
        getSessionLog().setLevel(level);
    }

    /**
     * PUBLIC:
     * <p>
     * Check if a message of the given level would actually be logged.
     * </p><p>
     *
     * @return true if the given message level will be logged
     * </p><p>
     * @param level the log request level
     * @param category the string representation of a TopLink category
     * </p>
     */
    public boolean shouldLog(int Level, String category) {
        return getSessionLog().shouldLog(Level, category);
    }

    /**
     * PUBLIC:
     * <p>
     * Log a message with level and category that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p>
     */
    public void log(int level, String category, String message) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, (Object[])null);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and a parameter that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p><p>
     * @param param a parameter of the message
     * </p>
     */
    public void log(int level, String category, String message, Object param) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, new Object[] { param });
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and two parameters that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p><p>
     * @param param1 a parameter of the message
     * </p><p>
     * @param param2 second parameter of the message
     * </p>
     */
    public void log(int level, String category, String message, Object param1, Object param2) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, new Object[] { param1, param2 });
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and three parameters that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p><p>
     * @param param1 a parameter of the message
     * </p><p>
     * @param param2 second parameter of the message
     * </p><p>
     * @param param3 third parameter of the message
     * </p>
     */
    public void log(int level, String category, String message, Object param1, Object param2, Object param3) {
        if (!shouldLog(level, category)) {
            return;
        }
        log(level, category, message, new Object[] { param1, param2, param3 });
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category and an array of parameters that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p><p>
     * @param params array of parameters to the message
     * </p>
     */
    public void log(int level, String category, String message, Object[] params) {
        log(level, category, message, params, null);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category, parameters and accessor that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param params array of parameters to the message
     * </p><p>
     * @param accessor the connection that generated the log entry
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p>
     */
    public void log(int level, String category, String message, Object[] params, Accessor accessor) {
        log(level, category, message, params, accessor, true);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, category, parameters and accessor. shouldTranslate determines if the message needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param params array of parameters to the message
     * </p><p>
     * @param accessor the connection that generated the log entry
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p><p>
     * @param shouldTranslate true if the message needs to be translated.
     * </p>
     */
    public void log(int level, String category, String message, Object[] params, Accessor accessor, boolean shouldTranslate) {
        if (shouldLog(level, category)) {
            startOperationProfile(SessionProfiler.Logging);
            log(new SessionLogEntry(level, category, this, message, params, accessor, shouldTranslate));
            endOperationProfile(SessionProfiler.Logging);
        }
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, parameters and accessor that needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param params array of parameters to the message
     * </p><p>
     * @param accessor the connection that generated the log entry
     * </p>
     */
    public void log(int level, String message, Object[] params, Accessor accessor) {
        log(level, message, params, accessor, true);
    }

    /**
     * INTERNAL:
     * <p>
     * Log a message with level, parameters and accessor. shouldTranslate determines if the message needs to be translated.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param message the string message
     * </p><p>
     * @param params array of parameters to the message
     * </p><p>
     * @param accessor the connection that generated the log entry
     * </p><p>
     * @param shouldTranslate true if the message needs to be translated.
     * </p>
     */
    public void log(int level, String message, Object[] params, Accessor accessor, boolean shouldTranslate) {
        if (shouldLog(level, null)) {
            startOperationProfile(SessionProfiler.Logging);
            log(new SessionLogEntry(level, this, message, params, accessor, shouldTranslate));
            endOperationProfile(SessionProfiler.Logging);
        }
    }

    /**
     * PUBLIC:
     * <p>
     * Log a throwable with level and category.
     * </p><p>
     *
     * @param level the log request level value
     * </p><p>
     * @param category the string representation of a TopLink category.
     * </p><p>
     * @param throwable a Throwable
     * </p>
     */
    public void logThrowable(int level, String category, Throwable throwable) {
        // Must not create the log if not logging as is a performance issue.
        if (shouldLog(level, category)) {
            startOperationProfile(SessionProfiler.Logging);
            log(new SessionLogEntry(this, level, category, throwable));
            endOperationProfile(SessionProfiler.Logging);
        }
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a severe level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void severe(String message, String category) {
        log(SessionLog.SEVERE, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a warning level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void warning(String message, String category) {
        log(SessionLog.WARNING, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a info level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void info(String message, String category) {
        log(SessionLog.INFO, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a config level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void config(String message, String category) {
        log(SessionLog.CONFIG, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a fine level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void fine(String message, String category) {
        log(SessionLog.FINE, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a finer level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void finer(String message, String category) {
        log(SessionLog.FINER, category, message);
    }

    /**
     * PUBLIC:
     * <p>
     * This method is called when a finest level message needs to be logged.
     * The message will be translated
     * </p><p>
     *
     * @param message the message key
     * </p>
     */
    public void finest(String message, String category) {
        log(SessionLog.FINEST, category, message);
    }

    /**
     * PUBLIC:
     * Allow any SEVERE level exceptions that occur within TopLink to be logged and handled by the exception handler.
     */
    public Object handleSevere(RuntimeException exception) throws RuntimeException {
        logThrowable(SessionLog.SEVERE, null, exception);
        if (hasExceptionHandler()) {
            return getExceptionHandler().handleException(exception);
        } else {
            throw exception;
        }
    }
}


/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * // Copyright (c) 1998, 2007, Oracle. All rights reserved.
 *
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License"). You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license." If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above. However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
package oracle.toplink.essentials.mappings;

import java.util.*;
import oracle.toplink.essentials.exceptions.*;
import oracle.toplink.essentials.expressions.*;
import oracle.toplink.essentials.internal.descriptors.*;
import oracle.toplink.essentials.internal.helper.*;
import oracle.toplink.essentials.internal.identitymaps.*;
import oracle.toplink.essentials.internal.queryframework.*;
import oracle.toplink.essentials.internal.sessions.*;
import oracle.toplink.essentials.sessions.DatabaseRecord;
import oracle.toplink.essentials.descriptors.DescriptorEvent;
import oracle.toplink.essentials.descriptors.DescriptorEventManager;
import oracle.toplink.essentials.queryframework.*;
import oracle.toplink.essentials.indirection.ValueHolderInterface;
import oracle.toplink.essentials.internal.sessions.AbstractRecord;
import oracle.toplink.essentials.internal.sessions.UnitOfWorkImpl;
import oracle.toplink.essentials.internal.sessions.AbstractSession;
import oracle.toplink.essentials.descriptors.ClassDescriptor;

/**
 * <p><b>Purpose</b>: The aggregate collection mapping is used to represent the aggregate relationship between a single
 * source object and a collection of target objects. The target objects cannot exist without the existence of the
 * source object (privately owned)
 * Unlike the normal aggregate mapping, there is a target table being mapped from the target objects.
 * Unlike normal 1:m mapping, there is no 1:1 back reference mapping, as foreign key constraints have been resolved by the aggregation.
 *
 * @author King (Yaoping) Wang
 * @since TOPLink/Java 3.0
 */
public class AggregateCollectionMapping extends CollectionMapping implements RelationalMapping {

    /** This is a key in the target table which is a foreign key in the target table. */
    protected transient Vector<DatabaseField> targetForeignKeyFields;

    /** This is a primary key in the source table that is used as foreign key in the target table */
    protected transient Vector<DatabaseField> sourceKeyFields;

    /** Foreign keys in the target table to the related keys in the source table */
    protected transient Map<DatabaseField, DatabaseField> targetForeignKeyToSourceKeys;

    /**
     * PUBLIC:
     * Default constructor.
     */
    public AggregateCollectionMapping() {
        this.targetForeignKeyToSourceKeys = new HashMap(5);
        this.sourceKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.targetForeignKeyFields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        this.deleteAllQuery = new DeleteAllQuery();
        //aggregates should always cascade all operations
        this.setCascadeAll(true);
    }

    /**
     * INTERNAL:
     */
    public boolean isRelationalMapping() {
        return true;
    }

    /**
     * PUBLIC:
     * Define the target foreign key relationship in the 1-M aggregate collection mapping.
     * Both the target foreign key field name and the source primary key field name must be specified.
     */
    public void addTargetForeignKeyFieldName(String targetForeignKey, String sourceKey) {
        getTargetForeignKeyFields().addElement(new DatabaseField(targetForeignKey));
        getSourceKeyFields().addElement(new DatabaseField(sourceKey));
    }

    /**
     * INTERNAL:
     * Used during building the backup shallow copy to copy the vector without re-registering the target objects.
     */
    public Object buildBackupCloneForPartObject(Object attributeValue, Object clone, Object backup, UnitOfWorkImpl unitOfWork) {
        ContainerPolicy containerPolicy = getContainerPolicy();
        if (attributeValue == null) {
            return containerPolicy.containerInstance(1);
        }

        Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue));
        synchronized (attributeValue) {
            for (Object valuesIterator = containerPolicy.iteratorFor(attributeValue);
                     containerPolicy.hasNext(valuesIterator);) {
                Object cloneValue = buildElementBackupClone(containerPolicy.next(valuesIterator, unitOfWork), unitOfWork);
                containerPolicy.addInto(cloneValue, clonedAttributeValue, unitOfWork);
            }
        }
        return clonedAttributeValue;
    }

    /**
     * INTERNAL:
     * Require for cloning, the part must be cloned.
     * Ignore the objects, use the attribute value.
     * this is identical to the super class except that the element must be added to the new
     * aggregates collection so that the referenced objects will be clonned correctly
     */
    public Object buildCloneForPartObject(Object attributeValue, Object original, Object clone, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        ContainerPolicy containerPolicy = getContainerPolicy();
        if (attributeValue == null) {
            return containerPolicy.containerInstance(1);
        }
        Object clonedAttributeValue = containerPolicy.containerInstance(containerPolicy.sizeFor(attributeValue));

        // I need to synchronize here to prevent the collection from changing while I am cloning it.
        // This will occur when I am merging into the cache and I am instantiating a UOW valueHolder at the same time
        // I can not synchronize around the clone, as this will cause deadlocks, so I will need to copy the collection then create the clones
        // I will use a temporary collection to help speed up the process
        Object temporaryCollection = null;
        synchronized (attributeValue) {
            temporaryCollection = containerPolicy.cloneFor(attributeValue);
        }
        for (Object valuesIterator = containerPolicy.iteratorFor(temporaryCollection);
                 containerPolicy.hasNext(valuesIterator);) {
            Object originalElement = containerPolicy.next(valuesIterator, unitOfWork);

            //need to add to aggregate list in the case that there are related objects.
            if (unitOfWork.isOriginalNewObject(original)) {
                unitOfWork.addNewAggregate(originalElement);
            }
            Object cloneValue = buildElementClone(originalElement, unitOfWork, isExisting);
            containerPolicy.addInto(cloneValue, clonedAttributeValue, unitOfWork);
        }
        return clonedAttributeValue;
    }

    /**
     * INTERNAL:
     * Clone the aggregate collection, if necessary.
     */
    protected Object buildElementBackupClone(Object element, UnitOfWorkImpl unitOfWork) {
        // Do not clone for read-only.
        if (unitOfWork.isClassReadOnly(element.getClass())) {
            return element;
        }

        ClassDescriptor aggregateDescriptor = getReferenceDescriptor(element.getClass(), unitOfWork);
        Object clonedElement = aggregateDescriptor.getObjectBuilder().buildBackupClone(element, unitOfWork);

        return clonedElement;
    }

    /**
     * INTERNAL:
     * Clone the aggregate collection, if necessary.
     */
    protected Object buildElementClone(Object element, UnitOfWorkImpl unitOfWork, boolean isExisting) {
        // Do not clone for read-only.
        if (unitOfWork.isClassReadOnly(element.getClass())) {
            return element;
        }

        ClassDescriptor aggregateDescriptor = getReferenceDescriptor(element.getClass(), unitOfWork);

        // bug 2612602 as we are building the working copy make sure that we call to correct clone method.
        Object clonedElement = aggregateDescriptor.getObjectBuilder().instantiateWorkingCopyClone(element, unitOfWork);
        aggregateDescriptor.getObjectBuilder().populateAttributesForClone(element, clonedElement, unitOfWork);
        // CR 4155 add the originals to the UnitOfWork so that we can find it later in the merge
        // as aggregates have no identity. If we don't do this we will loose indirection information.
        unitOfWork.getCloneToOriginals().put(clonedElement, element);
        return clonedElement;
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadeRegisterNewIfRequired(Object object, UnitOfWorkImpl uow, HardIdentityHashtable visitedObjects){
        //aggregate objects are not registered but their mappings should be.
        Object cloneAttribute = null;
        cloneAttribute = getAttributeValueFromObject(object);
        if ((cloneAttribute == null) || (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            return;
        }

        ObjectBuilder builder = null;
        ContainerPolicy cp = getContainerPolicy();
        Object cloneObjectCollection = null;
        cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow);
        Object cloneIter = cp.iteratorFor(cloneObjectCollection);
        while (cp.hasNext(cloneIter)) {
            Object nextObject = cp.next(cloneIter, uow);
            if (nextObject != null && (! visitedObjects.contains(nextObject))){
                visitedObjects.put(nextObject, nextObject);
                builder = getReferenceDescriptor(nextObject.getClass(), uow).getObjectBuilder();
                builder.cascadeRegisterNewForCreate(nextObject, uow, visitedObjects);
            }
        }
    }

    /**
     * INTERNAL:
     * Cascade registerNew for Create through mappings that require the cascade
     */
    public void cascadePerformRemoveIfRequired(Object object, UnitOfWorkImpl uow, HardIdentityHashtable visitedObjects){
        //aggregate objects are not registered but their mappings should be.
        Object cloneAttribute = null;
        cloneAttribute = getAttributeValueFromObject(object);
        if ((cloneAttribute == null)) {
            return;
        }

        ObjectBuilder builder = null;
        ContainerPolicy cp = getContainerPolicy();
        Object cloneObjectCollection = null;
        cloneObjectCollection = getRealCollectionAttributeValueFromObject(object, uow);
        Object cloneIter = cp.iteratorFor(cloneObjectCollection);
        while (cp.hasNext(cloneIter)) {
            Object nextObject = cp.next(cloneIter, uow);
            if (nextObject != null && ( ! visitedObjects.contains(nextObject) ) ){
                visitedObjects.put(nextObject, nextObject);
                builder = getReferenceDescriptor(nextObject.getClass(), uow).getObjectBuilder();
                builder.cascadePerformRemove(nextObject, uow, visitedObjects);
            }
        }
    }

    /**
     * INTERNAL:
     * The mapping clones itself to create deep copy.
     */
    public Object clone() {
        AggregateCollectionMapping mappingObject = (AggregateCollectionMapping)super.clone();

        mappingObject.setTargetForeignKeyToSourceKeys(new HashMap(getTargetForeignKeyToSourceKeys()));

        return mappingObject;
    }

    /**
     * INTERNAL:
     * This method is used to create a change record from comparing two aggregate collections
     * @return ChangeRecord
     */
    public ChangeRecord compareForChange(Object clone, Object backUp, ObjectChangeSet owner, AbstractSession session) {
        Object cloneAttribute = null;
        Object backUpAttribute = null;

        cloneAttribute = getAttributeValueFromObject(clone);

        if ((cloneAttribute != null) && (!getIndirectionPolicy().objectIsInstantiated(cloneAttribute))) {
            //If the clone's valueholder was not triggered then no changes were made.
            return null;
        }
        if (!owner.isNew()) {
            backUpAttribute = getAttributeValueFromObject(backUp);
            if ((backUpAttribute == null) && (cloneAttribute == null)) {
                return null;
            }
            ContainerPolicy cp = getContainerPolicy();
            Object backupCollection = null;
            Object cloneCollection = null;

            cloneCollection = getRealCollectionAttributeValueFromObject(clone, session);
            backupCollection = getRealCollectionAttributeValueFromObject(backUp, session);

            if (cp.sizeFor(backupCollection) != cp.sizeFor(cloneCollection)) {
                return convertToChangeRecord(cloneCollection, owner, session);
            }
            Object cloneIterator = cp.iteratorFor(cloneCollection);
            Object backUpIterator = cp.iteratorFor(backupCollection);
            boolean change = false;

            // For bug 2863721 must use a different UnitOfWorkChangeSet as here just
            // seeing if changes are needed. If changes are needed then a
            // real changeSet will be created later.
            UnitOfWorkChangeSet uowComparisonChangeSet = new UnitOfWorkChangeSet();
            while (cp.hasNext(cloneIterator)) {
                Object cloneObject = cp.next(cloneIterator, session);

                // For CR#2285 assume that if null is added the collection has changed.
                if (cloneObject == null) {
                    change = true;
                    break;
                }
                Object backUpObject = null;
                if (cp.hasNext(backUpIterator)) {
                    backUpObject = cp.next(backUpIterator, session);
                } else {
                    change = true;
                    break;
                }
                if (cloneObject.getClass().equals(backUpObject.getClass())) {
                    ObjectBuilder builder = getReferenceDescriptor(cloneObject.getClass(), session).getObjectBuilder();
                    ObjectChangeSet initialChanges = builder.createObjectChangeSet(cloneObject, uowComparisonChangeSet, owner.isNew(), session);

                    //compare for changes will return null if no change is detected and I need to remove the changeSet
                    ObjectChangeSet changes = builder.compareForChange(cloneObject, backUpObject, uowComparisonChangeSet, session);
                    if (changes != null) {
                        change = true;
                        break;
                    }
                } else {
                    change = true;
                    break;
                }
            }
            if ((change == true) || (cp.hasNext(backUpIterator))) {
                return convertToChangeRecord(cloneCollection, owner, session);
            } else {
                return null;
            }
        }

        return convertToChangeRecord(getRealCollectionAttributeValueFromObject(clone, session), owner, session);
    }

    /**
     * INTERNAL:
     * Compare the attributes belonging to this mapping for the objects.
     */
    public boolean compareObjects(Object firstObject, Object secondObject, AbstractSession session) {
        Object firstCollection = getRealCollectionAttributeValueFromObject(firstObject, session);
        Object secondCollection = getRealCollectionAttributeValueFromObject(secondObject, session);
        ContainerPolicy containerPolicy = getContainerPolicy();

        if (containerPolicy.sizeFor(firstCollection) != containerPolicy.sizeFor(secondCollection)) {
            return false;
        }

        if (containerPolicy.sizeFor(firstCollection) == 0) {
            return true;
        }

        //iterator the first aggregate collection
        for (Object iterFirst = containerPolicy.iteratorFor(firstCollection);
                 containerPolicy.hasNext(iterFirst);) {
            //fetch the next object from the first iterator.
            Object firstAggregateObject = containerPolicy.next(iterFirst, session);

            //iterator the second aggregate collection
            for (Object iterSecond = containerPolicy.iteratorFor(secondCollection); true;) {
                //fetch the next object from the second iterator.
                Object secondAggregateObject = containerPolicy.next(iterSecond, session);

                //matched object found, break to outer FOR loop
                if (getReferenceDescriptor().getObjectBuilder().compareObjects(firstAggregateObject, secondAggregateObject, session)) {
                    break;
                }

                if (!containerPolicy.hasNext(iterSecond)) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * INTERNAL:
     * This method is used to convert the contents of an aggregateCollection into a
     * changeRecord
     * @return oracle.toplink.essentials.internal.sessions.AggregateCollectionChangeRecord the changerecord representing this AggregateCollectionMapping
     * @param owner oracle.toplink.essentials.internal.sessions.ObjectChangeSet the ChangeSet that uses this record
     * @param cloneCollection Object the collection to convert
     * @param session oracle.toplink.essentials.publicinterface.Session
     */
    protected ChangeRecord convertToChangeRecord(Object cloneCollection, ObjectChangeSet owner, AbstractSession session) {
        ContainerPolicy cp = getContainerPolicy();
        Object cloneIter = cp.iteratorFor(cloneCollection);
        Vector collectionChanges = new Vector(2);
        while (cp.hasNext(cloneIter)) {
            Object aggregateObject = cp.next(cloneIter, session);

            // For CR#2258 quietly ignore nulls inserted into a collection.
            if (aggregateObject != null) {
                ObjectChangeSet changes = getReferenceDescriptor(aggregateObject.getClass(), session).getObjectBuilder().compareForChange(aggregateObject, null, (UnitOfWorkChangeSet)owner.getUOWChangeSet(), session);
                collectionChanges.addElement(changes);
            }
        }

        //cr 3013 Removed if collection is empty return null block, which prevents recording clear() change
        AggregateCollectionChangeRecord changeRecord = new AggregateCollectionChangeRecord(owner);
        changeRecord.setAttribute(getAttributeName());
        changeRecord.setMapping(this);
        changeRecord.setChangedValues(collectionChanges);
        return changeRecord;
    }

    /**
     * To delete all the entries matching the selection criteria from the table stored in the
     * referenced descriptor
     */
    protected void deleteAll(WriteObjectQuery query) throws DatabaseException {
        Object referenceObjects = null;
        if(usesIndirection()) {
           Object attribute = getAttributeAccessor().getAttributeValueFromObject(query.getObject());
           if(attribute == null || !((ValueHolderInterface)attribute).isInstantiated()) {
               // An empty Vector indicates to DeleteAllQuery that no objects should be removed from cache
               referenceObjects = new Vector(0);
           }
        }
        if(referenceObjects == null) {
            referenceObjects = this.getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());
        }
        // Ensure that the query is prepare before cloning.
        ((DeleteAllQuery)getDeleteAllQuery()).executeDeleteAll(query.getSession().getSessionForClass(getReferenceClass()), query.getTranslationRow(), getContainerPolicy().vectorFor(referenceObjects, query.getSession()));
    }

    /**
     * INTERNAL:
     * Execute a descriptor event for the specified event code.
     */
    protected void executeEvent(int eventCode, ObjectLevelModifyQuery query) {
        ClassDescriptor referenceDescriptor = getReferenceDescriptor(query.getObject().getClass(), query.getSession());

        // PERF: Avoid events if no listeners.
        if (referenceDescriptor.getEventManager().hasAnyEventListeners()) {
            referenceDescriptor.getEventManager().executeEvent(new DescriptorEvent(eventCode, query));
        }
    }

    /**
     * INTERNAL:
     * Extract the source primary key value from the target row.
     * Used for batch reading, most following same order and fields as in the mapping.
     */
    protected Vector extractKeyFromTargetRow(AbstractRecord row, AbstractSession session) {
        Vector key = new Vector(getTargetForeignKeyFields().size());

        for (int index = 0; index < getTargetForeignKeyFields().size(); index++) {
            DatabaseField targetField = (DatabaseField)getTargetForeignKeyFields().elementAt(index);
            DatabaseField sourceField = (DatabaseField)getSourceKeyFields().elementAt(index);
            Object value = row.get(targetField);

            // Must ensure the classificatin to get a cache hit.
            try {
                value = session.getDatasourcePlatform().getConversionManager().convertObject(value, getDescriptor().getObjectBuilder().getFieldClassification(sourceField));
            } catch (ConversionException e) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
            }

            key.addElement(value);
        }

        return key;
    }

    /**
     * INTERNAL:
     * Extract the primary key value from the source row.
     * Used for batch reading, most following same order and fields as in the mapping.
     */
    protected Vector extractPrimaryKeyFromRow(AbstractRecord row, AbstractSession session) {
        Vector key = new Vector(getSourceKeyFields().size());

        for (Enumeration fieldEnum = getSourceKeyFields().elements(); fieldEnum.hasMoreElements();) {
            DatabaseField field = (DatabaseField)fieldEnum.nextElement();
            Object value = row.get(field);

            // Must ensure the classificatin to get a cache hit.
            try {
                value = session.getDatasourcePlatform().getConversionManager().convertObject(value, getDescriptor().getObjectBuilder().getFieldClassification(field));
            } catch (ConversionException e) {
                throw ConversionException.couldNotBeConverted(this, getDescriptor(), e);
            }

            key.addElement(value);
        }

        return key;
    }

    /**
     * INTERNAL:
     * return the aggregate databaseRow with the primary keys from the source table and targer table
     */
    public AbstractRecord getAggregateRow(ObjectLevelModifyQuery query, Object object) {
        Vector referenceObjectKeys = getReferenceObjectKeys(query);
        AbstractRecord aggregateRow = new DatabaseRecord();
        Vector keys = getTargetForeignKeyFields();
        for (int keyIndex = 0; keyIndex < keys.size(); keyIndex++) {
            aggregateRow.put(keys.elementAt(keyIndex), referenceObjectKeys.elementAt(keyIndex));
        }
        getReferenceDescriptor(object.getClass(), query.getSession()).getObjectBuilder().buildRow(aggregateRow, object, query.getSession());

        return aggregateRow;
    }

    /**
     * Delete all criteria is created with target foreign keys and source keys.
     * This criteria is then used to delete target records from the table.
     */
    protected Expression getDeleteAllCriteria(AbstractSession session) {
        Expression expression;
        Expression criteria = null;
        Expression builder = new ExpressionBuilder();

        for (Iterator keys = getTargetForeignKeyToSourceKeys().keySet().iterator(); keys.hasNext();) {
            DatabaseField targetForeignKey = (DatabaseField)keys.next();
            DatabaseField sourceKey = (DatabaseField)getTargetForeignKeyToSourceKeys().get(targetForeignKey);

            expression = builder.getField(targetForeignKey).equal(builder.getParameter(sourceKey));

            criteria = expression.and(criteria);
        }

        return criteria;
    }

    /**
     * INTERNAL:
     * for inheritance purpose
     */
    public ClassDescriptor getReferenceDescriptor(Class theClass, AbstractSession session) {
        if (getReferenceDescriptor().getJavaClass().equals(theClass)) {
            return getReferenceDescriptor();
        } else {
            ClassDescriptor subclassDescriptor = session.getDescriptor(theClass);
            if (subclassDescriptor == null) {
                throw DescriptorException.noSubClassMatch(theClass, this);
            } else {
                return subclassDescriptor;
            }
        }
    }

    /**
     * INTERNAL:
     * get reference object keys
     */
    public Vector getReferenceObjectKeys(ObjectLevelModifyQuery query) throws DatabaseException, OptimisticLockException {
        Vector referenceObjectKeys = new Vector(getSourceKeyFields().size());

        //For CR#2587-S.M. For nested aggregate collections the source keys can easily be read from the original query.
        AbstractRecord translationRow = query.getTranslationRow();

        for (Enumeration sourcekeys = getSourceKeyFields().elements();
                 sourcekeys.hasMoreElements();) {
            DatabaseField sourceKey = (DatabaseField)sourcekeys.nextElement();

            // CR#2587. Try first to get the source key from the original query. If that fails try to get it from the object.
            Object referenceKey = null;
            if ((translationRow != null) && (translationRow.containsKey(sourceKey))) {
                referenceKey = translationRow.get(sourceKey);
            } else {
                referenceKey = getDescriptor().getObjectBuilder().extractValueFromObjectForField(query.getObject(), sourceKey, query.getSession());
            }
            referenceObjectKeys.addElement(referenceKey);
        }

        return referenceObjectKeys;
    }

    /**
     * PUBLIC:
     * Return the source key field names associated with the mapping.
     * These are in-order with the targetForeignKeyFieldNames.
     */
    public Vector getSourceKeyFieldNames() {
        Vector fieldNames = new Vector(getSourceKeyFields().size());
        for (Enumeration fieldsEnum = getSourceKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    }

    /**
     * INTERNAL:
     * Return the source key names associated with the mapping
     */
    public Vector<DatabaseField> getSourceKeyFields() {
        return sourceKeyFields;
    }

    /**
     * PUBLIC:
     * Return the target foregin key field names associated with the mapping.
     * These are in-order with the sourceKeyFieldNames.
     */
    public Vector getTargetForeignKeyFieldNames() {
        Vector fieldNames = new Vector(getTargetForeignKeyFields().size());
        for (Enumeration fieldsEnum = getTargetForeignKeyFields().elements();
                 fieldsEnum.hasMoreElements();) {
            fieldNames.addElement(((DatabaseField)fieldsEnum.nextElement()).getQualifiedName());
        }

        return fieldNames;
    }

    /**
     * INTERNAL:
     * Return the target foregin key fields associated with the mapping
     */
    public Vector<DatabaseField> getTargetForeignKeyFields() {
        return targetForeignKeyFields;
    }

    /**
     * INTERNAL:
     */
    public Map<DatabaseField, DatabaseField> getTargetForeignKeyToSourceKeys() {
        return targetForeignKeyToSourceKeys;
    }

    /**
     * INTERNAL:
     * For aggregate collection mapping the reference descriptor is cloned. The cloned descriptor is then
     * assigned primary keys and table names before initialize. Once cloned descriptor is initialized
     * it is assigned as reference descriptor in the aggregate mapping. This is a very specifiec
     * behaviour for aggregate mappings. The original descriptor is used only for creating clones and
     * after that mapping never uses it.
     * Some initialization is done in postInitialize to ensure the target descriptor's references are initialized.
     */
    public void initialize(AbstractSession session) throws DescriptorException {
        super.initialize(session);

        if (!getReferenceDescriptor().isAggregateCollectionDescriptor()) {
            session.getIntegrityChecker().handleError(DescriptorException.referenceDescriptorIsNotAggregateCollection(getReferenceClass().getName(), this));
        }

        if (shouldInitializeSelectionCriteria()) {
            if (isSourceKeySpecified()) {
                initializeTargetForeignKeyToSourceKeys(session);
            } else {
                initializeTargetForeignKeyToSourceKeysWithDefaults(session);
            }

            initializeSelectionCriteria(session);
        }

        // Aggregate 1:m never maintains cache as target objects are aggregates.
        getSelectionQuery().setShouldMaintainCache(false);

        initializeDeleteAllQuery(session);
    }

    /**
     * INTERNAL:
     * For aggregate mapping the reference descriptor is cloned. Also the involved inheritanced descriptor, its childern
     * and parents all need to be cloned. The cloned descriptors are then assigned primary keys and table names before
     * initialize. Once cloned descriptor is initialized it is assigned as reference descriptor in the aggregate mapping.
     * This is a very specifiec behaviour for aggregate mappings. The original descriptor is used only for creating clones
     * and after that mapping never uses it.
     * Some initialization is done in postInitialize to ensure the target descriptor's references are initialized.
     */
    public void initializeChildInheritance(ClassDescriptor parentDescriptor, AbstractSession session) throws DescriptorException {
        //recursive call to the further childern descriptors
        if (parentDescriptor.getInheritancePolicy().hasChildren()) {
            //setFields(clonedChildDescriptor.getFields());
            Vector childDescriptors = parentDescriptor.getInheritancePolicy().getChildDescriptors();
            Vector cloneChildDescriptors = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance();
            for (Enumeration enumtr = childDescriptors.elements(); enumtr.hasMoreElements();) {
                ClassDescriptor clonedChildDescriptor = (ClassDescriptor)((ClassDescriptor)enumtr.nextElement()).clone();

                if (!clonedChildDescriptor.isAggregateCollectionDescriptor()) {
                    session.getIntegrityChecker().handleError(DescriptorException.referenceDescriptorIsNotAggregate(clonedChildDescriptor.getJavaClass().getName(), this));
                }

                clonedChildDescriptor.getInheritancePolicy().setParentDescriptor(parentDescriptor);
                clonedChildDescriptor.preInitialize(session);
                clonedChildDescriptor.initialize(session);
                cloneChildDescriptors.addElement(clonedChildDescriptor);
                initializeChildInheritance(clonedChildDescriptor, session);
            }
            parentDescriptor.getInheritancePolicy().setChildDescriptors(cloneChildDescriptors);
        }
    }

    /**
     * INTERNAL:
     * Initialize delete all query. This query is used to delete the collection of objects from the
     * target table.
     */
    protected void initializeDeleteAllQuery(AbstractSession session) {
        DeleteAllQuery query = (DeleteAllQuery)getDeleteAllQuery();
        query.setReferenceClass(getReferenceClass());
        query.setShouldMaintainCache(false);
        if (!hasCustomDeleteAllQuery()) {
            if (getSelectionCriteria() == null) {
                query.setSelectionCriteria(getDeleteAllCriteria(session));
            } else {
                query.setSelectionCriteria(getSelectionCriteria());
            }
        }
    }

    /**
     * INTERNAL:
     * For aggregate mapping the reference descriptor is cloned. Also the involved inheritanced descriptor, its childern
     * and parents all need to be cloned. The cloned descriptors are then assigned primary keys and table names before
     * initialize. Once cloned descriptor is initialized it is assigned as reference descriptor in the aggregate mapping.
     * This is a very specifiec behaviour for aggregate mappings. The original descriptor is used only for creating clones
     * and after that mapping never uses it.
     * Some initialization is done in postInitialize to ensure the target descriptor's references are initialized.
     */
    public void initializeParentInheritance(ClassDescriptor parentDescriptor, ClassDescriptor childDescriptor, AbstractSession session) throws DescriptorException {
        if (!parentDescriptor.isAggregateCollectionDescriptor()) {
            session.getIntegrityChecker().handleError(DescriptorException.referenceDescriptorIsNotAggregateCollection(parentDescriptor.getJavaClass().getName(), this));
        }

        ClassDescriptor clonedParentDescriptor = (ClassDescriptor)parentDescriptor.clone();

        //recursive call to the further parent descriptors
        if (clonedParentDescriptor.getInheritancePolicy().isChildDescriptor()) {
            ClassDescriptor parentToParentDescriptor = session.getDescriptor(clonedParentDescriptor.getJavaClass());
            initializeParentInheritance(parentToParentDescriptor, parentDescriptor, session);
        }

        Vector childern = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(1);
        childern.addElement(childDescriptor);
        clonedParentDescriptor.getInheritancePolicy().setChildDescriptors(childern);
        clonedParentDescriptor.preInitialize(session);
        clonedParentDescriptor.initialize(session);
    }

    /**
     * INTERNAL:
     * Selection criteria is created with target foreign keys and source keys.
     * This criteria is then used to read records from the target table.
     */
    protected void initializeSelectionCriteria(AbstractSession session) {
        Expression expression;
        Expression criteria;
        Expression builder = new ExpressionBuilder();

        for (Iterator keys = getTargetForeignKeyToSourceKeys().keySet().iterator(); keys.hasNext();) {
            DatabaseField targetForeignKey = (DatabaseField)keys.next();
            DatabaseField sourceKey = (DatabaseField)getTargetForeignKeyToSourceKeys().get(targetForeignKey);

            expression = builder.getField(targetForeignKey).equal(builder.getParameter(sourceKey));

            criteria = expression.and(getSelectionCriteria());
            setSelectionCriteria(criteria);
        }
    }

    /**
     * INTERNAL:
     * The foreign keys and the primary key names are converted to DatabaseFields and stored.
     */
    protected void initializeTargetForeignKeyToSourceKeys(AbstractSession session) throws DescriptorException {
        if (getTargetForeignKeyFields().isEmpty()) {
            throw DescriptorException.noTargetForeignKeysSpecified(this);
        }

        for (Enumeration keys = getTargetForeignKeyFields().elements(); keys.hasMoreElements();) {
            DatabaseField foreignKeyfield = (DatabaseField)keys.nextElement();
            getReferenceDescriptor().buildField(foreignKeyfield);
        }

        for (Enumeration keys = getSourceKeyFields().elements(); keys.hasMoreElements();) {
            DatabaseField sourceKeyfield = (DatabaseField)keys.nextElement();
            getDescriptor().buildField(sourceKeyfield);
        }

        if (getTargetForeignKeyFields().size() != getSourceKeyFields().size()) {
            throw DescriptorException.targetForeignKeysSizeMismatch(this);
        }

        Enumeration<DatabaseField> targetForeignKeysEnum = getTargetForeignKeyFields().elements();
        Enumeration<DatabaseField> sourceKeysEnum = getSourceKeyFields().elements();
        for (; targetForeignKeysEnum.hasMoreElements();) {
            getTargetForeignKeyToSourceKeys().put(targetForeignKeysEnum.nextElement(), sourceKeysEnum.nextElement());
        }
    }

    /**
     * INTERNAL:
     * The foreign keys and the primary key names are converted to DatabaseFields and stored. The source keys
     * are not specified by the user so primary keys are extracted from the reference descriptor.
     */
    protected void initializeTargetForeignKeyToSourceKeysWithDefaults(AbstractSession session) throws DescriptorException {
        if (getTargetForeignKeyFields().isEmpty()) {
            throw DescriptorException.noTargetForeignKeysSpecified(this);
        }

        List<DatabaseField> sourceKeys = getDescriptor().getPrimaryKeyFields();
        setSourceKeyFields(oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(sourceKeys));
        for (Enumeration keys = getTargetForeignKeyFields().elements(); keys.hasMoreElements();) {
            DatabaseField foreignKeyfield = ((DatabaseField)keys.nextElement());
            getReferenceDescriptor().buildField(foreignKeyfield);
        }

        if (getTargetForeignKeyFields().size() != sourceKeys.size()) {
            throw DescriptorException.targetForeignKeysSizeMismatch(this);
        }

        for (int index = 0; index < getTargetForeignKeyFields().size(); index++) {
            getTargetForeignKeyToSourceKeys().put(getTargetForeignKeyFields().get(index), sourceKeys.get(index));
        }
    }

    /**
     * INTERNAL:
     * Iterate on the specified element.
     */
    public void iterateOnElement(DescriptorIterator iterator, Object element) {
        // CR#... Aggregate collections must iterate as aggregates, not regular mappings.
        // For some reason the element can be null, this makes absolutly no sense, but we have a test case for it...
        if (element != null) {
            iterator.iterateForAggregateMapping(element, this, iterator.getSession().getDescriptor(element));
        }
    }

    /**
     * INTERNAL:
     */
    public boolean isAggregateCollectionMapping() {
        return true;
    }

    /**
     * INTERNAL:
     */
    public boolean isPrivateOwned() {
        return true;
    }

    /**
     * Checks if source key is specified or not.
     */
    protected boolean isSourceKeySpecified() {
        return !(getSourceKeyFields().isEmpty());
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     * Because this is a collection mapping, values are added to or removed from the
     * collection based on the changeset
     */
    public void mergeChangesIntoObject(Object target, ChangeRecord changeRecord, Object source, MergeManager mergeManager) {
        //Check to see if the target has an instantiated collection
        if (!isAttributeValueInstantiated(target)) {
            //Then do nothing.
            return;
        }

        ContainerPolicy containerPolicy = getContainerPolicy();
        AbstractSession session = mergeManager.getSession();
        Object valueOfTarget = null;

        //At this point the source's indirection must be instantiated or the changeSet would never have
        // been created
        Object sourceAggregate = null;

        //On a distributed cache if our changes are for the same version as the target object
        //then load the changes from database.
        // CR 4143
        // CR 4155 Always replace the collection with the query results as we will not be able to
        // find the originals for merging and indirection information may be lost.
        if (mergeManager.shouldMergeChangesIntoDistributedCache()) {
            ClassDescriptor descriptor = getDescriptor();
            AbstractRecord parentRow = descriptor.getObjectBuilder().extractPrimaryKeyRowFromObject(target, session);
            Object result = getIndirectionPolicy().valueFromQuery(getSelectionQuery(), parentRow, session);//fix for indirection
            setAttributeValueInObject(target, result);
            return;
        }

        // iterate over the changes and merge the collections
        Vector aggregateObjects = ((AggregateCollectionChangeRecord)changeRecord).getChangedValues();
        valueOfTarget = containerPolicy.containerInstance();
        // Next iterate over the changes and add them to the container
        ObjectChangeSet objectChanges = null;
        for (int i = 0; i < aggregateObjects.size(); ++i) {
            objectChanges = (ObjectChangeSet)aggregateObjects.elementAt(i);
            Class localClassType = objectChanges.getClassType(session);
            sourceAggregate = objectChanges.getUnitOfWorkClone();

            // cr 4155 Load the target from the UnitOfWork. This will be the original
            // aggregate object that has the original indirection in it.
            Object targetAggregate = ((UnitOfWorkImpl)mergeManager.getSession()).getCloneToOriginals().get(sourceAggregate);

            if (targetAggregate == null) {
                targetAggregate = getReferenceDescriptor(localClassType, session).getObjectBuilder().buildNewInstance();
            }
            getReferenceDescriptor(localClassType, session).getObjectBuilder().mergeChangesIntoObject(targetAggregate, objectChanges, sourceAggregate, mergeManager);
            containerPolicy.addInto(targetAggregate, valueOfTarget, session);
        }
        setRealAttributeValueInObject(target, valueOfTarget);
    }

    /**
     * INTERNAL:
     * Merge changes from the source to the target object.
     */
    public void mergeIntoObject(Object target, boolean isTargetUnInitialized, Object source, MergeManager mergeManager) {
        if (isTargetUnInitialized) {
            // This will happen if the target object was removed from the cache before the commit was attempted
            if (mergeManager.shouldMergeWorkingCopyIntoOriginal() && (!isAttributeValueInstantiated(source))) {
                setAttributeValueInObject(target, getIndirectionPolicy().getOriginalIndirectionObject(getAttributeValueFromObject(source), mergeManager.getSession()));
                return;
            }
        }
        if (!shouldMergeCascadeReference(mergeManager)) {
            // This is only going to happen on mergeClone, and we should not attempt to merge the reference
            return;
        }
        if (mergeManager.shouldMergeOriginalIntoWorkingCopy()) {
            if (!isAttributeValueInstantiated(target)) {
                // This will occur when the clone's value has not been instantiated yet and we do not need
                // the refresh that attribute
                return;
            }
        } else if (!isAttributeValueInstantiated(source)) {
            // I am merging from a clone into an original. No need to do merge if the attribute was never
            // modified
            return;
        }

        ContainerPolicy containerPolicy = getContainerPolicy();
        Object valueOfSource = getRealCollectionAttributeValueFromObject(source, mergeManager.getSession());
        Object valueOfTarget = containerPolicy.containerInstance(containerPolicy.sizeFor(valueOfSource));
        for (Object sourceValuesIterator = containerPolicy.iteratorFor(valueOfSource);
                 containerPolicy.hasNext(sourceValuesIterator);) {
            Object sourceValue = containerPolicy.next(sourceValuesIterator, mergeManager.getSession());

            //CR#2896 - TW
            Object originalValue = getReferenceDescriptor(sourceValue.getClass(), mergeManager.getSession()).getObjectBuilder().buildNewInstance();
            getReferenceDescriptor(sourceValue.getClass(), mergeManager.getSession()).getObjectBuilder().mergeIntoObject(originalValue, true, sourceValue, mergeManager);
            containerPolicy.addInto(originalValue, valueOfTarget, mergeManager.getSession());
        }

        // Must re-set variable to allow for set method to re-morph changes if the collection is not being stored directly.
        setRealAttributeValueInObject(target, valueOfTarget);
    }

    /**
     * INTERNAL:
     * An object was added to the collection during an update, insert it if private.
     */
    protected void objectAddedDuringUpdate(ObjectLevelModifyQuery query, Object objectAdded, ObjectChangeSet changeSet) throws DatabaseException, OptimisticLockException {
        // Insert must not be done for uow or cascaded queries and we must cascade to cascade policy.
        InsertObjectQuery insertQuery = getAndPrepareModifyQueryForInsert(query, objectAdded);
        query.getSession().executeQuery(insertQuery, insertQuery.getTranslationRow());
    }

    /**
     * INTERNAL:
     * An object was removed to the collection during an update, delete it if private.
     */
    protected void objectRemovedDuringUpdate(ObjectLevelModifyQuery query, Object objectDeleted) throws DatabaseException, OptimisticLockException {
        // Delete must not be done for uow or cascaded queries and we must cascade to cascade policy.
        DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
        prepareModifyQueryForDelete(query, deleteQuery, objectDeleted);
        query.getSession().executeQuery(deleteQuery, deleteQuery.getTranslationRow());
    }

    /**
     * INTERNAL:
     * An object is still in the collection, update it as it may have changed.
     */
    protected void objectUnchangedDuringUpdate(ObjectLevelModifyQuery query, Object object, Hashtable backupCloneKeyedCache, CacheKey cachedKey) throws DatabaseException, OptimisticLockException {
        // Always write for updates, either private or in uow if calling this method.
        UpdateObjectQuery updateQuery = new UpdateObjectQuery();
        Object backupclone = backupCloneKeyedCache.get(cachedKey);
        updateQuery.setBackupClone(backupclone);
        prepareModifyQueryForUpdate(query, updateQuery, object);
        query.getSession().executeQuery(updateQuery, updateQuery.getTranslationRow());
    }

    /**
     * INTERNAL:
     * For aggregate collection mapping the reference descriptor is cloned. The cloned descriptor is then
     * assigned primary keys and table names before initialize. Once cloned descriptor is initialized
     * it is assigned as reference descriptor in the aggregate mapping. This is a very specifiec
     * behaviour for aggregate mappings. The original descriptor is used only for creating clones and
     * after that mapping never uses it.
     * Some initialization is done in postInitialize to ensure the target descriptor's references are initialized.
     */
    public void postInitialize(AbstractSession session) throws DescriptorException {
        super.postInitialize(session);
        getReferenceDescriptor().postInitialize(session);
    }

    /**
     * INTERNAL:
     * Insert privately owned parts
     */
    public void postInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (isReadOnly()) {
            return;
        }

        Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());

        // insert each object one by one
        ContainerPolicy cp = getContainerPolicy();
        for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) {
            Object object = cp.next(iter, query.getSession());
            InsertObjectQuery insertQuery = getAndPrepareModifyQueryForInsert(query, object);
            query.getSession().executeQuery(insertQuery, insertQuery.getTranslationRow());
        }
    }

    /**
     * INTERNAL:
     * Update the privately owned parts
     */
    public void postUpdate(WriteObjectQuery writeQuery) throws DatabaseException, OptimisticLockException {
        if (isReadOnly()) {
            return;
        }

        // If objects are not instantiated that means they are not changed.
        if (!isAttributeValueInstantiated(writeQuery.getObject())) {
            return;
        }

        // Manage objects added and removed from the collection.
        Object objects = getRealCollectionAttributeValueFromObject(writeQuery.getObject(), writeQuery.getSession());
        Object currentObjectsInDB = readPrivateOwnedForObject(writeQuery);
        if (currentObjectsInDB == null) {
            currentObjectsInDB = getContainerPolicy().containerInstance(1);
        }
        compareObjectsAndWrite(currentObjectsInDB, objects, writeQuery);
    }

    /**
     * INTERNAL:
     * Delete privately owned parts
     */
    public void preDelete(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (isReadOnly()) {
            return;
        }

        // if privately owned parts have their privately own parts, delete those one by one
        // else delete everything in one shot.
        if (getReferenceDescriptor().hasDependencyOnParts() || getReferenceDescriptor().usesOptimisticLocking() || (getReferenceDescriptor().hasInheritance() && getReferenceDescriptor().getInheritancePolicy().shouldReadSubclasses()) || getReferenceDescriptor().hasMultipleTables()) {
            Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());
            ContainerPolicy containerPolicy = getContainerPolicy();
            for (Object iter = containerPolicy.iteratorFor(objects); containerPolicy.hasNext(iter);) {
                Object object = containerPolicy.next(iter, query.getSession());
                DeleteObjectQuery deleteQuery = new DeleteObjectQuery();
                prepareModifyQueryForDelete(query, deleteQuery, object);
                query.getSession().executeQuery(deleteQuery, deleteQuery.getTranslationRow());
            }
            if (!query.getSession().isUnitOfWork()) {
                // This deletes any objects on the database, as the collection in memory may has been changed.
                // This is not required for unit of work, as the update would have already deleted these objects,
                // and the backup copy will include the same objects causing double deletes.
                verifyDeleteForUpdate(query);
            }
        } else {
            deleteAll(query);
        }
    }

    /**
     * INTERNAL:
     * The message is passed to its reference class descriptor.
     */
    public void preInsert(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        if (isReadOnly()) {
            return;
        }

        Object objects = getRealCollectionAttributeValueFromObject(query.getObject(), query.getSession());

        // pre-insert each object one by one
        ContainerPolicy cp = getContainerPolicy();
        for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) {
            Object object = cp.next(iter, query.getSession());
            InsertObjectQuery insertQuery = getAndPrepareModifyQueryForInsert(query, object);

            // aggregates do not actually use a query to write to the database so the pre-write must be called here
            executeEvent(DescriptorEventManager.PreWriteEvent, insertQuery);
            executeEvent(DescriptorEventManager.PreInsertEvent, insertQuery);
            getReferenceDescriptor().getQueryManager().preInsert(insertQuery);
        }
    }

    /**
     * INTERNAL:
     * Returns clone of InsertObjectQuery from the reference descriptor, if it is not set - create it.
     */
    protected InsertObjectQuery getInsertObjectQuery(AbstractSession session, ClassDescriptor desc) {
        InsertObjectQuery insertQuery = desc.getQueryManager().getInsertQuery();
        if (insertQuery == null) {
            insertQuery = new InsertObjectQuery();
            desc.getQueryManager().setInsertQuery(insertQuery);
        }
        if (insertQuery.getModifyRow() == null) {
            AbstractRecord modifyRow = new DatabaseRecord();
            for (int i = 0; i < getTargetForeignKeyFields().size(); i++) {
                DatabaseField field = (DatabaseField)getTargetForeignKeyFields().elementAt(i);
                modifyRow.put(field, null);
            }
            desc.getObjectBuilder().buildTemplateInsertRow(session, modifyRow);
            insertQuery.setModifyRow(modifyRow);
        }
        return insertQuery;
    }

    /**
     * INTERNAL:
     * setup the modifyQuery for post insert/update and pre delete
     */
    public InsertObjectQuery getAndPrepareModifyQueryForInsert(ObjectLevelModifyQuery originalQuery, Object object) {
        AbstractSession session = originalQuery.getSession();
        ClassDescriptor objReferenceDescriptor = getReferenceDescriptor(object.getClass(), session);
        InsertObjectQuery insertQueryFromDescriptor = getInsertObjectQuery(session, objReferenceDescriptor);
        insertQueryFromDescriptor.checkPrepare(session, insertQueryFromDescriptor.getModifyRow());

        InsertObjectQuery insertQuery = (InsertObjectQuery)insertQueryFromDescriptor.clone();
        insertQuery.setObject(object);

        AbstractRecord targetForeignKeyRow = new DatabaseRecord();
        Vector referenceObjectKeys = getReferenceObjectKeys(originalQuery);
        for (int keyIndex = 0; keyIndex < getTargetForeignKeyFields().size(); keyIndex++) {
            targetForeignKeyRow.put(getTargetForeignKeyFields().elementAt(keyIndex), referenceObjectKeys.elementAt(keyIndex));
        }

        insertQuery.setModifyRow(targetForeignKeyRow);
        insertQuery.setTranslationRow(targetForeignKeyRow);
        insertQuery.setSession(session);
        insertQuery.setCascadePolicy(originalQuery.getCascadePolicy());
        insertQuery.dontMaintainCache();

        // For bug 2863721 must set a backup clone for compatibility with
        // old event mechanism, even though for AggregateCollections there is no
        // way to get a backup directly from a clone.
        if (session.isUnitOfWork()) {
            Object backupAttributeValue = getReferenceDescriptor(object.getClass(), session).getObjectBuilder().buildNewInstance();
            insertQuery.setBackupClone(backupAttributeValue);
        }
        return insertQuery;
    }

    /**
     * INTERNAL:
     * setup the modifyQuery for pre delete
     */
    public void prepareModifyQueryForDelete(ObjectLevelModifyQuery originalQuery, ObjectLevelModifyQuery modifyQuery, Object object) {
        AbstractRecord aggregateRow = getAggregateRow(originalQuery, object);
        modifyQuery.setObject(object);
        modifyQuery.setPrimaryKey(getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(aggregateRow, originalQuery.getSession()));
        modifyQuery.setModifyRow(aggregateRow);
        modifyQuery.setTranslationRow(aggregateRow);
        modifyQuery.setSession(originalQuery.getSession());
        if (originalQuery.shouldCascadeOnlyDependentParts()) {
            //This query is the result of being in a UnitOfWork therefor use the Aggregate Collection
            //specific cascade policy to prevent cascading the delete now
            modifyQuery.setCascadePolicy(DatabaseQuery.CascadeAggregateDelete);
        } else {
            modifyQuery.setCascadePolicy(originalQuery.getCascadePolicy());
        }
        modifyQuery.dontMaintainCache();
    }

    /**
     * INTERNAL:
     * setup the modifyQuery for update,
     */
    public void prepareModifyQueryForUpdate(ObjectLevelModifyQuery originalQuery, ObjectLevelModifyQuery modifyQuery, Object object) {
        AbstractRecord aggregateRow = getAggregateRow(originalQuery, object);
        modifyQuery.setObject(object);
        modifyQuery.setPrimaryKey(getReferenceDescriptor().getObjectBuilder().extractPrimaryKeyFromRow(aggregateRow, originalQuery.getSession()));
        modifyQuery.setTranslationRow(aggregateRow);
        modifyQuery.setSession(originalQuery.getSession());
        modifyQuery.setCascadePolicy(originalQuery.getCascadePolicy());
        modifyQuery.dontMaintainCache();
    }

    /**
     * PUBLIC:
     * Set the source key field names associated with the mapping.
     * These must be in-order with the targetForeignKeyFieldNames.
     */
    public void setSourceKeyFieldNames(Vector fieldNames) {
        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setSourceKeyFields(fields);
    }

    /**
     * INTERNAL:
     * set all the primary key names associated with this mapping
     */
    public void setSourceKeyFields(Vector<DatabaseField> sourceKeyFields) {
        this.sourceKeyFields = sourceKeyFields;
    }

    /**
     * PUBLIC:
     * Set the target foregin key field names associated with the mapping.
     * These must be in-order with the sourceKeyFieldNames.
     */
    public void setTargetForeignKeyFieldNames(Vector fieldNames) {
        Vector fields = oracle.toplink.essentials.internal.helper.NonSynchronizedVector.newInstance(fieldNames.size());
        for (Enumeration fieldNamesEnum = fieldNames.elements(); fieldNamesEnum.hasMoreElements();) {
            fields.addElement(new DatabaseField((String)fieldNamesEnum.nextElement()));
        }

        setTargetForeignKeyFields(fields);
    }

    /**
     * INTERNAL:
     * set the target foregin key fields associated with the mapping
     */
    public void setTargetForeignKeyFields(Vector<DatabaseField> targetForeignKeyFields) {
        this.targetForeignKeyFields = targetForeignKeyFields;
    }

    protected void setTargetForeignKeyToSourceKeys(Map<DatabaseField, DatabaseField> targetForeignKeyToSourceKeys) {
        this.targetForeignKeyToSourceKeys = targetForeignKeyToSourceKeys;
    }

    /**
     * Returns true as any process leading to object modification should also affect its privately owned parts
     * Usually used by write, insert, update and delete.
     */
    protected boolean shouldObjectModifyCascadeToParts(ObjectLevelModifyQuery query) {
        if (isReadOnly()) {
            return false;
        }

        return true;
    }

    /**
     * ADVANCED:
     * This method is used to have an object add to a collection once the changeSet is applied
     * The referenceKey parameter should only be used for direct Maps. PLEASE ENSURE that the changes
     * have been made in the object model first.
     */
    public void simpleAddToCollectionChangeRecord(Object referenceKey, Object changeSetToAdd, ObjectChangeSet changeSet, AbstractSession session) {
        AggregateCollectionChangeRecord collectionChangeRecord = (AggregateCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());
        if (collectionChangeRecord == null) {
            //if there is no change for this attribute then create a changeSet for it. no need to modify the resulting
            // change record as it should be built from the clone which has the changes allready
            Object cloneObject = ((UnitOfWorkChangeSet)changeSet.getUOWChangeSet()).getUOWCloneForObjectChangeSet(changeSet);
            Object cloneCollection = this.getRealAttributeValueFromObject(cloneObject, session);
            collectionChangeRecord = (AggregateCollectionChangeRecord)convertToChangeRecord(cloneCollection, changeSet, session);
            changeSet.addChange(collectionChangeRecord);
        } else {
            collectionChangeRecord.getChangedValues().add(changeSetToAdd);
        }
    }

    /**
     * ADVANCED:
     * This method is used to have an object removed from a collection once the changeSet is applied
     * The referenceKey parameter should only be used for direct Maps. PLEASE ENSURE that the changes
     * have been made in the object model first.
     */
    public void simpleRemoveFromCollectionChangeRecord(Object referenceKey, Object changeSetToRemove, ObjectChangeSet changeSet, AbstractSession session) {
        AggregateCollectionChangeRecord collectionChangeRecord = (AggregateCollectionChangeRecord)changeSet.getChangesForAttributeNamed(this.getAttributeName());

        if (collectionChangeRecord == null) {
            //if there is no change for this attribute then create a changeSet for it. no need to modify the resulting
            // change record as it should be built from the clone which has the changes allready
            Object cloneObject = ((UnitOfWorkChangeSet)changeSet.getUOWChangeSet()).getUOWCloneForObjectChangeSet(changeSet);
            Object cloneCollection = this.getRealAttributeValueFromObject(cloneObject, session);
            collectionChangeRecord = (AggregateCollectionChangeRecord)convertToChangeRecord(cloneCollection, changeSet, session);
            changeSet.addChange(collectionChangeRecord);
        } else {
            collectionChangeRecord.getChangedValues().remove(changeSetToRemove);
        }
    }

    /**
     * INTERNAL:
     * Retrieves a value from the row for a particular query key
     */
    protected Object valueFromRowInternal(AbstractRecord row, JoinedAttributeManager joinManager, AbstractSession executionSession) throws DatabaseException {
        // For CR#2587: a fix to allow the reading of nested aggregate collections that
        // use foreign keys as primary keys.
        // Even though foreign keys are not read in a read query insert them into the row that
        // is returned from the database to allow cascading of primary keys.
        // This row will eventually become the translation row which is used to read the aggregate collection.
        // The fix works by passing foreign key information between source and target queries via the translation row.
        // Must clone the row first, for due to prior optimizations the vector of fields is now part of
        // a prepared query!
        row = (AbstractRecord)row.clone();
        int i = 0;
        for (Enumeration sourceKeys = getSourceKeyFields().elements();
                 sourceKeys.hasMoreElements(); i++) {
            DatabaseField sourceKey = (DatabaseField)sourceKeys.nextElement();
            Object value = null;

            // First insure that the source foreign key field is in the row.
            // N.B. If get() is used and returns null it may just mean that the field exists but the value is null.
            int index = row.getFields().indexOf(sourceKey);
            if (index == -1) {
                //Line x: Retrieve the value from the source query's translation row.
                value = joinManager.getBaseQuery().getTranslationRow().get(sourceKey);
                row.add(sourceKey, value);
            } else {
                value = row.getValues().elementAt(index);
            }

            //Now duplicate the source key field values with target key fields, so children aggregate collections can later access them.
            //This will enable the later execution of the above line x.
            row.add((DatabaseField)getTargetForeignKeyFields().elementAt(i), value);
        }
        return super.valueFromRowInternal(row, joinManager, executionSession);
    }

    /**
     * INTERNAL:
     * Checks if object is deleted from the database or not.
     */
    public boolean verifyDelete(Object object, AbstractSession session) throws DatabaseException {
        // Row is built for translation
        if (isReadOnly()) {
            return true;
        }

        AbstractRecord row = getDescriptor().getObjectBuilder().buildRowForTranslation(object, session);
        Object value = session.executeQuery(getSelectionQuery(), row);

        return getContainerPolicy().isEmpty(value);
    }

    /**
     * Verifying deletes make sure that all the records privately owned by this mapping are
     * actually removed. If such records are found than those are all read and removed one
     * by one taking their privately owned parts into account.
     */
    protected void verifyDeleteForUpdate(WriteObjectQuery query) throws DatabaseException, OptimisticLockException {
        Object objects = readPrivateOwnedForObject(query);

        // Delete all these object one by one.
        ContainerPolicy cp = getContainerPolicy();
        for (Object iter = cp.iteratorFor(objects); cp.hasNext(iter);) {
            query.getSession().deleteObject(cp.next(iter, query.getSession()));
        }
    }

    /**
     * INTERNAL:
     * Add a new value and its change set to the collection change record. This is used by
     * attribute change tracking. Currently it is not supported in AggregateCollectionMapping.
     */
    public void addToCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "addToCollectionChangeRecord");
    }
    
    /**
     * INTERNAL
     * Return true if this mapping supports cascaded version optimistic locking.
     */
    public boolean isCascadedLockingSupported() {
        return true;
    }
    
    /**
     * INTERNAL:
     * Return if this mapping supports change tracking.
     */
    public boolean isChangeTrackingSupported() {
        return false;
    }

    /**
     * INTERNAL:
     * Remove a value and its change set from the collection change record. This is used by
     * attribute change tracking. Currently it is not supported in AggregateCollectionMapping.
     */
    public void removeFromCollectionChangeRecord(Object newKey, Object newValue, ObjectChangeSet objectChangeSet, UnitOfWorkImpl uow) throws DescriptorException {
        throw DescriptorException.invalidMappingOperation(this, "removeFromCollectionChangeRecord");
    }
}