/*
* Copyright 2011 Sven Buschbeck.
*
* This file is part of the ActiveObjects library.
*
* ActiveObjects is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ActiveObjects 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Foobar. If not, see <http://www.gnu.org/licenses/>.
*/
package net.svenbuschbeck.gwt.ao.client;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import net.svenbuschbeck.gwt.ao.client.events.HasValueAddedHandlers;
import net.svenbuschbeck.gwt.ao.client.events.HasValueChangedHandlers;
import net.svenbuschbeck.gwt.ao.client.events.HasValueRemovedHandlers;
import net.svenbuschbeck.gwt.ao.client.events.ValueAddedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueAddedHandler;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueChangedHandler;
import net.svenbuschbeck.gwt.ao.client.events.ValueRemovedEvent;
import net.svenbuschbeck.gwt.ao.client.events.ValueRemovedHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.HandlerRegistration;
/**
* Maintains a list of widgets, fires valueChangeEvents if an view is added
*
* @author Sven Buschbeck
*
* @param <T>
*
* OPTIMIZE Change HandlerManager (deprecated) to whatever it is
* supposed to be now.
*/
@SuppressWarnings("deprecation") public class ActiveList<T> implements HasValueAddedHandlers<T>,
HasValueRemovedHandlers<T>, HasValueChangedHandlers<List<T>>, Iterable<T>, List<T> {
public abstract class ReadOnlyAddValueHook implements ReadOnlyHook<T> {
@Override public void commitValue(T value) {
_add(value);
}
}
public abstract class ReadOnlyRemoveValueHook implements ReadOnlyHook<T> {
@Override public void commitValue(T value) {
_remove(value);
}
}
private LinkedList<T> collection = new LinkedList<T>();
private T[] dummyForConvertion = null;
private HandlerManager manager = new HandlerManager(null);
private final boolean valuesHaveToBeUnique;
private final boolean valuesCanBeNull;
private Long maxSize = null;
private final ReadOnlyAddValueHook readOnlyAddValueHook;
private final ReadOnlyRemoveValueHook readOnlyRemoveValueHook;
/**
* Creates ActiveCollection setting uniqueValues=false.
*/
public ActiveList() {
this(false);
}
/**
* Creates an ActiveCollection requiring all it's elements to be a.equals(b)
* == false.
*
* @param uniqueValues
*/
public ActiveList(boolean uniqueValues) {
this(uniqueValues, true);
}
/**
* Creates ActiveCollection setting uniqueValues=false.
*
* @param adoptCollection
*/
public ActiveList(Collection<T> adoptCollection, ReadOnlyAddValueHook readOnlyAddValueHook,
ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
this(false, adoptCollection, readOnlyAddValueHook, readOnlyRemoveValueHook);
}
/**
* Creates ActiveCollection setting valuesCanBeNull=true.
*
* @param valuesHaveToBeUnique
* @param adoptCollection
*/
public ActiveList(boolean valuesHaveToBeUnique, Collection<T> adoptCollection) {
this(valuesHaveToBeUnique, adoptCollection, null, null);
}
/**
* Creates ActiveCollection setting valuesCanBeNull=true.
*
* @param valuesHaveToBeUnique
* @param adoptCollection
*/
public ActiveList(boolean valuesHaveToBeUnique, Collection<T> adoptCollection,
ReadOnlyAddValueHook readOnlyAddValueHook, ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
this(valuesHaveToBeUnique, true, adoptCollection, readOnlyAddValueHook, readOnlyRemoveValueHook);
}
/**
* Creates ActiveCollection setting initial collection to null (will cause
* creating an empty collection).
*
* @param valuesHaveToBeUnique
* @param valuesCanBeNull
*/
public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull) {
this(valuesHaveToBeUnique, valuesCanBeNull, null, null);
}
/**
* Creates ActiveCollection setting initial collection to null (will cause
* creating an empty collection).
*
* @param valuesHaveToBeUnique
* @param valuesCanBeNull
*/
public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, ReadOnlyAddValueHook readOnlyAddValueHook,
ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
this(valuesHaveToBeUnique, valuesCanBeNull, null, readOnlyAddValueHook, readOnlyRemoveValueHook);
}
/**
* Creates ActiveCollection setting trickerValueAddedEventForAll=false.
*
* @param valuesHaveToBeUnique
* @param valuesCanBeNull
* @param adoptCollection
*/
public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, Collection<T> adoptCollection) {
this(valuesHaveToBeUnique, valuesCanBeNull, adoptCollection, null, null);
}
/**
* Creates ActiveCollection setting trickerValueAddedEventForAll=false.
*
* @param valuesHaveToBeUnique
* @param valuesCanBeNull
* @param adoptCollection
*/
public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, Collection<T> adoptCollection,
ReadOnlyAddValueHook readOnlyAddValueHook, ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
this(valuesHaveToBeUnique, valuesCanBeNull, adoptCollection, false, readOnlyAddValueHook,
readOnlyRemoveValueHook);
}
/**
* Creates ActiveCollection with all parameters there are
*
* @param valuesHaveToBeUnique
* @param valuesCanBeNull
* @param adoptCollection
* @param trickerValueAddedEventForAll
*/
public ActiveList(boolean valuesHaveToBeUnique, boolean valuesCanBeNull, Collection<T> adoptCollection,
boolean trickerValueAddedEventForAll, ReadOnlyAddValueHook readOnlyAddValueHook,
ReadOnlyRemoveValueHook readOnlyRemoveValueHook) {
this.valuesHaveToBeUnique = valuesHaveToBeUnique;
this.valuesCanBeNull = valuesCanBeNull;
this.readOnlyAddValueHook = readOnlyAddValueHook;
this.readOnlyRemoveValueHook = readOnlyRemoveValueHook;
adopt(adoptCollection, trickerValueAddedEventForAll);
}
public void adopt(Collection<T> adoptCollection, boolean trickerValueAddedEventForAll) {
collection = (adoptCollection != null) ? new LinkedList<T>(adoptCollection) : new LinkedList<T>();
if (trickerValueAddedEventForAll) {
trickerValueAddedEventForAll();
}
}
private void trickerValueAddedEventForAll() {
// TODO Add option to use Scheduler.get().scheduleIncremental() to fire each event using a scheduler not to block UI
for (T value : collection) {
ValueAddedEvent.fire(this, value);
}
fireValueChangeEvent(new LinkedList<T>(), getStaticCopy());
}
@Override public void fireEvent(GwtEvent<?> event) {
manager.fireEvent(event);
}
/**
* Adds and returns e. Returning e allows to concatenate further operations
* on e, e.g. added e to two collections in one line:
* collection2.add(collection.add(e));
*
* @param e
* @return
*/
public T addAndReturn(T e) {
add(e);
return e;
}
@Override public boolean add(T e) {
if (readOnlyAddValueHook != null) {
readOnlyAddValueHook.onNewValue(e);
return true;
}
return _add(e);
}
/*
* (non-Javadoc)
*
* @see java.util.List#add(int, java.lang.Object)
*/
@Override public void add(int arg0, T arg1) {
}
private boolean _add(T e) {
List<T> before = getStaticCopy();
boolean gotAdded = ValueAddedEvent.addAndFireIfRequired(this, collection, e, valuesHaveToBeUnique,
valuesCanBeNull, maxSize);
if (gotAdded) {
fireValueChangeEvent(before, getStaticCopy());
}
return gotAdded;
}
/**
* Removes given object from collection. If contained, ValueRemovedEvent
* gets fired.
*
* @param e
* @return e
*/
public T removeAndReturn(T e) {
_remove(e);
return e;
}
@SuppressWarnings("unchecked") @Override public boolean remove(Object e) {
if (readOnlyRemoveValueHook != null) {
readOnlyAddValueHook.onNewValue((T) e);
return true;
}
return _remove((T) e);
}
private boolean _remove(T e) {
List<T> before = getStaticCopy();
if (ValueRemovedEvent.removeAndFireIfNotNull(this, collection, e)) {
fireValueChangeEvent(before, getStaticCopy());
return true;
}
return false;
}
private void fireValueChangeEvent(List<T> before, List<T> after) {
ValueChangedEvent.fire(this, before, after);
}
@Override public HandlerRegistration addValueAddedHandler(ValueAddedHandler<T> handler) {
return addValueAddedHandler(handler, false);
}
public HandlerRegistration addValueAddedHandler(ValueAddedHandler<T> handler, boolean trickerValueAddedEventForAll) {
HandlerRegistration handlerRegistration = manager.addHandler(ValueAddedEvent.getType(), handler);
if (trickerValueAddedEventForAll) {
trickerValueAddedEventForAll();
}
return handlerRegistration;
}
@Override public HandlerRegistration addValueRemovedHandler(ValueRemovedHandler<T> handler) {
return manager.addHandler(ValueRemovedEvent.getType(), handler);
}
public HandlerRegistration addValueChangedHandler(ValueChangedHandler<List<T>> valueChangedHandler) {
return addValueChangedHandler(valueChangedHandler, false);
};
/*
* (non-Javadoc)
*
* @seenet.svenbuschbeck.gwt.ao.client.events.HasValueChangedHandlers#
* addValueChangedHandler
* (net.svenbuschbeck.gwt.ao.client.events.ValueChangedHandler, boolean)
*/
@Override public HandlerRegistration addValueChangedHandler(ValueChangedHandler<List<T>> valueChangedHandler,
boolean triggerInitially) {
HandlerRegistration handlerRegistration = manager.addHandler(ValueChangedEvent.getType(), valueChangedHandler);
List<T> staticCopy = getStaticCopy();
ValueChangedEvent.fire(this, staticCopy, staticCopy);
return handlerRegistration;
}
// Implement Iterator<T> //////////////////////////////////////////////////
@Override public Iterator<T> iterator() {
// Can cause CooncurrentUpdateExceptions, so it's always better to iterate over a copy
// return collection.iterator();
return getStaticCopy().iterator();
}
// Implement Collection<T> ////////////////////////////////////////////////
@Override public int size() {
return collection.size();
}
@Override public void clear() {
// iterating on the copy to avoid java.util.ConcurrentModificationException as the removeValue events might cause the collection to be changed in parallel
// T[] copy = (T[]) collection.toArray();
// for (T element : copy) {
// remove(element);
// }
// idea of copying collection implemented in forEach method
forEach(new ForEach<T>() {
@Override public void execute(T element) {
remove(element);
}
});
}
@Override public boolean addAll(Collection<? extends T> c) {
int size = collection.size();
for (T element : c) {
add(element);
}
return size != collection.size();
}
/*
* (non-Javadoc)
*
* @see java.util.List#addAll(int, java.util.Collection)
*/
@Override public boolean addAll(int arg0, Collection<? extends T> arg1) {
List<T> before = getStaticCopy();
collection.addAll(arg0, arg1);
boolean changed = before.size() != collection.size();
for (T obj : arg1) {
ValueAddedEvent.fire(this, obj);
}
if (changed) {
fireValueChangeEvent(collection, collection);
}
return changed;
}
@Override public boolean contains(Object o) {
return collection.contains(o);
}
@Override public boolean containsAll(Collection<?> c) {
return collection.containsAll(c);
}
@Override public boolean isEmpty() {
return collection.isEmpty();
}
@Override public boolean removeAll(Collection<?> c) {
int size = collection.size();
for (Object element : c) {
remove(element);
}
return size != collection.size();
}
@Override public boolean retainAll(final Collection<?> c) {
int size = collection.size();
// for (T element : collection) {
// if (!c.contains(element)) {
// remove(element);
// }
// }
forEach(new ForEach<T>() {
@Override public void execute(T element) {
if (!c.contains(element)) {
remove(element);
}
}
});
return size != collection.size();
}
@Override public Object[] toArray() {
return collection.toArray();
}
@Override public <T2> T2[] toArray(T2[] a) {
return collection.toArray(a);
}
/**
* Use this method to iterate over all elements in this collection avoiding
* "concurrent change" exceptions.
*
* @param forEach
* callback method
*/
public void forEach(ForEach<T> forEach) {
// copy collection to not run into "concurrent change" exceptions.
// for (T object : new ArrayList<T>(collection)) {
Collection<T> staticCopy = getStaticCopy();
if (staticCopy != null) {
for (T object : staticCopy) {
forEach.execute(object);
}
}
}
/**
* Returns a static copy of the containing collection of objects. Use this
* collection to iterate over it over time to avoid "concurrent change"
* exceptions.
*
* @return Independent copy of internal collection.
*/
public List<T> getStaticCopy() {
assert collection != null : "Inner collection must not be null.";
return new ArrayList<T>(collection);
}
/**
* @param maximumNumberOfCanvasMedia
*/
public void setMaxSize(Long maximumNumberOfElements) {
this.maxSize = Math.max(0, maximumNumberOfElements);
while (size() > maximumNumberOfElements) {
pop();
}
}
/**
* Removes and returns last element in collection
*/
public T pop() {
return removeAndReturn(collection.getLast());
}
public Long getMaxSize() {
return maxSize;
}
public void clearMaxSize() {
maxSize = null;
}
/**
* @return
*/
public boolean hasFreeSlots() {
return size() < getMaxSize();
}
/**
* Returns the element at the given index. Makes use of direct access if
* containing collection is a list or converts collection to array
* otherwise.
*
* @param index
* @return
* @throws IndexOutOfBoundsException
*/
public T get(int index) {
if (collection instanceof List) {
return ((List<T>) collection).get(index);
}
return collection.toArray(dummyForConvertion)[index];
}
/**
* Returns the index of the given object in this collection or -1 if it is
* not contained. Makes use of direct access if containing collection is a
* list or iterates otherwise
*
* @param object
* @return index of the given object or -1 if the object is not contained in
* this collection
*/
@Override public int indexOf(Object object) {
// if (object != null) {
// if (collection instanceof List) {
return ((List<T>) collection).indexOf(object);
// }
// T[] array = collection.toArray(dummyForConvertion);
// for (int x = 0; x < array.length; x++) {
// if (object.equals(array[x])) {
// return x;
// }
// }
// }
// return -1;
}
/**
* Returns the first element that is object.equals(element)
*
* @param object
* @return element that equals the given object.
*/
public T getFirstObjectEqualTo(T object) {
int index = indexOf(object);
if (index >= 0) {
return get(index);
}
return null;
}
/*
* (non-Javadoc)
*
* @see java.lang.Object#toString()
*/
@Override public String toString() {
return "ActiveCollection(size=" + size() + "; maxSize=" + getMaxSize() + ")";
}
public boolean isReadOnly() {
return readOnlyAddValueHook != null;
}
/*
* (non-Javadoc)
*
* @see java.util.List#lastIndexOf(java.lang.Object)
*/
@Override public int lastIndexOf(Object arg0) {
return collection.lastIndexOf(arg0);
}
/*
* (non-Javadoc)
*
* @see java.util.List#listIterator()
*/
@Override public ListIterator<T> listIterator() {
return collection.listIterator();
}
/*
* (non-Javadoc)
*
* @see java.util.List#listIterator(int)
*/
@Override public ListIterator<T> listIterator(int arg0) {
return collection.listIterator(arg0);
}
/*
* (non-Javadoc)
*
* @see java.util.List#remove(int)
*/
@Override public T remove(int index) {
T removed = collection.remove(index);
if (removed != null) {
ValueRemovedEvent.fire(this, removed);
}
return removed;
}
/*
* (non-Javadoc)
*
* @see java.util.List#set(int, java.lang.Object)
*/
@Override public T set(int index, T newObject) {
T oldObject = collection.get(index);
if (compare(oldObject, newObject)) {
return oldObject;
}
remove(oldObject);
add(index, newObject);
return newObject;
}
/*
* (non-Javadoc)
*
* @see java.util.List#subList(int, int)
*/
@Override public List<T> subList(int arg0, int arg1) {
return collection.subList(arg0, arg1);
}
private boolean compare(T object1, T object2) {
return object1 == object2 || (object1 != null && object1.equals(object2));
}
}