/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. 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_1_1.html * or packager/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 packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [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. */ /* * 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.support.sqlstore.PersistenceManager; import org.glassfish.persistence.common.I18NHelper; /** * 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; } } }