/* * Copyright 2009 Thomas Yip, Werner Guttmann, Ralf Joachim * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Id$ */ package org.castor.persist.proxy; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import org.castor.persist.ProposedEntity; import org.castor.persist.TransactionContext; import org.exolab.castor.jdo.LockNotGrantedException; import org.exolab.castor.jdo.PersistenceException; import org.exolab.castor.persist.ClassMolder; import org.exolab.castor.persist.OID; import org.exolab.castor.persist.spi.Identity; /** * It is a lazy Collection. The collection initially contains only the * identities of elements of one type. If any element is needed, it will * be fetched "on the fly". * * @author <a href="mailto:yip AT intalio DOT com">Thomas Yip</a> * @author <a href="mailto:werner DOT guttmann AT gmx DOT net">Werner Guttmann</a> * @author <a href="mailto:ralf DOT joachim AT syscon DOT eu">Ralf Joachim</a> * @version $Revision$ $Date: 2009-07-13 17:22:43 (Mon, 13 Jul 2009) $ */ public final class LazyHashSet<E> implements LazyCollection<E>, Set<E> { /** Transaction to fetch an entity on the fly if needed. */ private final TransactionContext _tx; /** The ClassMolder of the entities. */ private final ClassMolder _molder; /** Set of identities of the entities currently contained. */ private final List<Identity> _current = new ArrayList<Identity>(); /** Set of identities of entities that has been removed. */ private final List<Identity> _removed = new ArrayList<Identity>(); /** Set of identities of entities that have been added. */ private final List<Identity> _added = new ArrayList<Identity>(); /** Map entities loaded to their identity. */ private final Map<Identity, E> _loaded = new HashMap<Identity, E>(); /** Change count of the collection. */ private int _changecount; /** Number of elements in this collection. */ private int _size; /** * Creates an instance of LazyHashSet. * * @param tx Current transaction context * @param molder Associated ClassMolder * @param ids Set of identifiers. */ public LazyHashSet(final TransactionContext tx, final ClassMolder molder, final List<Identity> ids) { _tx = tx; _molder = molder; if (ids != null) { _current.addAll(ids); } _size = _current.size(); } public boolean add(final E entity) { Identity id = _molder.getIdentity(_tx, entity); if (_current.contains(id)) { if (_removed.contains(id)) { _removed.remove(id); _loaded.put(id, entity); _changecount++; _size++; return true; } return _loaded.put(id, entity) != entity; } if (_removed.contains(id)) { throw new RuntimeException("Illegal Internal State."); } if (_added.add(id)) { _loaded.put(id, entity); _changecount++; _size++; return true; } return _loaded.put(id, entity) != entity; } public boolean addAll(final Collection<? extends E> collection) { boolean changed = false; Iterator<? extends E> iter = collection.iterator(); while (iter.hasNext()) { if (add(iter.next())) { changed = true; } } if (changed) { _changecount++; } return changed; } public void clear() { Iterator<E> iter = iterator(); while (iter.hasNext()) { iter.next(); iter.remove(); } } public boolean contains(final Object entity) { Identity id = _molder.getIdentity(_tx, entity); if (_added.contains(id)) { return true; } if (_current.contains(id) && !_removed.contains(id)) { return true; } return false; } public boolean containsAll(final Collection<?> collection) { Iterator<?> iter = collection.iterator(); while (iter.hasNext()) { if (!contains(iter.next())) { return false; } } return true; } public boolean isEmpty() { return size() == 0; } public Iterator<E> iterator() { return new IteratorImp(this); } public boolean remove(final Object entity) { Identity id = _molder.getIdentity(_tx, entity); if (_removed.contains(id)) { return false; } if (_added.contains(id)) { _added.remove(id); _changecount++; _size--; return true; } else if (_current.contains(id)) { // We need to have the object in our _loaded map because // when the TX tries to commit later, it will call our // find() method for any deleted objects. See find() // [below] for details. _loaded.put(id, (E) entity); _removed.add(id); _changecount++; _size--; return true; } return false; } public boolean removeAll(final Collection<?> collection) { boolean changed = false; Iterator<?> iter = collection.iterator(); while (iter.hasNext()) { if (remove(iter.next())) { changed = true; } } if (changed) { _changecount++; } return changed; } public boolean retainAll(final Collection<?> collection) { boolean changed = false; Iterator<E> iter = iterator(); while (iter.hasNext()) { E entity = iter.next(); if (!collection.contains(entity)) { changed = true; iter.remove(); } } if (changed) { _changecount++; } return changed; } public int size() { return _size; } public Object[] toArray() { Object[] result = new Object[size()]; int count = 0; Iterator<E> iter = iterator(); while (iter.hasNext()) { result[count++] = iter.next(); } return result; } public <A> A[] toArray(final A[] array) { if (array == null) { throw new NullPointerException(); } A[] result; int size = size(); if (size <= array.length) { result = array; } else { result = (A[]) Array.newInstance(array.getClass() .getComponentType(), size); } Iterator<E> iter = iterator(); int count = 0; while (iter.hasNext()) { result[count++] = (A) iter.next(); } // patch the extra space with null while (count < result.length) { result[count++] = null; } return result; } public boolean equals(final Object o) { return this == o; } public int hashCode() { return super.hashCode(); } private final class IteratorImp implements Iterator<E> { private int _changestamp; private int _cursor; private int _iterationsize; private LazyHashSet<E> _parent; private IteratorImp(final LazyHashSet<E> rc) { _parent = rc; _changestamp = rc._changecount; // iterationsize is the number of elements to iterate over // during the life of this Iterator. Items in _deleted are // not included because they are duplicates of items in // _ids and thus are not iterated over. _iterationsize = _parent._added.size() + _parent._current.size(); } public boolean hasNext() { if (_changestamp != _parent._changecount) { throw new ConcurrentModificationException( "Concurrent Modification is not allowed!"); } if (_cursor >= _added.size()) { // skip deleted ids while ((_cursor < _iterationsize) && isSkipped(_current.get(_cursor - _added.size()))) { _cursor++; } } if (_cursor >= _iterationsize) { return false; } return true; } public E next() { if (_changestamp != _parent._changecount) { throw new ConcurrentModificationException( "Concurrent Modification is not allowed!"); } // only needed if application did not call hasNext(), will skip deleted ids if (!hasNext()) { throw new NoSuchElementException( "Read after the end of iterator!"); } Identity id; E entity; if (_cursor < _added.size()) { id = _added.get(_cursor++); entity = _loaded.get(id); if (entity != null) { return entity; } return lazyLoad(id); } // the deleted ids were skipped by hasNext(), get is safe id = _current.get(_cursor++ - _added.size()); entity = _loaded.get(id); if (entity != null) { return entity; } return lazyLoad(id); } private boolean isSkipped(final Identity id) { if (_removed.contains(id)) { return true; } // make sure the object is not deleted in // the current transaction outside this class OID oid = new OID(_parent._molder, id); return _parent._tx.isDeletedByOID(oid); } private E lazyLoad(final Identity ids) { E o; if (!_tx.isOpen()) { throw new RuntimeException("Transaction is closed!"); } try { ProposedEntity proposedValue = new ProposedEntity(_parent._molder); o = (E) _parent._tx.load(ids, proposedValue, null); _parent._loaded.put(ids, o); return o; } catch (LockNotGrantedException e) { throw new RuntimeException( "Lock Not Granted for lazy loaded object\n" + e); } catch (PersistenceException e) { throw new RuntimeException( "PersistenceException for lazy loaded object\n" + e); } } public void remove() { if (_cursor <= 0) { throw new IllegalStateException( "Method next() must be called before remove!"); } if (_changestamp != _parent._changecount) { throw new ConcurrentModificationException( "Concurrent Modification is not allowed!"); } Identity id; _cursor--; if (_cursor < _added.size()) { _parent._added.remove(_cursor); _parent._size--; // Manipulating the _added array must be // reflected in iteration size _iterationsize--; _parent._changecount++; _changestamp = _parent._changecount; } else { // backward to the first not deleted ids id = _current.get(_cursor); while (_removed.contains(id)) { id = _current.get(_cursor--); } if (_cursor < _added.size()) { _parent._added.remove(id); _parent._size--; // Manipulating the _added array must be // reflected in iterationsize _iterationsize--; _parent._changecount++; _changestamp = _parent._changecount; } else { _parent._removed.add(id); _parent._size--; _parent._changecount++; _cursor++; // undo decrement of cursor above _changestamp = _parent._changecount; } } } } public List<Identity> getIdsList() { List<Identity> result = new ArrayList<Identity>(); result.addAll(_current); result.addAll(_added); result.removeAll(_removed); return result; } public List<Identity> getRemovedIdsList() { return _removed; } public List<E> getAddedEntitiesList() { List<E> added = new ArrayList<E>(); for (Iterator<Identity> iter = _added.iterator(); iter.hasNext();) { added.add(_loaded.get(iter.next())); } return added; } public void committed(final TransactionContext tx) { // just reset state if we are called in our transaction if (tx == _tx) { _added.clear(); _removed.clear(); _changecount = 0; // ClassMolder registered us, we have to unregister ourself tx.removeTxSynchronizable(this); } } public void rolledback(final TransactionContext tx) { committed(tx); } }