blob: 34bc22561e283e76cdff7a735c7cff05fc4135da [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2010 Matthew Hall and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Matthew Hall - initial API and implementation (bug 194734)
* Matthew Hall - bugs 265561, 262287, 268203, 268688, 301774
* Florian Pirchner - ensured proper diff calculation for Vaadin containers
*
******************************************************************************/
package org.eclipse.osbp.runtime.web.vaadin.databinding.values;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.core.databinding.observable.Diffs;
import org.eclipse.core.databinding.observable.ObservableTracker;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.list.AbstractObservableList;
import org.eclipse.core.databinding.observable.list.ListDiff;
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
import org.eclipse.core.databinding.property.INativePropertyListener;
import org.eclipse.core.databinding.property.IProperty;
import org.eclipse.core.databinding.property.IPropertyObservable;
import org.eclipse.core.databinding.property.ISimplePropertyListener;
import org.eclipse.core.databinding.property.SimplePropertyEvent;
import org.eclipse.core.databinding.property.list.SimpleListProperty;
// TODO: Auto-generated Javadoc
/**
* The Class SimpleVaadinPropertyObservableList.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public class SimpleVaadinPropertyObservableList extends AbstractObservableList
implements IPropertyObservable {
/** The source. */
private Object source;
/** The property. */
private SimpleListProperty property;
/** The updating. */
private volatile boolean updating = false;
/** The mod count. */
private volatile int modCount = 0;
/** The listener. */
private INativePropertyListener listener;
/** The cached list. */
private List cachedList;
/** The stale. */
private boolean stale;
/**
* Instantiates a new simple vaadin property observable list.
*
* @param realm
* the realm
* @param source
* the source
* @param property
* the property
*/
public SimpleVaadinPropertyObservableList(Realm realm, Object source,
SimpleListProperty property) {
super(realm);
this.source = source;
this.property = property;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#firstListenerAdded()
*/
protected void firstListenerAdded() {
if (!isDisposed()) {
if (listener == null) {
listener = property
.adaptListener(new ISimplePropertyListener() {
public void handleEvent(
final SimplePropertyEvent event) {
if (!isDisposed() && !updating) {
getRealm().exec(new Runnable() {
public void run() {
if (event.type == SimplePropertyEvent.CHANGE) {
modCount++;
notifyIfChanged((ListDiff) event.diff);
} else if (event.type == SimplePropertyEvent.STALE
&& !stale) {
stale = true;
fireStale();
}
}
});
}
}
});
}
getRealm().exec(new Runnable() {
public void run() {
cachedList = new ArrayList(getList());
stale = false;
if (listener != null)
listener.addTo(source);
}
});
}
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#lastListenerRemoved()
*/
protected void lastListenerRemoved() {
if (listener != null)
listener.removeFrom(source);
cachedList = null;
stale = false;
}
/**
* Gets the ter called.
*
* @return the ter called
*/
private void getterCalled() {
ObservableTracker.getterCalled(this);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.IObservableList#getElementType()
*/
public Object getElementType() {
return property.getElementType();
}
// Queries
/**
* Gets the list.
*
* @return the list
*/
private List getList() {
return property.getList(source);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#doGetSize()
*/
protected int doGetSize() {
return getList().size();
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#contains(java.lang.Object)
*/
public boolean contains(Object o) {
getterCalled();
return getList().contains(o);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#containsAll(java.util.Collection)
*/
public boolean containsAll(Collection c) {
getterCalled();
return getList().containsAll(c);
}
/* (non-Javadoc)
* @see java.util.AbstractList#get(int)
*/
public Object get(int index) {
getterCalled();
return getList().get(index);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#indexOf(java.lang.Object)
*/
public int indexOf(Object o) {
getterCalled();
return getList().indexOf(o);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#isEmpty()
*/
public boolean isEmpty() {
getterCalled();
return getList().isEmpty();
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#lastIndexOf(java.lang.Object)
*/
public int lastIndexOf(Object o) {
getterCalled();
return getList().lastIndexOf(o);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#toArray()
*/
public Object[] toArray() {
getterCalled();
return getList().toArray();
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#toArray(java.lang.Object[])
*/
public Object[] toArray(Object[] a) {
getterCalled();
return getList().toArray(a);
}
// Single change operations
/**
* Update list.
*
* @param list
* the list
* @param diff
* the diff
*/
private void updateList(List list, ListDiff diff) {
if (!diff.isEmpty()) {
boolean wasUpdating = updating;
updating = true;
try {
property.updateList(source, diff);
modCount++;
} finally {
updating = wasUpdating;
}
notifyIfChanged(null);
}
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#add(java.lang.Object)
*/
public boolean add(Object o) {
checkRealm();
List list = getList();
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
list.size(), true, o));
updateList(list, diff);
return true;
}
/* (non-Javadoc)
* @see java.util.AbstractList#add(int, java.lang.Object)
*/
public void add(int index, Object o) {
checkRealm();
List list = getList();
if (index < 0 || index > list.size())
throw new IndexOutOfBoundsException();
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index,
true, o));
updateList(list, diff);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#iterator()
*/
public Iterator iterator() {
getterCalled();
return new Iterator() {
int expectedModCount = modCount;
List list = new ArrayList(getList());
ListIterator iterator = list.listIterator();
Object lastElement = null;
int lastIndex = -1;
public boolean hasNext() {
getterCalled();
checkForComodification();
return iterator.hasNext();
}
public Object next() {
getterCalled();
checkForComodification();
Object next = lastElement = iterator.next();
lastIndex = iterator.previousIndex();
return next;
}
public void remove() {
checkRealm();
checkForComodification();
if (lastIndex == -1)
throw new IllegalStateException();
iterator.remove(); // stay in sync
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
lastIndex, false, lastElement));
updateList(list, diff);
lastElement = null;
lastIndex = -1;
expectedModCount = modCount;
}
private void checkForComodification() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
}
};
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#move(int, int)
*/
public Object move(int oldIndex, int newIndex) {
checkRealm();
List list = getList();
int size = list.size();
if (oldIndex < 0 || oldIndex >= size || newIndex < 0
|| newIndex >= size)
throw new IndexOutOfBoundsException();
if (oldIndex == newIndex)
return list.get(oldIndex);
Object element = list.get(oldIndex);
ListDiff diff = Diffs.createListDiff(
Diffs.createListDiffEntry(oldIndex, false, element),
Diffs.createListDiffEntry(newIndex, true, element));
updateList(list, diff);
return element;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#remove(java.lang.Object)
*/
public boolean remove(Object o) {
checkRealm();
List list = getList();
int index = list.indexOf(o);
if (index == -1)
return false;
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index,
false, o));
updateList(list, diff);
return true;
}
/* (non-Javadoc)
* @see java.util.AbstractList#listIterator()
*/
public ListIterator listIterator() {
return listIterator(0);
}
/* (non-Javadoc)
* @see java.util.AbstractList#listIterator(int)
*/
public ListIterator listIterator(final int index) {
getterCalled();
return new ListIterator() {
int expectedModCount = modCount;
List list = new ArrayList(getList());
ListIterator iterator = list.listIterator(index);
Object lastElement = null;
int lastIndex = -1;
public boolean hasNext() {
getterCalled();
checkForComodification();
return iterator.hasNext();
}
public int nextIndex() {
getterCalled();
checkForComodification();
return iterator.nextIndex();
}
public Object next() {
getterCalled();
checkForComodification();
lastElement = iterator.next();
lastIndex = iterator.previousIndex();
return lastElement;
}
public boolean hasPrevious() {
getterCalled();
checkForComodification();
return iterator.hasPrevious();
}
public int previousIndex() {
getterCalled();
checkForComodification();
return iterator.previousIndex();
}
public Object previous() {
getterCalled();
checkForComodification();
lastElement = iterator.previous();
lastIndex = iterator.nextIndex();
return lastElement;
}
public void add(Object o) {
checkRealm();
checkForComodification();
int index = iterator.nextIndex();
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
index, true, o));
updateList(list, diff);
iterator.add(o); // keep in sync
lastElement = null;
lastIndex = -1;
expectedModCount = modCount;
}
public void set(Object o) {
checkRealm();
checkForComodification();
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
lastIndex, false, lastElement), Diffs
.createListDiffEntry(lastIndex, true, o));
updateList(list, diff);
iterator.set(o);
lastElement = o;
expectedModCount = modCount;
}
public void remove() {
checkRealm();
checkForComodification();
if (lastIndex == -1)
throw new IllegalStateException();
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(
lastIndex, false, lastElement));
updateList(list, diff);
iterator.remove(); // keep in sync
lastElement = null;
lastIndex = -1;
expectedModCount = modCount;
}
private void checkForComodification() {
if (expectedModCount != modCount)
throw new ConcurrentModificationException();
}
};
}
/* (non-Javadoc)
* @see java.util.AbstractList#remove(int)
*/
public Object remove(int index) {
checkRealm();
List list = getList();
Object element = list.get(index);
ListDiff diff = Diffs.createListDiff(Diffs.createListDiffEntry(index,
false, element));
updateList(list, diff);
return element;
}
/* (non-Javadoc)
* @see java.util.AbstractList#set(int, java.lang.Object)
*/
public Object set(int index, Object o) {
checkRealm();
List list = getList();
Object oldElement = list.get(index);
ListDiff diff = Diffs.createListDiff(
Diffs.createListDiffEntry(index, false, oldElement),
Diffs.createListDiffEntry(index, true, o));
updateList(list, diff);
return oldElement;
}
/* (non-Javadoc)
* @see java.util.AbstractList#subList(int, int)
*/
public List subList(int fromIndex, int toIndex) {
getterCalled();
return Collections.unmodifiableList(getList().subList(fromIndex,
toIndex));
}
// Bulk change operations
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#addAll(java.util.Collection)
*/
public boolean addAll(Collection c) {
checkRealm();
if (c.isEmpty())
return false;
List list = getList();
return addAll(list, list.size(), c);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#addAll(int, java.util.Collection)
*/
public boolean addAll(int index, Collection c) {
checkRealm();
if (c.isEmpty())
return false;
return addAll(getList(), index, c);
}
/**
* Adds the all.
*
* @param list
* the list
* @param index
* the index
* @param c
* the c
* @return true, if successful
*/
private boolean addAll(List list, int index, Collection c) {
if (index < 0 || index > list.size())
throw new IndexOutOfBoundsException();
ListDiffEntry[] entries = new ListDiffEntry[c.size()];
int offsetIndex = 0;
for (Iterator it = c.iterator(); it.hasNext();) {
Object element = it.next();
entries[offsetIndex] = Diffs.createListDiffEntry(index
+ offsetIndex, true, element);
offsetIndex++;
}
ListDiff diff = Diffs.createListDiff(entries);
updateList(list, diff);
return true;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#removeAll(java.util.Collection)
*/
public boolean removeAll(Collection c) {
checkRealm();
if (c.isEmpty())
return false;
List list = getList();
if (list.isEmpty())
return false;
List entries = new ArrayList();
for (ListIterator it = list.listIterator(); it.hasNext();) {
int index = it.nextIndex() - entries.size();
Object element = it.next();
if (c.contains(element)) {
entries.add(Diffs.createListDiffEntry(index, false, element));
}
}
if (entries.isEmpty())
return false;
ListDiff diff = Diffs.createListDiff((ListDiffEntry[]) entries
.toArray(new ListDiffEntry[entries.size()]));
updateList(list, diff);
return true;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#retainAll(java.util.Collection)
*/
public boolean retainAll(Collection c) {
checkRealm();
List list = getList();
if (list.isEmpty())
return false;
if (c.isEmpty()) {
clear();
return true;
}
List entries = new ArrayList();
for (ListIterator it = list.listIterator(); it.hasNext();) {
int index = it.nextIndex() - entries.size();
Object element = it.next();
if (!c.contains(element)) {
entries.add(Diffs.createListDiffEntry(index, false, element));
}
}
if (entries.isEmpty())
return false;
ListDiff diff = Diffs.createListDiff((ListDiffEntry[]) entries
.toArray(new ListDiffEntry[entries.size()]));
updateList(list, diff);
return true;
}
/* (non-Javadoc)
* @see java.util.AbstractList#clear()
*/
public void clear() {
checkRealm();
List list = getList();
if (list.isEmpty())
return;
List entries = new ArrayList();
for (ListIterator it = list.listIterator(list.size()); it.hasPrevious();) {
// always report 0 as the remove index
int index = it.previousIndex();
Object element = it.previous();
entries.add(Diffs.createListDiffEntry(index, false, element));
}
ListDiff diff = Diffs.createListDiff((ListDiffEntry[]) entries
.toArray(new ListDiffEntry[entries.size()]));
updateList(list, diff);
}
/**
* Notify if changed.
*
* @param diff
* the diff
*/
private void notifyIfChanged(ListDiff diff) {
if (hasListeners()) {
List oldList = cachedList;
List newList = cachedList = new ArrayList(getList());
// for Vaadin containers we need to calculate the diff each time.
// Vaadin containers also send list diffs, if an UI filter was added
// But an UI filter must never change the underlying datasource
diff = Diffs.computeListDiff(oldList, newList);
if (!diff.isEmpty() || stale) {
stale = false;
fireListChange(diff);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#isStale()
*/
public boolean isStale() {
getterCalled();
return stale;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#equals(java.lang.Object)
*/
public boolean equals(Object o) {
getterCalled();
return getList().equals(o);
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#hashCode()
*/
public int hashCode() {
getterCalled();
return getList().hashCode();
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.IObserving#getObserved()
*/
public Object getObserved() {
return source;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.property.IPropertyObservable#getProperty()
*/
public IProperty getProperty() {
return property;
}
/* (non-Javadoc)
* @see org.eclipse.core.databinding.observable.list.AbstractObservableList#dispose()
*/
public synchronized void dispose() {
if (!isDisposed()) {
if (listener != null)
listener.removeFrom(source);
property = null;
source = null;
listener = null;
stale = false;
}
super.dispose();
}
}