| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| package org.apache.openjpa.util; |
| |
| import java.io.ObjectStreamException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| |
| import org.apache.commons.collections.Predicate; |
| import org.apache.commons.collections.iterators.FilterIterator; |
| import org.apache.commons.collections.iterators.IteratorChain; |
| import org.apache.openjpa.kernel.OpenJPAStateManager; |
| import org.apache.openjpa.lib.util.Closeable; |
| import org.apache.openjpa.lib.util.Localizer; |
| |
| /** |
| * A collection proxy designed for collections backed by extremely large |
| * result sets in which each call to {@link #iterator} may perform a database |
| * query. Changes to the collection are tracked through a |
| * {@link ChangeTracker}. This collection has the following limitations: |
| * <ul> |
| * <li>The <code>size</code> method may return {@link Integer#MAX_VALUE}.</li> |
| * <li>The collection cannot contain duplicate elements.</li> |
| * </ul> |
| * |
| * @author Abe White |
| */ |
| public abstract class AbstractLRSProxyCollection |
| implements Set, LRSProxy, Predicate, CollectionChangeTracker { |
| |
| private static final Localizer _loc = Localizer.forPackage |
| (AbstractLRSProxyCollection.class); |
| |
| private Class _elementType = null; |
| private CollectionChangeTrackerImpl _ct = null; |
| private OpenJPAStateManager _sm = null; |
| private int _field = -1; |
| private OpenJPAStateManager _origOwner = null; |
| private int _origField = -1; |
| private int _count = -1; |
| private boolean _iterated = false; |
| |
| /** |
| * Constructor. |
| * |
| * @param elementType the allowed type of elements, or null for no |
| * restrictions |
| * @param ordered true if this collection is ordered |
| */ |
| public AbstractLRSProxyCollection(Class elementType, boolean ordered) { |
| _elementType = elementType; |
| _ct = new CollectionChangeTrackerImpl(this, false, ordered); |
| _ct.setAutoOff(false); |
| } |
| |
| public void setOwner(OpenJPAStateManager sm, int field) { |
| // can't transfer ownership of an lrs proxy |
| if (sm != null && _origOwner != null |
| && (_origOwner != sm || _origField != field)) { |
| throw new InvalidStateException(_loc.get("transfer-lrs", |
| _origOwner.getMetaData().getField(_origField))); |
| } |
| |
| _sm = sm; |
| _field = field; |
| |
| // keep track of original owner so we can detect transfer attempts |
| if (sm != null) { |
| _origOwner = sm; |
| _origField = field; |
| } |
| } |
| |
| public OpenJPAStateManager getOwner() { |
| return _sm; |
| } |
| |
| public int getOwnerField() { |
| return _field; |
| } |
| |
| public ChangeTracker getChangeTracker() { |
| return this; |
| } |
| |
| public Object copy(Object orig) { |
| // used to store fields for rollback; we don't store lrs fields |
| return null; |
| } |
| |
| public boolean add(Object o) { |
| Proxies.assertAllowedType(o, _elementType); |
| Proxies.dirty(this, false); |
| _ct.added(o); |
| return true; |
| } |
| |
| public boolean addAll(Collection all) { |
| Proxies.dirty(this, false); |
| boolean added = false; |
| Object add; |
| for (Iterator itr = all.iterator(); itr.hasNext();) { |
| add = itr.next(); |
| Proxies.assertAllowedType(add, _elementType); |
| _ct.added(add); |
| added = true; |
| } |
| return added; |
| } |
| |
| public boolean remove(Object o) { |
| if (!contains(o)) |
| return false; |
| Proxies.dirty(this, false); |
| Proxies.removed(this, o, false); |
| _ct.removed(o); |
| return true; |
| } |
| |
| public boolean removeAll(Collection all) { |
| Proxies.dirty(this, false); |
| boolean removed = false; |
| Object rem; |
| for (Iterator itr = all.iterator(); itr.hasNext();) { |
| rem = itr.next(); |
| if (remove(rem)) { |
| Proxies.removed(this, rem, false); |
| _ct.removed(rem); |
| removed = true; |
| } |
| } |
| return removed; |
| } |
| |
| public boolean retainAll(Collection all) { |
| if (all.isEmpty()) { |
| clear(); |
| return true; |
| } |
| |
| Proxies.dirty(this, false); |
| Itr itr = (Itr) iterator(); |
| try { |
| boolean removed = false; |
| Object rem; |
| while (itr.hasNext()) { |
| rem = itr.next(); |
| if (!all.contains(rem)) { |
| Proxies.removed(this, rem, false); |
| _ct.removed(rem); |
| removed = true; |
| } |
| } |
| return removed; |
| } finally { |
| itr.close(); |
| } |
| } |
| |
| public void clear() { |
| Proxies.dirty(this, false); |
| Itr itr = (Itr) iterator(); |
| try { |
| Object rem; |
| while (itr.hasNext()) { |
| rem = itr.next(); |
| Proxies.removed(this, rem, false); |
| _ct.removed(rem); |
| } |
| } |
| finally { |
| itr.close(); |
| } |
| } |
| |
| public boolean contains(Object o) { |
| if (_elementType != null && !_elementType.isInstance(o)) |
| return false; |
| if (_ct.getAdded().contains(o)) |
| return true; |
| if (_ct.getRemoved().contains(o)) |
| return false; |
| if (!has(o)) |
| return false; |
| return true; |
| } |
| |
| public boolean containsAll(Collection all) { |
| for (Iterator itr = all.iterator(); itr.hasNext();) |
| if (!contains(itr.next())) |
| return false; |
| return true; |
| } |
| |
| public Object[] toArray() { |
| return asList().toArray(); |
| } |
| |
| public Object[] toArray(Object[] a) { |
| return asList().toArray(a); |
| } |
| |
| private List asList() { |
| Itr itr = (Itr) iterator(); |
| try { |
| List list = new ArrayList(); |
| while (itr.hasNext()) |
| list.add(itr.next()); |
| return list; |
| } finally { |
| itr.close(); |
| } |
| } |
| |
| public int size() { |
| if (_count == -1) |
| _count = count(); |
| if (_count == Integer.MAX_VALUE) |
| return _count; |
| return _count + _ct.getAdded().size() - _ct.getRemoved().size(); |
| } |
| |
| public boolean isEmpty() { |
| return size() == 0; |
| } |
| |
| public Iterator iterator() { |
| _iterated = true; |
| |
| IteratorChain chain = new IteratorChain(); |
| chain.addIterator(new FilterIterator(itr(), this)); |
| |
| // note have to copy _ct.getAdded to prevent concurrent mod errors |
| chain.addIterator(new ArrayList(_ct.getAdded()).iterator()); |
| return new Itr(chain); |
| } |
| |
| /** |
| * used in testing; we need to be able to make sure that OpenJPA does not |
| * iterate lrs fields during standard crud operations |
| */ |
| boolean isIterated() { |
| return _iterated; |
| } |
| |
| /** |
| * used in testing; we need to be able to make sure that OpenJPA does not |
| * iterate lrs fields during standard crud operations |
| */ |
| void setIterated(boolean it) { |
| _iterated = it; |
| } |
| |
| protected Object writeReplace() |
| throws ObjectStreamException { |
| return asList(); |
| } |
| |
| /** |
| * Implement this method to return an iterator over the contents of the |
| * collection. This method may be invoked multiple times. The returned |
| * iterator does not have to support the {@link Iterator#remove} method, |
| * and may implement {@link org.apache.openjpa.lib.util.Closeable}. |
| */ |
| protected abstract Iterator itr(); |
| |
| /** |
| * Return whether the collection contains the given element. |
| */ |
| protected abstract boolean has(Object o); |
| |
| /** |
| * Return the number of elements in the collection, or |
| * {@link Integer#MAX_VALUE}. |
| */ |
| protected abstract int count(); |
| |
| //////////////////////////// |
| // Predicate Implementation |
| //////////////////////////// |
| |
| public boolean evaluate(Object o) { |
| return !_ct.getRemoved().contains(o); |
| } |
| |
| ////////////////////////////////////////// |
| // CollectionChangeTracker Implementation |
| ////////////////////////////////////////// |
| |
| public boolean isTracking() { |
| return _ct.isTracking(); |
| } |
| |
| public void startTracking() { |
| _ct.startTracking(); |
| reset(); |
| } |
| |
| public void stopTracking() { |
| _ct.stopTracking(); |
| reset(); |
| } |
| |
| private void reset() { |
| if (_count != Integer.MAX_VALUE) |
| _count = -1; |
| } |
| |
| public Collection getAdded() { |
| return _ct.getAdded(); |
| } |
| |
| public Collection getRemoved() { |
| return _ct.getRemoved(); |
| } |
| |
| public Collection getChanged() { |
| return _ct.getChanged(); |
| } |
| |
| public void added(Object val) { |
| _ct.added(val); |
| } |
| |
| public void removed(Object val) { |
| _ct.removed(val); |
| } |
| |
| public int getNextSequence() { |
| return _ct.getNextSequence(); |
| } |
| |
| public void setNextSequence(int seq) { |
| _ct.setNextSequence(seq); |
| } |
| |
| /** |
| * Wrapper around our filtering iterator chain. |
| */ |
| private class Itr |
| implements Iterator, Closeable { |
| |
| private static final int OPEN = 0; |
| private static final int LAST_ELEM = 1; |
| private static final int CLOSED = 2; |
| |
| private final IteratorChain _itr; |
| private Object _last = null; |
| private int _state = OPEN; |
| |
| public Itr(IteratorChain itr) { |
| _itr = itr; |
| } |
| |
| public boolean hasNext() { |
| if (_state == CLOSED) |
| return false; |
| |
| // close automatically if no more elements |
| if (!_itr.hasNext()) { |
| free(); |
| _state = LAST_ELEM; |
| return false; |
| } |
| return true; |
| } |
| |
| public Object next() { |
| if (_state != OPEN) |
| throw new NoSuchElementException(); |
| _last = _itr.next(); |
| return _last; |
| } |
| |
| public void remove() { |
| if (_state == CLOSED || _last == null) |
| throw new NoSuchElementException(); |
| Proxies.dirty(AbstractLRSProxyCollection.this, false); |
| _ct.removed(_last); |
| Proxies.removed(AbstractLRSProxyCollection.this, _last, false); |
| _last = null; |
| } |
| |
| public void close() { |
| free(); |
| _state = CLOSED; |
| } |
| |
| private void free() { |
| if (_state != OPEN) |
| return; |
| |
| List itrs = _itr.getIterators(); |
| Iterator itr; |
| for (int i = 0; i < itrs.size(); i++) { |
| itr = (Iterator) itrs.get(i); |
| if (itr instanceof FilterIterator) |
| itr = ((FilterIterator) itr).getIterator(); |
| ImplHelper.close(itr); |
| } |
| } |
| |
| protected void finalize() { |
| close(); |
| } |
| } |
| } |