/*
GRANITE DATA SERVICES
Copyright (C) 2013 GRANITE DATA SERVICES S.A.S.
This file is part of Granite Data Services.
Granite Data Services is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
Granite Data Services is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License
for more details.
You should have received a copy of the GNU Library General Public License
along with this library; if not, see <http://www.gnu.org/licenses/>.
*/
package org.granite.client.persistence.collection;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import org.granite.client.persistence.LazyInitializationException;
import org.granite.client.persistence.Loader;
import org.granite.logging.Logger;
import org.granite.messaging.persistence.PersistentCollectionSnapshot;
import org.granite.util.TypeUtil;
/**
* @author Franck WOLFF
*/
public abstract class AbstractPersistentCollection<C> implements PersistentCollection {
private static final Logger log = Logger.getLogger(AbstractPersistentCollection.class);
private volatile C collection = null;
private volatile boolean dirty = false;
private volatile String detachedState = null;
private Loader<PersistentCollection> loader = new DefaultCollectionLoader();
private List<ChangeListener> changeListeners = new ArrayList<ChangeListener>();
private List<InitializationListener> initializationListeners = new ArrayList<InitializationListener>();
protected AbstractPersistentCollection() {
}
protected void init(C collection, String detachedState, boolean dirty) {
this.collection = collection;
if (detachedState != null)
this.detachedState = detachedState;
this.dirty = dirty;
}
public Loader<PersistentCollection> getLoader() {
return this.loader;
}
public void setLoader(Loader<PersistentCollection> loader) {
this.loader = loader;
}
protected boolean checkInitializedRead() {
if (wasInitialized())
return true;
loader.load(this, null);
return false;
}
protected void checkInitializedWrite() {
if (!wasInitialized())
throw new LazyInitializationException(getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(this)));
}
protected C getCollection() {
return collection;
}
public String getDetachedState() {
return detachedState;
}
protected ClassLoader getClassLoader() {
return Thread.currentThread().getContextClassLoader();
}
public boolean wasInitialized() {
return collection != null;
}
public boolean isDirty() {
return dirty;
}
public void dirty() {
dirty = true;
for (ChangeListener listener : changeListeners)
listener.changed(this);
}
public void clearDirty() {
dirty = false;
}
public PersistentCollection clone(boolean uninitialize) {
try {
@SuppressWarnings("unchecked")
AbstractPersistentCollection<C> collection = TypeUtil.newInstance(getClass(), AbstractPersistentCollection.class);
if (wasInitialized() && !uninitialize)
collection.init(getCollection(), getDetachedState(), isDirty());
return collection;
}
catch (Exception e) {
throw new RuntimeException("Could not clone collection " + this.getClass().getName(), e);
}
}
protected abstract PersistentCollectionSnapshot createSnapshot(Object io, boolean forReading);
protected abstract void updateFromSnapshot(ObjectInput in, PersistentCollectionSnapshot snapshot);
public void writeExternal(ObjectOutput out) throws IOException {
PersistentCollectionSnapshot snapshot = createSnapshot(out, false);
snapshot.writeExternal(out);
}
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
PersistentCollectionSnapshot snapshot = createSnapshot(in, true);
snapshot.readExternal(in);
updateFromSnapshot(in, snapshot);
}
@Override
public String toString() {
return getClass().getName() + " {initialized=" + wasInitialized() + ", dirty=" + isDirty() + "}" +
(collection != null ? ": " + collection.toString() : "");
}
class IteratorProxy<E> implements Iterator<E> {
private final Iterator<E> iterator;
public IteratorProxy(Iterator<E> iterator) {
this.iterator = iterator;
}
public boolean hasNext() {
return iterator.hasNext();
}
public E next() {
if (!checkInitializedRead())
return null;
return iterator.next();
}
public void remove() {
checkInitializedWrite();
iterator.remove();
dirty();
}
@Override
public int hashCode() {
return iterator.hashCode();
}
@Override
public boolean equals(Object obj) {
return iterator.equals(obj);
}
}
class ListIteratorProxy<E> implements ListIterator<E> {
private final ListIterator<E> iterator;
private E lastNextOrPrevious = null;
public ListIteratorProxy(ListIterator<E> iterator) {
this.iterator = iterator;
}
public boolean hasNext() {
return iterator.hasNext();
}
public E next() {
if (!checkInitializedRead())
return null;
return (lastNextOrPrevious = iterator.next());
}
public boolean hasPrevious() {
return iterator.hasPrevious();
}
public E previous() {
if (!checkInitializedRead())
return null;
return (lastNextOrPrevious = iterator.previous());
}
public int nextIndex() {
return iterator.nextIndex();
}
public int previousIndex() {
return iterator.previousIndex();
}
public void remove() {
checkInitializedWrite();
iterator.remove();
lastNextOrPrevious = null;
dirty();
}
public void set(E e) {
checkInitializedWrite();
iterator.set(e);
if (e == null ? lastNextOrPrevious != null : !e.equals(lastNextOrPrevious))
dirty();
}
public void add(E e) {
checkInitializedWrite();
iterator.add(e);
lastNextOrPrevious = null;
dirty();
}
@Override
public int hashCode() {
return iterator.hashCode();
}
@Override
public boolean equals(Object obj) {
return iterator.equals(obj);
}
}
class CollectionProxy<E> implements Collection<E> {
protected final Collection<E> collection;
public CollectionProxy(Collection<E> collection) {
this.collection = collection;
}
public int size() {
return collection.size();
}
public boolean isEmpty() {
return collection.isEmpty();
}
public boolean contains(Object o) {
return collection.contains(o);
}
public Iterator<E> iterator() {
return new IteratorProxy<E>(collection.iterator());
}
public Object[] toArray() {
return collection.toArray();
}
public <T> T[] toArray(T[] a) {
return collection.toArray(a);
}
public boolean add(E e) {
if (collection.add(e)) {
dirty();
return true;
}
return false;
}
public boolean remove(Object o) {
if (collection.remove(o)) {
dirty();
return true;
}
return false;
}
public boolean containsAll(Collection<?> c) {
return collection.containsAll(c);
}
public boolean addAll(Collection<? extends E> c) {
if (collection.addAll(c)) {
dirty();
return true;
}
return false;
}
public boolean removeAll(Collection<?> c) {
if (collection.removeAll(c)) {
dirty();
return true;
}
return false;
}
public boolean retainAll(Collection<?> c) {
if (collection.retainAll(c)) {
dirty();
return true;
}
return false;
}
public void clear() {
if (!collection.isEmpty()) {
collection.clear();
dirty();
}
}
@Override
public int hashCode() {
return collection.hashCode();
}
@Override
public boolean equals(Object obj) {
return collection.equals(obj);
}
}
class SetProxy<E> extends CollectionProxy<E> implements Set<E> {
public SetProxy(Set<E> collection) {
super(collection);
}
}
class ListProxy<E> extends CollectionProxy<E> implements List<E> {
public ListProxy(List<E> collection) {
super(collection);
}
public boolean addAll(int index, Collection<? extends E> c) {
if (((List<E>)collection).addAll(index, c)) {
dirty();
return true;
}
return false;
}
public E get(int index) {
return ((List<E>)collection).get(index);
}
public E set(int index, E element) {
E previousElement = ((List<E>)collection).set(index, element);
if (previousElement == null ? element != null : !previousElement.equals(element))
dirty();
return previousElement;
}
public void add(int index, E element) {
((List<E>)collection).add(index, element);
dirty();
}
public E remove(int index) {
E removedElement = ((List<E>)collection).remove(index);
dirty();
return removedElement;
}
public int indexOf(Object o) {
return ((List<E>)collection).indexOf(o);
}
public int lastIndexOf(Object o) {
return ((List<E>)collection).lastIndexOf(o);
}
public ListIterator<E> listIterator() {
return listIterator(0);
}
public ListIterator<E> listIterator(int index) {
return new ListIteratorProxy<E>(((List<E>)collection).listIterator(index));
}
public List<E> subList(int fromIndex, int toIndex) {
return new ListProxy<E>(((List<E>)collection).subList(fromIndex, toIndex));
}
}
class SortedSetProxy<E> extends SetProxy<E> implements SortedSet<E> {
public SortedSetProxy(SortedSet<E> collection) {
super(collection);
}
public Comparator<? super E> comparator() {
return ((SortedSet<E>)collection).comparator();
}
public SortedSet<E> subSet(E fromElement, E toElement) {
return new SortedSetProxy<E>(((SortedSet<E>)collection).subSet(fromElement, toElement));
}
public SortedSet<E> headSet(E toElement) {
return new SortedSetProxy<E>(((SortedSet<E>)collection).headSet(toElement));
}
public SortedSet<E> tailSet(E fromElement) {
return new SortedSetProxy<E>(((SortedSet<E>)collection).tailSet(fromElement));
}
public E first() {
return ((SortedSet<E>)collection).first();
}
public E last() {
return ((SortedSet<E>)collection).last();
}
}
class SortedMapProxy<K, V> implements SortedMap<K, V> {
protected final SortedMap<K, V> sortedMap;
public SortedMapProxy(SortedMap<K, V> sortedMap) {
this.sortedMap = sortedMap;
}
public int size() {
return sortedMap.size();
}
public boolean isEmpty() {
return sortedMap.isEmpty();
}
public boolean containsKey(Object key) {
return sortedMap.containsKey(key);
}
public boolean containsValue(Object value) {
return sortedMap.containsValue(value);
}
public V get(Object key) {
return sortedMap.get(key);
}
public V put(K key, V value) {
boolean containsKey = sortedMap.containsKey(key);
V previousValue = sortedMap.put(key, value);
if (!containsKey || (previousValue == null ? value != null : !previousValue.equals(value)))
dirty();
return previousValue;
}
public V remove(Object key) {
boolean containsKey = sortedMap.containsKey(key);
V removedValue = sortedMap.remove(key);
if (containsKey)
dirty();
return removedValue;
}
public void putAll(Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet())
put(entry.getKey(), entry.getValue());
}
public void clear() {
if (!sortedMap.isEmpty()) {
sortedMap.clear();
dirty();
}
}
public Comparator<? super K> comparator() {
return sortedMap.comparator();
}
public SortedMap<K, V> subMap(K fromKey, K toKey) {
return new SortedMapProxy<K, V>(sortedMap.subMap(fromKey, toKey));
}
public SortedMap<K, V> headMap(K toKey) {
return new SortedMapProxy<K, V>(sortedMap.headMap(toKey));
}
public SortedMap<K, V> tailMap(K fromKey) {
return new SortedMapProxy<K, V>(sortedMap.tailMap(fromKey));
}
public K firstKey() {
return sortedMap.firstKey();
}
public K lastKey() {
return sortedMap.lastKey();
}
public Set<K> keySet() {
return new SetProxy<K>(sortedMap.keySet());
}
public Collection<V> values() {
return new CollectionProxy<V>(sortedMap.values());
}
public Set<Entry<K, V>> entrySet() {
return new SetProxy<Entry<K, V>>(sortedMap.entrySet());
}
@Override
public int hashCode() {
return sortedMap.hashCode();
}
@Override
public boolean equals(Object obj) {
return sortedMap.equals(obj);
}
}
public void addListener(ChangeListener listener) {
if (!changeListeners.contains(listener))
changeListeners.add(listener);
}
public void removeListener(ChangeListener listener) {
changeListeners.remove(listener);
}
private static class DefaultCollectionLoader implements Loader<PersistentCollection> {
public void load(PersistentCollection collection, InitializationCallback callback) {
throw new LazyInitializationException(collection.getClass().getName() + "@" + Integer.toHexString(System.identityHashCode(collection)));
}
public void onInitializing() {
}
public void onInitialize() {
}
public void onUninitialize() {
}
}
public void addListener(InitializationListener listener) {
if (!initializationListeners.contains(listener))
initializationListeners.add(listener);
}
public void removeListener(InitializationListener listener) {
initializationListeners.remove(listener);
}
public void initializing() {
loader.onInitializing();
}
public void initialize() {
loader.onInitialize();
for (InitializationListener listener : initializationListeners)
listener.initialized(this);
doInitialize();
log.debug("initialized");
}
protected abstract void doInitialize();
public void uninitialize() {
loader.onUninitialize();
for (InitializationListener listener : initializationListeners)
listener.uninitialized(this);
log.debug("uninitialized");
}
public void withInitialized(InitializationCallback callback) {
if (wasInitialized())
callback.call(this);
else
loader.load(this, callback);
}
}