/*
 * The contents of this file are subject to the terms 
 * of the Common Development and Distribution License 
 * (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/CDDLv1.0.html or
 * glassfish/bootstrap/legal/CDDLv1.0.txt.
 * See the License for the specific language governing 
 * permissions and limitations under the License.
 * 
 * When distributing Covered Code, include this CDDL 
 * Header Notice in each file and include the License file 
 * at glassfish/bootstrap/legal/CDDLv1.0.txt.  
 * If applicable, add the following below the CDDL Header, 
 * with the fields enclosed by brackets [] replaced by
 * you own identifying information: 
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * Copyright 2006 Sun Microsystems, Inc. All rights reserved.
 */

/*
 * sco.HashSet.java
 */

package com.sun.jdo.spi.persistence.support.sqlstore.sco;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.ResourceBundle;

import com.sun.jdo.api.persistence.support.JDOUserException;
import com.sun.jdo.spi.persistence.support.sqlstore.PersistenceCapable;
import com.sun.jdo.spi.persistence.support.sqlstore.StateManager;
import com.sun.jdo.spi.persistence.support.sqlstore.SCOCollection;
import com.sun.jdo.spi.persistence.utility.I18NHelper;
import com.sun.jdo.spi.persistence.support.sqlstore.PersistenceManager;


/**
 * A mutable 2nd class object date.
 * @author Marina Vatkina
 * @version 1.0
 * @see java.util.HashSet
 */
