blob: 299af7bc682b289fc4e7f0c85d1e6a90a619abe1 [file] [log] [blame]
/*
* 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.AbstractCollection;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.Collection;
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.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.apache.commons.collections.iterators.IteratorChain;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.lib.util.Closeable;
import org.apache.openjpa.lib.util.Localizer;
/**
* A map proxy designed for maps backed by extremely large result sets in
* which each call to {@link #get} or {@link #containsKey} may perform a
* database query. Changes to the map are tracked through a
* {@link ChangeTracker}. This map has the following limitations:
* <ul>
* <li>The <code>size</code> method may return {@link Integer#MAX_VALUE}.</li>
* <li>Null keys and values are not supported.</li>
* </ul>
*
* @author Abe White
*/
public abstract class AbstractLRSProxyMap
implements Map, LRSProxy, MapChangeTracker, Predicate {
private static final int MODE_KEY = 0;
private static final int MODE_VALUE = 1;
private static final int MODE_ENTRY = 2;
private static final Localizer _loc = Localizer.forPackage
(AbstractLRSProxyMap.class);
private Class _keyType = null;
private Class _valueType = null;
private MapChangeTrackerImpl _ct = null;
private OpenJPAStateManager _sm = null;
private int _field = -1;
private OpenJPAStateManager _origOwner = null;
private int _origField = -1;
private Map _map = null;
private int _count = -1;
private boolean _iterated = false;
public AbstractLRSProxyMap(Class keyType, Class valueType) {
_keyType = keyType;
_valueType = valueType;
_ct = new MapChangeTrackerImpl(this);
_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 rollbac; we don't store lrs fields
return null;
}
/**
* 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;
}
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 boolean containsKey(Object key) {
if (_keyType != null && !_keyType.isInstance(key))
return false;
if (_map != null && _map.containsKey(key))
return true;
if (_ct.getTrackKeys()) {
if (_ct.getRemoved().contains(key))
return false;
return hasKey(key);
}
// value tracking:
// if we've removed values, we need to see if this key represents
// a removed instance. otherwise we can rely on the 1-1 between
// keys and values when using value tracking
if (_ct.getRemoved().isEmpty())
return hasKey(key);
return get(key) != null;
}
public boolean containsValue(Object val) {
if (_valueType != null && !_valueType.isInstance(val))
return false;
if (_map != null && _map.containsValue(val))
return true;
if (!_ct.getTrackKeys()) {
if (_ct.getRemoved().contains(val))
return false;
return hasValue(val);
}
// key tracking
Collection keys = keys(val);
if (keys == null || keys.isEmpty())
return false;
keys.removeAll(_ct.getRemoved());
keys.removeAll(_ct.getChanged());
return keys.size() > 0;
}
public Object get(Object key) {
if (_keyType != null && !_keyType.isInstance(key))
return null;
Object ret = (_map == null) ? null : _map.get(key);
if (ret != null)
return ret;
if (_ct.getTrackKeys() && _ct.getRemoved().contains(key))
return null;
Object val = value(key);
if (!_ct.getTrackKeys() && _ct.getRemoved().contains(val))
return null;
return val;
}
public Object put(Object key, Object value) {
Proxies.assertAllowedType(key, _keyType);
Proxies.assertAllowedType(value, _valueType);
Proxies.dirty(this, false);
if (_map == null)
_map = new HashMap();
Object old = _map.put(key, value);
if (old == null && (!_ct.getTrackKeys()
|| !_ct.getRemoved().contains(key)))
old = value(key);
if (old != null) {
_ct.changed(key, old, value);
Proxies.removed(this, old, false);
} else
_ct.added(key, value);
return old;
}
public void putAll(Map m) {
Map.Entry entry;
for (Iterator itr = m.entrySet().iterator(); itr.hasNext();) {
entry = (Map.Entry) itr.next();
put(entry.getKey(), entry.getValue());
}
}
public Object remove(Object key) {
Proxies.dirty(this, false);
Object old = (_map == null) ? null : _map.remove(key);
if (old == null && (!_ct.getTrackKeys()
|| !_ct.getRemoved().contains(key)))
old = value(key);
if (old != null) {
_ct.removed(key, old);
Proxies.removed(this, key, true);
Proxies.removed(this, old, false);
}
return old;
}
public void clear() {
Proxies.dirty(this, false);
Itr itr = iterator(MODE_ENTRY);
try {
Map.Entry entry;
while (itr.hasNext()) {
entry = (Map.Entry) itr.next();
Proxies.removed(this, entry.getKey(), true);
Proxies.removed(this, entry.getValue(), false);
_ct.removed(entry.getKey(), entry.getValue());
}
}
finally {
itr.close();
}
}
public Set keySet() {
return new AbstractSet() {
public int size() {
return AbstractLRSProxyMap.this.size();
}
public boolean remove(Object o) {
return AbstractLRSProxyMap.this.remove(o) != null;
}
public Iterator iterator() {
return AbstractLRSProxyMap.this.iterator(MODE_KEY);
}
};
}
public Collection values() {
return new AbstractCollection() {
public int size() {
return AbstractLRSProxyMap.this.size();
}
public Iterator iterator() {
return AbstractLRSProxyMap.this.iterator(MODE_VALUE);
}
};
}
public Set entrySet() {
return new AbstractSet() {
public int size() {
return AbstractLRSProxyMap.this.size();
}
public Iterator iterator() {
return AbstractLRSProxyMap.this.iterator(MODE_ENTRY);
}
};
}
protected Object writeReplace()
throws ObjectStreamException {
Itr itr = iterator(MODE_ENTRY);
try {
Map map = new HashMap();
Map.Entry entry;
while (itr.hasNext()) {
entry = (Map.Entry) itr.next();
map.put(entry.getKey(), entry.getValue());
}
return map;
} finally {
itr.close();
}
}
/**
* Return whether the given key exists in the map.
*/
protected abstract boolean hasKey(Object key);
/**
* Return whether the given value exists in the map.
*/
protected abstract boolean hasValue(Object value);
/**
* Return all keys for the given value.
*/
protected abstract Collection keys(Object value);
/**
* Return the value of the given key.
*/
protected abstract Object value(Object key);
/**
* Implement this method to return an iterator over the entries
* in the map. Each returned object must implement the
* <code>Map.Entry</code> interface. This method may be invoked multiple
* times. The 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 the number of entries in the map, or {@link Integer#MAX_VALUE}.
*/
protected abstract int count();
private Itr iterator(int mode) {
_iterated = true;
// have to copy the entry set of _map to prevent concurrent mod errors
IteratorChain chain = new IteratorChain();
if (_map != null)
chain.addIterator(new ArrayList(_map.entrySet()).iterator());
chain.addIterator(new FilterIterator(itr(), this));
return new Itr(mode, chain);
}
////////////////////////////
// Predicate Implementation
////////////////////////////
public boolean evaluate(Object obj) {
Map.Entry entry = (Map.Entry) obj;
return (_ct.getTrackKeys()
&& !_ct.getRemoved().contains(entry.getKey())
|| (!_ct.getTrackKeys()
&& !_ct.getRemoved().contains(entry.getValue())))
&& (_map == null || !_map.containsKey(entry.getKey()));
}
///////////////////////////////////
// MapChangeTracker Implementation
///////////////////////////////////
public boolean isTracking() {
return _ct.isTracking();
}
public void startTracking() {
_ct.startTracking();
reset();
}
public void stopTracking() {
_ct.stopTracking();
reset();
}
private void reset() {
if (_map != null)
_map.clear();
if (_count != Integer.MAX_VALUE)
_count = -1;
}
public boolean getTrackKeys() {
return _ct.getTrackKeys();
}
public void setTrackKeys(boolean keys) {
_ct.setTrackKeys(keys);
}
public Collection getAdded() {
return _ct.getAdded();
}
public Collection getRemoved() {
return _ct.getRemoved();
}
public Collection getChanged() {
return _ct.getChanged();
}
public void added(Object key, Object val) {
_ct.added(key, val);
}
public void removed(Object key, Object val) {
_ct.removed(key, val);
}
public void changed(Object key, Object orig, Object val) {
_ct.changed(key, orig, 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 int _mode;
private final IteratorChain _itr;
private Map.Entry _last = null;
private int _state = OPEN;
public Itr(int mode, IteratorChain itr) {
_mode = mode;
_itr = itr;
}
public boolean hasNext() {
if (_state != OPEN)
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 = (Map.Entry) _itr.next();
switch (_mode) {
case MODE_KEY:
return _last.getKey();
case MODE_VALUE:
return _last.getValue();
default:
return _last;
}
}
public void remove() {
if (_state == CLOSED || _last == null)
throw new NoSuchElementException();
Proxies.dirty(AbstractLRSProxyMap.this, false);
Proxies.removed(AbstractLRSProxyMap.this, _last.getKey(), true);
Proxies.removed(AbstractLRSProxyMap.this, _last.getValue(), false);
// need to get a reference to the key before we remove it
// from the map, since in JDK 1.3-, the act of removing an entry
// from the map will also null the entry's value, which would
// result in incorrectly passing a null to the change tracker
Object key = _last.getKey();
Object value = _last.getValue();
if (_map != null)
_map.remove(key);
_ct.removed(key, value);
_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 ();
}
}
}