public class HashSet
    extends java.util.HashSet
    implements SCOCollection
{

    private transient PersistenceCapable owner;

    private transient String fieldName;

    private transient Class elementType;

    private transient boolean allowNulls;

    private transient java.util.HashSet added = new java.util.HashSet();

    private transient java.util.HashSet removed = new java.util.HashSet();

    private transient boolean isDeferred;

    /**
     * I18N message handlers
     */
    private final static ResourceBundle messages = I18NHelper.loadBundle(
                             "com.sun.jdo.spi.persistence.support.sqlstore.impl.Bundle", // NOI18N
                             HashSet.class.getClassLoader());

    private final static ResourceBundle messages1 = I18NHelper.loadBundle(
                             "com.sun.jdo.spi.persistence.support.sqlstore.Bundle", // NOI18N
                             HashSet.class.getClassLoader());


    /**
     * Creates a new empty <code>HashSet</code> object.
     * Assigns owning object and field name.
     *
     * @param owner the owning object
     * @param fieldName the owning field name
     * @param elementType the element types allowed
     * @param allowNulls true if nulls are allowed
     */
    public HashSet(Object owner, String fieldName, Class elementType, boolean allowNulls)
    {
        super();
    if (owner instanceof PersistenceCapable)
        {
                this.owner = (PersistenceCapable)owner;
            this.fieldName = fieldName;
        }
    this.elementType = elementType;
        this.allowNulls = allowNulls;
    }

    /**
     * Creates a new empty <code>HashSet</code> object that has
     * the specified initial capacity.Assigns owning object and field name.
     *
     * @param owner     the owning object
     * @param fieldName the owning field name
     * @param elementType the element types allowed
     * @param allowNulls true if nulls are allowed
     * @param      initialCapacity   the initial capacity of the hash map.
     * @throws     IllegalArgumentException if the initial capacity is less
     *             than zero.
     * @see java.util.HashSet
     */
    public HashSet(Object owner, String fieldName,
                                Class elementType, boolean allowNulls,
                                int initialCapacity)
    {
    super(initialCapacity);
    if (owner instanceof PersistenceCapable)
        {
                this.owner = (PersistenceCapable)owner;
            this.fieldName = fieldName;
        }
    this.elementType = elementType;
        this.allowNulls = allowNulls;
    }

    // -------------------------Public Methods------------------

    /**
     * Adds the specified element to this set if it is not already
     * present.
     *
     * @param o element to be added to this set.
     * @return <tt>true</tt> if the set did not already contain the specified
     * element.
     * @see java.util.HashSet
     */
    public boolean add(Object o)
    {
        if (allowNulls == false && o == null)
        {
            throw new JDOUserException(I18NHelper.getMessage(messages,
                        "sco.nulls_not_allowed")); // NOI18N
        }

        if (elementType != null && !elementType.isAssignableFrom(o.getClass()))
        {
            throw new JDOUserException(I18NHelper.getMessage(messages,
                                     "sco.classcastexception", elementType.getName()), // NOI18N
                                       new ClassCastException(), new Object[] {o});
        }

        if (owner != null)
        {
            StateManager stateManager = owner.jdoGetStateManager();

            if (stateManager != null)
            {
                PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                pm.acquireShareLock();

                boolean modified = false;

                try
                {
                    pm.acquireFieldUpdateLock();
                    try
                    {
                       // Mark the field as dirty
                       stateManager.makeDirty(fieldName);

                       modified = super.add(o);

                       if (modified)
                       {
                           if (removed.remove(o) == false)
                           {
                               added.add(o);
                           }
                           stateManager.applyUpdates(fieldName, this);
                       }
                       return modified;
                    }
                    finally
                    {
                        pm.releaseFieldUpdateLock();
                    }

                }
                catch (JDOUserException e)
                {
                    Object[] failedObjects = e.getFailedObjectArray();

                    if (modified && (failedObjects != null))
                    {
                        //
                        // The failedObjects array may contain objects other
                        // than the one added. We iterate through it to find
                        // the one added and remove it from the collection.
                        //
                        for (int i = 0; i < failedObjects.length; i++)
                        {
                            Object failedObject = failedObjects[i];

                            if (failedObject == o)
                            {
                                super.remove(failedObject);
                                break;
                            }
                        }
                    }

                    throw e;
                }
                finally
                {
                    pm.releaseShareLock();
                }
            }
        }

        return super.add(o);
    }

    /**
     * Adds all of the elements in the specified collection to this collection
     *
     * @param c collection whose elements are to be added to this collection.
     * @return <tt>true</tt> if this collection changed as a result of the
     * call.
     * @throws UnsupportedOperationException if the <tt>addAll</tt> method is
     *            not supported by this collection.
     *
     * @see java.util.AbstractCollection
     * @see java.util.HashSet
     */
    public boolean addAll(Collection c)
    {
        if (allowNulls == false && c.contains(null))
        {
            throw new JDOUserException(I18NHelper.getMessage(messages,
                        "sco.nulls_not_allowed")); // NOI18N
        }

        ArrayList errc = new ArrayList();

        if (elementType != null)
        {
            // iterate the collection and make a list of wrong elements.
            Iterator i = c.iterator();
            while (i.hasNext())
            {
                Object o = i.next();
                if (!elementType.isAssignableFrom(o.getClass()))
                    errc.add(o);
            }
        }

        if (errc != null && errc.size() > 0)
        {
            throw new JDOUserException(I18NHelper.getMessage(messages,
                                     "sco.classcastexception", elementType.getName()), // NOI18N
                                       new ClassCastException(), errc.toArray());
        }

        boolean modified = false;

        if (owner != null)
        {
            StateManager stateManager = owner.jdoGetStateManager();

            if (stateManager != null)
            {
                PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                pm.acquireShareLock();

                try
                {
                    pm.acquireFieldUpdateLock();
                    try
                    {
                        // Mark the field as dirty
                        stateManager.makeDirty(fieldName);

                        for (Iterator iter = c.iterator(); iter.hasNext();)
                        {
                            Object o = iter.next();
                            if (!super.contains(o))
                            {
                                if (removed.remove(o) == false)
                                {
                                    added.add(o);
                                }

                                super.add(o);
                                modified = true;
                            }
                        }

                        // Apply updates
                        if (modified)
                        {
                            stateManager.applyUpdates(fieldName, this);
                        }
                        return modified;
                    }
                    finally
                    {
                        pm.releaseFieldUpdateLock();
                    }
                }
                catch (JDOUserException e)
                {
                    Object[] failedObjects = e.getFailedObjectArray();

                    if (modified && (failedObjects != null))
                    {
                        for (int i = 0; i < failedObjects.length; i++)
                        {
                            super.remove(failedObjects[i]);
                        }
                    }

                    throw e;
                }
                finally
                {
                    pm.releaseShareLock();
                }
            }
        }

        return super.addAll(c);
    }

    /**
     * Removes the given element from this set if it is present.
     *
     * @param o object to be removed from this set, if present.
     * @return <tt>true</tt> if the set contained the specified element.
     * @see java.util.HashSet
     */
    public boolean remove(Object o)
    {
    // Mark the field as dirty

        if (owner != null)
        {
            StateManager stateManager = owner.jdoGetStateManager();

            if (stateManager != null)
            {
                PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                pm.acquireShareLock();

                try
                {
                    pm.acquireFieldUpdateLock();
                    try
                    {
                        stateManager.makeDirty(fieldName);

                        boolean modified = super.remove(o);

                        if (modified)
                        {
                            if (added.remove(o) == false)
                            {
                                removed.add(o);
                            }

                            stateManager.applyUpdates(fieldName, this);
                        }

                        return modified;
                    }
                    finally
                    {
                        pm.releaseFieldUpdateLock();
                    }
                }
                finally
                {
                    pm.releaseShareLock();
                }
            }
        }

        return super.remove(o);
    }


    /**
     * Removes from this collection all of its elements that are contained in
     * the specified collection (optional operation). <p>
     *
     *
     * @param c elements to be removed from this collection.
     * @return <tt>true</tt> if this collection changed as a result of the
     * call.
     *
     * @throws    UnsupportedOperationException removeAll is not supported
     *            by this collection.
     *
     * @see java.util.HashSet
     * @see java.util.AbstractCollection
     */
    public boolean removeAll(Collection c)
    {
        // Mark the field as dirty

        if (owner != null)
        {
            StateManager stateManager = owner.jdoGetStateManager();

            if (stateManager != null)
            {
                PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                pm.acquireShareLock();

                try
                {
                    pm.acquireFieldUpdateLock();
                    try
                    {
                        stateManager.makeDirty(fieldName);

                        for (Iterator iter = c.iterator(); iter.hasNext();)
                        {
                            Object o = iter.next();
                            if (super.contains(o))
                            {
                                if (added.remove(o) == false)
                                {
                                    removed.add(o);
                                }
                            }
                        }

                        boolean modified = super.removeAll(c);

                        // Apply updates
                        if (modified)
                        {
                            stateManager.applyUpdates(fieldName, this);
                        }

                        return modified;
                    }
                    finally
                    {
                        pm.releaseFieldUpdateLock();
                    }
                }
                finally
                {
                    pm.releaseShareLock();
                }
            }
        }

        return super.removeAll(c);
    }

    /**
     * Retains only the elements in this collection that are contained in the
     * specified collection (optional operation).
     *
     * @return <tt>true</tt> if this collection changed as a result of the
     *         call.
     *
     * @throws UnsupportedOperationException if the <tt>retainAll</tt> method
     *            is not supported by this collection.
     *
     * @see java.util.HashSet
     * @see java.util.AbstractCollection
     */
    public boolean retainAll(Collection c)
    {
        if (owner != null)
        {
            StateManager stateManager = owner.jdoGetStateManager();

            if (stateManager != null)
            {
                PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                pm.acquireShareLock();

                try
                {
                    pm.acquireFieldUpdateLock();
                    try
                    {
                        // Mark the field as dirty
                        stateManager.makeDirty(fieldName);

                        for (Iterator iter = super.iterator(); iter.hasNext();)
                        {
                            Object o = iter.next();
                            if (!c.contains(o))
                            {
                                if (added.remove(o) == false)
                                {
                                    removed.add(o);
                                }
                            }
                        }

                        boolean modified = super.retainAll(c);

                        // Apply updates

                        if (modified)
                        {
                            stateManager.applyUpdates(fieldName, this);
                        }

                        return modified;
                    }
                    finally
                    {
                        pm.releaseFieldUpdateLock();
                    }
                }
                finally
                {
                    pm.releaseShareLock();
                }
            }
        }

        return super.retainAll(c);
    }

    /**
     * Removes all of the elements from this set.
     * @see java.util.HashSet
     */
    public void clear()
    {
        if (owner != null)
        {
            StateManager stateManager = owner.jdoGetStateManager();

            if (stateManager != null)
            {
                PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                pm.acquireShareLock();

                try
                {
                    pm.acquireFieldUpdateLock();
                    try
                    {
                        // Mark the field as dirty
                        stateManager.makeDirty(fieldName);

                        removed.clear();
                        added.clear();

                        for (Iterator iter = super.iterator(); iter.hasNext();)
                        {
                            removed.add(iter.next());
                        }

                        super.clear();

                        // Apply updates
                        stateManager.applyUpdates(fieldName, this);
                        return;
                    }
                    finally
                    {
                        pm.releaseFieldUpdateLock();
                    }
                }
                finally
                {
                    pm.releaseShareLock();
                }
            }
        }

        super.clear();
    }

    /**
     * Creates and returns a copy of this object.
     *
     * <P>Mutable Second Class Objects are required to provide a public
     * clone method in order to allow for copying PersistenceCapable
     * objects. In contrast to Object.clone(), this method must not throw a
     * CloneNotSupportedException.
     */
    public Object clone()
    {
        HashSet obj = (HashSet) super.clone();

        // RESOLVE: check if added/removed should not be cleared
        // for a deferred collection, but applyDeferredUpdates logic
        // be used?
    obj.unsetOwner();

        return obj;
    }

    /**
     * Returns an iterator over the elements in this set.  The elements
     * are returned in no particular order.
     *
     * @return an Iterator over the elements in this set.
     * @see java.util.ConcurrentModificationException
     */
    public Iterator iterator() {
        return new SCOHashIterator(super.iterator(), this);
    }

    private class SCOHashIterator implements Iterator {
        Iterator _iterator = null;
        HashSet _caller = null;
        Object lastReturned = null;

        SCOHashIterator(Iterator it, HashSet cl) {
            _iterator = it;
            _caller = cl;
        }

        public boolean hasNext() {
            return _iterator.hasNext();
        }

        public Object next() {
            lastReturned = _iterator.next();
            return  lastReturned;
        }

        public void remove() {
            // Check if called twice.
            if (lastReturned == null)
                throw new IllegalStateException();

            if (_caller.owner != null) {
                // Mark the field as dirty
                StateManager stateManager = _caller.owner.jdoGetStateManager();

                if (stateManager != null) {
                    PersistenceManager pm = (PersistenceManager) stateManager.getPersistenceManagerInternal();

                    pm.acquireShareLock();

                    try {
                        pm.acquireFieldUpdateLock();
                        try
                        {
                            stateManager.makeDirty(_caller.fieldName);

                            _iterator.remove();

                            if (added.remove(lastReturned) == false) {
                                removed.add(lastReturned);
                            }

                            stateManager.applyUpdates(_caller.fieldName, _caller);

                        } finally {
                            pm.releaseFieldUpdateLock();
                        }
                    }
                    finally
                    {
                        pm.releaseShareLock();
                    }
                }
            } else {
                // No owner - regular HashSet operation.
                _iterator.remove();
            }
            lastReturned = null;
        }
    }

    //
    // The following internal methods should be called under an outer lock such
    // as fieldUpdateLock. There is no need to synchronize them.
    //

    /**
     * Creates and returns a copy of this object without resetting the owner and field value.
     *
     */
    public Object cloneInternal()
    {
        return super.clone();
    }

    /**
     * Cleans removed and added lists
     */
    public void reset()
    {
        // RESOLVE: do we need to synchronize this??
        if (added != null)
            added.clear();

        if (removed != null)
            removed.clear();
    }

    /**
     * Mark this HashSet as deferred.
     */
    public void markDeferred()
    {
        isDeferred = true;
    }

    /**
     * Return true is this HashSet is deferred, false otherwise.
     */
    public boolean isDeferred()
    {
        return isDeferred;
    }

    /**
     * If the HashSet is deferred, we first initialize the internal collection
     * with c and they apply any deferred updates specified by the added and
     * removed lists.
     */
    public void applyDeferredUpdates(Collection c)
    {
        if (!isDeferred)
        {
            // should throw an exception??
            return;
        }

        isDeferred = false;
        addAllInternal(c);

        addAllInternal(added);
        removeAllInternal(removed);
        added.clear();
        removed.clear();
    }

    /**
      * Adds an object to the list without recording changes if the HashSet is
      * not deferred. Otherwise, add o to the added list.
     */
    public void addInternal(Object o)
    {
        if (isDeferred)
        {
            if (removed.remove(o) == false)
            {
                added.add(o);
            }
        }
        else
        {
            super.add(o);
        }
    }


    /**
     * Adds a Collection to the list without recording changes if the HashSet is
     * not deferred. Otherwise, add o to the removed list.
     */
    public void addAllInternal(Collection c)
    {
        if (c == null)
        {
            return;
        }

        Iterator iter = c.iterator();

        while (iter.hasNext())
        {
            addInternal(iter.next());
        }
    }

    /**
     * @inheritDoc
     */
    public void addToBaseCollection(Object o)
    {
        super.add(o);
    }

    /*
     * Remove c from the list if the HashSet is not deferred.
     * Otherwise, add c to the removed list.
     */
    public void removeAllInternal(Collection c)
    {
        if (c == null)
        {
            return;
        }

        Iterator iter = c.iterator();

        while (iter.hasNext())
        {
            removeInternal(iter.next());
        }
    }

    /**
     * Returns added collection
     *
     * @return added    collection of added elements
     */
    public Collection getAdded()
    {
        return (Collection)added;
    }

    /**
     * Returns removed collection
     *
     * @return removed  collection of removed elements
     */
    public Collection getRemoved()
    {
        return (Collection)removed;
    }


    /**
     * Clears Collection without notifing the owner
     */
    public void clearInternal()
    {
        super.clear();
        this.reset();
    }

    /**
     * Removes an element without notifing the owner
     */
    public void removeInternal(Object o)
    {
        if (isDeferred)
        {
            if (added.remove(o) == false)
            {
                removed.add(o);
            }

        }
        else
        {
            super.remove(o);
        }
    }

    /**
     * Nullifies references to the owner Object and Field
     */
    public void unsetOwner()
    {
        this.owner = null;
        this.fieldName = null;
        this.elementType = null;
                added.clear();
                removed.clear();
    }

    /**
     * Returns the owner object of the SCO instance
     *
     * @return owner object
     */
    public Object getOwner()
    {
        return this.owner;
    }

    /**
     * Returns the field name
     *
     * @return field name as java.lang.String
     */
    public String getFieldName()
    {
        return this.fieldName;
    }

    /**
     * Marks object dirty
     */
    public StateManager makeDirty()
    {
        StateManager stateManager = owner.jdoGetStateManager();

        if (stateManager != null)
        {
            stateManager.makeDirty(fieldName);
        }

        return stateManager;
    }


    /**
     * Apply changes (can be a no-op)
     */
    public void applyUpdates(StateManager sm, boolean modified)
    {
         if (modified && sm != null)
        {
            sm.applyUpdates(fieldName, this);
        }
    }

    /**
     * Set the owner if this instance is not owned.
     * @see SCOCollection#setOwner
     * @param owner the new owner.
     * @param fieldName the new field name.
     * @param elementType the new element type as Class, or null if type
     * is not checked or not supported.
     */
    public void setOwner(Object owner, String fieldName, Class elementType) {

        if (this.owner != null) {
            throw new JDOUserException(I18NHelper.getMessage(
                messages1, "core.statemanager.anotherowner"), // NOI18N
                new Object[]{this.owner, this.fieldName});
        }
        if (owner instanceof PersistenceCapable) {
                this.owner = (PersistenceCapable)owner;
                this.fieldName = fieldName;
                this.elementType = elementType;
        }
    }
    }




