Released patch (plus one additional test) for Bug 177770 [DataBinding] Mutators not implemented on JavaBeansObservableList
diff --git a/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.java b/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.java
index 6749099..35ee4c5 100644
--- a/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.java
+++ b/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/databinding/beans/BeansObservables.java
@@ -35,9 +35,8 @@
 import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableValue;
 
 /**
- * A factory for creating observable objects of Java
- * objects that conform to the <a
- * href="http://java.sun.com/products/javabeans/docs/spec.html">JavaBean
+ * A factory for creating observable objects of Java objects that conform to the
+ * <a href="http://java.sun.com/products/javabeans/docs/spec.html">JavaBean
  * specification</a> for bound properties.
  * 
  * @since 1.1
@@ -150,7 +149,8 @@
 
 	/**
 	 * Returns an observable list in the given realm tracking the
-	 * collection-typed named property of the given bean object
+	 * collection-typed named property of the given bean object. The returned
+	 * list is mutable.
 	 * 
 	 * @param realm
 	 *            the realm
@@ -160,7 +160,7 @@
 	 *            the name of the collection-typed property
 	 * @return an observable list tracking the collection-typed named property
 	 *         of the given bean object
-	 * 
+	 * @see #observeList(Realm, Object, String, Class)
 	 */
 	public static IObservableList observeList(Realm realm, Object bean,
 			String propertyName) {
@@ -169,7 +169,12 @@
 
 	/**
 	 * Returns an observable list in the given realm tracking the
-	 * collection-typed named property of the given bean object
+	 * collection-typed named property of the given bean object. The returned
+	 * list is mutable. When an item is added or removed the setter is invoked
+	 * for the list on the parent bean to provide notification to other
+	 * listeners via <code>PropertyChangeEvents</code>. This is done to
+	 * provide the same behavior as is expected from arrays as specified in the
+	 * bean spec in section 7.2.
 	 * 
 	 * @param realm
 	 *            the realm
diff --git a/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/internal/beans/JavaBeanObservableList.java b/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/internal/beans/JavaBeanObservableList.java
index f752b17..b510f3a 100644
--- a/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/internal/beans/JavaBeanObservableList.java
+++ b/bundles/org.eclipse.core.databinding.beans/src/org/eclipse/core/internal/databinding/internal/beans/JavaBeanObservableList.java
@@ -23,12 +23,12 @@
 import java.util.Iterator;
 import java.util.List;
 
+import org.eclipse.core.databinding.BindingException;
 import org.eclipse.core.databinding.beans.IBeanObservable;
 import org.eclipse.core.databinding.observable.Diffs;
 import org.eclipse.core.databinding.observable.Realm;
 import org.eclipse.core.databinding.observable.list.ListDiffEntry;
 import org.eclipse.core.databinding.observable.list.ObservableList;
-import org.eclipse.core.runtime.Assert;
 
 /**
  * @since 1.0
@@ -87,6 +87,7 @@
 	}
 
 	private Object primGetValues() {
+		Exception ex = null;
 		try {
 			Method readMethod = descriptor.getReadMethod();
 			if (!readMethod.isAccessible()) {
@@ -94,11 +95,13 @@
 			}
 			return readMethod.invoke(object, new Object[0]);
 		} catch (IllegalArgumentException e) {
+			ex = e;
 		} catch (IllegalAccessException e) {
+			ex = e;
 		} catch (InvocationTargetException e) {
+			ex = e;
 		}
-		Assert.isTrue(false, "Could not read collection values"); //$NON-NLS-1$
-		return null;
+		throw new BindingException("Could not read collection values", ex); //$NON-NLS-1$
 	}
 
 	private Object[] getValues() {
@@ -142,17 +145,22 @@
 	}
 
 	private void primSetValues(Object newValue) {
+		Exception ex = null;
 		try {
 			Method writeMethod = descriptor.getWriteMethod();
 			if (!writeMethod.isAccessible()) {
 				writeMethod.setAccessible(true);
 			}
 			writeMethod.invoke(object, new Object[] { newValue });
+			return;
 		} catch (IllegalArgumentException e) {
+			ex = e;
 		} catch (IllegalAccessException e) {
+			ex = e;
 		} catch (InvocationTargetException e) {
+			ex = e;
 		}
-		Assert.isTrue(false, "Could not write collection values"); //$NON-NLS-1$
+		throw new BindingException("Could not write collection values", ex); //$NON-NLS-1$
 	}
 
 	public Object set(int index, Object element) {
@@ -223,7 +231,7 @@
 			int i = 0;
 			for (Iterator it = c.iterator(); it.hasNext();) {
 				Object o = it.next();
-				entries[i] = Diffs.createListDiffEntry(index++, true, o);
+				entries[i++] = Diffs.createListDiffEntry(index++, true, o);
 			}
 			fireListChange(Diffs.createListDiff(entries));
 			return result;
@@ -244,7 +252,7 @@
 			int i = 0;
 			for (Iterator it = c.iterator(); it.hasNext();) {
 				Object o = it.next();
-				entries[i] = Diffs.createListDiffEntry(index++, true, o);
+				entries[i++] = Diffs.createListDiffEntry(index++, true, o);
 			}
 			fireListChange(Diffs.createListDiff(entries));
 			return result;
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/beans/BeansObservablesTest.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/beans/BeansObservablesTest.java
index ba2ac42..5a2ed8c 100644
--- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/beans/BeansObservablesTest.java
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/databinding/beans/BeansObservablesTest.java
@@ -46,8 +46,8 @@
 
 	public void testObserveListArrayInferredElementType() throws Exception {
 		IObservableList list = BeansObservables.observeList(Realm.getDefault(),
-				model, "listArray", null);
-		assertEquals("element type", elementType, list.getElementType());
+				model, "list", null);
+		assertEquals("element type", Object.class, list.getElementType());
 	}
 
 	public void testObserveListNonInferredElementType() throws Exception {
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/Bean.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/Bean.java
index f05543d..da8baab 100644
--- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/Bean.java
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/Bean.java
@@ -24,7 +24,7 @@
 public class Bean {
 	/* package */PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
 	private String value;
-	private List list;
+	private Object[] list = new Object[0];
 	private Set set;
 
 	public Bean() {
@@ -35,7 +35,7 @@
 	}
 
 	public Bean(List list) {
-		this.list = list;
+		this.list = (Bean[]) list.toArray(new Bean[list.size()]);
 	}
 
 	public Bean(Set set) {
@@ -58,16 +58,12 @@
 		changeSupport.firePropertyChange("value", this.value, this.value = value);
 	}
 
-	public List getList() {
+	public Object[] getList() {
 		return list;
 	}
 
-	public void setList(List list) {
-		changeSupport.firePropertyChange("list", this.list, this.list = list);
-	}
-
-	public Bean[] getListArray() {
-		return (Bean[]) list.toArray(new Bean[list.size()]);
+	public void setList(Object[] elements) {
+		changeSupport.firePropertyChange("list", this.list, this.list = elements);
 	}
 
 	public Set getSet() {
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/BeanObservableListDecoratorTest.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/BeanObservableListDecoratorTest.java
index f5aa9a5..6b86e0f 100644
--- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/BeanObservableListDecoratorTest.java
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/BeanObservableListDecoratorTest.java
@@ -38,7 +38,7 @@
 		
 		Bean bean = new Bean();
 		propertyDescriptor = new PropertyDescriptor(
-				"list", Bean.class);
+				"list", Bean.class,"getList","setList");
 		observableList = new JavaBeanObservableList(
 				SWTObservables.getRealm(Display.getDefault()), bean,
 				propertyDescriptor, Bean.class);
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableArrayBasedListTest.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableArrayBasedListTest.java
new file mode 100644
index 0000000..2122a63
--- /dev/null
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableArrayBasedListTest.java
@@ -0,0 +1,467 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Brad Reynolds and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Brad Reynolds - initial API and implementation
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.databinding.internal.beans;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyDescriptor;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableList;
+import org.eclipse.jface.databinding.swt.SWTObservables;
+import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
+import org.eclipse.jface.tests.databinding.EventTrackers.ListChangeEventTracker;
+import org.eclipse.swt.widgets.Display;
+
+/**
+ * @since 1.1
+ */
+public class JavaBeanObservableArrayBasedListTest extends AbstractDefaultRealmTestCase {
+	private JavaBeanObservableList list;
+
+	private PropertyDescriptor propertyDescriptor;
+
+	private Bean bean;
+
+	private String propertyName;
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see junit.framework.TestCase#setUp()
+	 */
+	protected void setUp() throws Exception {
+		super.setUp();
+
+		propertyName = "list";
+		propertyDescriptor = new PropertyDescriptor(propertyName, Bean.class);
+		bean = new Bean(new ArrayList());
+
+		list = new JavaBeanObservableList(SWTObservables.getRealm(Display
+				.getDefault()), bean, propertyDescriptor, Bean.class);
+	}
+
+	public void testGetObserved() throws Exception {
+		assertEquals(bean, list.getObserved());
+	}
+
+	public void testGetPropertyDescriptor() throws Exception {
+		assertEquals(propertyDescriptor, list.getPropertyDescriptor());
+	}
+
+	public void testRegistersListenerAfterFirstListenerIsAdded()
+			throws Exception {
+		assertFalse(bean.changeSupport.hasListeners(propertyName));
+		list.addListChangeListener(new ListChangeEventTracker());
+		assertTrue(bean.changeSupport.hasListeners(propertyName));
+	}
+
+	public void testRemovesListenerAfterLastListenerIsRemoved()
+			throws Exception {
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertTrue(bean.changeSupport.hasListeners(propertyName));
+		list.removeListChangeListener(listener);
+		assertFalse(bean.changeSupport.hasListeners(propertyName));
+	}
+
+	public void testFiresListChangeEvents() throws Exception {
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		bean.setList(new Bean[] { new Bean() });
+		assertEquals(1, listener.count);
+	}
+
+	public void testAddAddsElement() throws Exception {
+		int count = list.size();
+		String element = "1";
+
+		assertEquals(0, count);
+		list.add(element);
+		assertEquals(count + 1, list.size());
+		assertEquals(element, bean.getList()[count]);
+	}
+
+	public void testAddListChangeEvent() throws Exception {
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		String element = "1";
+
+		list.add(element);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], true, 0, element);
+	}
+
+	public void testAddFiresPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.add("0");
+			}			
+		});
+	}
+
+	public void testAddAtIndex() throws Exception {
+		String element = "1";
+		assertEquals(0, list.size());
+
+		list.add(0, element);
+		assertEquals(element, bean.getList()[0]);
+	}
+
+	public void testAddAtIndexListChangeEvent() throws Exception {
+		String element = "1";
+		assertEquals(0, list.size());
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		list.add(0, element);
+
+		ListChangeEvent event = listener.event;
+		assertEntry(event.diff.getDifferences()[0], true, 0, element);
+	}
+	
+	public void testAddAtIndexPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.add(0, "0");
+			}			
+		});
+	}
+
+	public void testRemove() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(1, bean.getList().length);
+		list.remove(element);
+		assertEquals(0, bean.getList().length);
+	}
+
+	public void testRemoveListChangeEvent() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(1, list.size());
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		list.remove(element);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 0, element);
+	}
+	
+	public void testRemovePropertyChangeEvent() throws Exception {
+		list.add("0");
+		
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.remove("0");
+			}			
+		});
+	}
+
+	public void testRemoveAtIndex() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(element, bean.getList()[0]);
+
+		list.remove(0);
+		assertEquals(0, bean.getList().length);
+	}
+
+	public void testRemoveAtIndexListChangeEvent() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(1, list.size());
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		list.remove(0);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 0, element);
+	}
+	
+	public void testRemoveAtIndexPropertyChangeEvent() throws Exception {
+		list.add("0");
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.remove(0);
+			}			
+		});
+	}
+
+	public void testAddAll() throws Exception {
+		Collection elements = Arrays.asList(new String[] { "1", "2" });
+		assertEquals(0, list.size());
+
+		list.addAll(elements);
+
+		assertEquals(2, bean.getList().length);
+	}
+
+	public void testAddAllListChangEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		assertEquals(0, list.size());
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+		assertEquals(0, listener.count);
+
+		list.addAll(elements);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+
+		assertEntry(event.diff.getDifferences()[0], true, 0, elements.get(0));
+		assertEntry(event.diff.getDifferences()[1], true, 1, elements.get(1));
+	}
+	
+	public void testAddAllPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.addAll(Arrays.asList(new String[] {"0", "1"}));
+			}			
+		});
+	}
+
+	public void testAddAllAtIndex() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+
+		assertEquals(2, list.size());
+
+		list.addAll(2, elements);
+
+		assertEquals(4, bean.getList().length);
+		assertEquals(elements.get(0), bean.getList()[0]);
+		assertEquals(elements.get(1), bean.getList()[1]);
+	}
+
+	public void testAddAllAtIndexListChangeEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+
+		list.addAll(2, elements);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], true, 2, elements.get(0));
+		assertEntry(event.diff.getDifferences()[1], true, 3, elements.get(1));
+	}
+	
+	public void testAddAllAtIndexPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.addAll(0, Arrays.asList(new String[] {"1", "2"}));
+			}			
+		});
+	}
+
+	public void testRemoveAll() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+		list.addAll(elements);
+
+		assertEquals(4, bean.getList().length);
+		list.removeAll(elements);
+
+		assertEquals(2, bean.getList().length);
+		assertEquals(elements.get(0), bean.getList()[0]);
+		assertEquals(elements.get(1), bean.getList()[1]);
+	}
+
+	public void testRemoveAllListChangeEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+		list.addAll(elements);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		list.removeAll(elements);
+
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 0, elements.get(0));
+		assertEntry(event.diff.getDifferences()[1], false, 0, elements.get(1));
+	}
+	
+	public void testRemoveAllPropertyChangeEvent() throws Exception {
+		list.add("0");
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.removeAll(Arrays.asList(new String[] {"0"}));
+			}			
+		});
+	}
+
+	public void testRetailAll() throws Exception {
+		List elements = Arrays.asList(new String[] { "0", "1", "2", "3" });
+		list.addAll(elements);
+
+		assertEquals(4, bean.getList().length);
+
+		list.retainAll(elements.subList(0, 2));
+		assertEquals(2, bean.getList().length);
+
+		assertEquals(elements.get(0), bean.getList()[0]);
+		assertEquals(elements.get(1), bean.getList()[1]);
+	}
+
+	public void testRetainAllListChangeEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "0", "1", "2", "3" });
+		list.addAll(elements);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		list.retainAll(elements.subList(0, 2));
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 2, elements.get(2));
+		assertEntry(event.diff.getDifferences()[1], false, 2, elements.get(3));
+	}
+	
+	public void testRetainAllPropertyChangeEvent() throws Exception {
+		list.addAll(Arrays.asList(new String[] {"0", "1"}));
+		
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.retainAll(Arrays.asList(new String[] {"0"}));
+			}			
+		});
+	}
+
+	public void testSet() throws Exception {
+		String oldElement = "old";
+		String newElement = "new";
+		list.add(oldElement);
+
+		assertEquals(oldElement, bean.getList()[0]);
+
+		list.set(0, newElement);
+		assertEquals(newElement, bean.getList()[0]);
+	}
+
+	public void testSetListChangeEvent() throws Exception {
+		String oldElement = "old";
+		String newElement = "new";
+		list.add(oldElement);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+		assertEquals(0, listener.count);
+
+		list.set(0, newElement);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], true, 0, newElement);
+		assertEntry(event.diff.getDifferences()[1], false, 1, oldElement);
+	}
+
+	public void testSetPropertyChangeEvent() throws Exception {
+		list.add("0");
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.set(0, "1");
+			}			
+		});
+	}
+
+	public void testListChangeEventFiresWhenNewListIsSet() throws Exception {
+		Bean[] elements = new Bean[] { new Bean(), new Bean() };
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		bean.setList(elements);
+		assertEquals(1, listener.count);
+	}
+
+	private static void assertEntry(ListDiffEntry entry, boolean addition,
+			int position, Object element) {
+		assertEquals("addition", addition, entry.isAddition());
+		assertEquals("position", position, entry.getPosition());
+		assertEquals("element", element, entry.getElement());
+	}
+
+	private static void assertPropertyChangeEvent(Bean bean, Runnable runnable) {
+		PropertyChangeTracker listener = new PropertyChangeTracker();
+		bean.addPropertyChangeListener(listener);
+		
+		Object[] old = bean.getList();
+		assertEquals(0, listener.count);
+		
+		runnable.run();
+		
+		PropertyChangeEvent event = listener.evt;
+		assertEquals("event did not fire", 1, listener.count);
+		assertEquals("list", event.getPropertyName());
+		assertEquals("old value", old, event.getOldValue());
+		assertEquals("new value", bean.getList(), event.getNewValue());
+		assertFalse("lists are equal", bean.getList().equals(old));
+	}
+	
+	private static class PropertyChangeTracker implements
+			PropertyChangeListener {
+		int count;
+
+		PropertyChangeEvent evt;
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+		 */
+		public void propertyChange(PropertyChangeEvent evt) {
+			count++;
+			this.evt = evt;
+		}
+	}
+}
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableListTest.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableListTest.java
index 9baed2c..8b1a9d3 100644
--- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableListTest.java
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/JavaBeanObservableListTest.java
@@ -11,25 +11,34 @@
 
 package org.eclipse.core.tests.internal.databinding.internal.beans;
 
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
 import java.beans.PropertyDescriptor;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
 
-import org.eclipse.core.databinding.observable.list.IListChangeListener;
 import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
 import org.eclipse.core.internal.databinding.internal.beans.JavaBeanObservableList;
 import org.eclipse.jface.databinding.swt.SWTObservables;
 import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
+import org.eclipse.jface.tests.databinding.EventTrackers.ListChangeEventTracker;
 import org.eclipse.swt.widgets.Display;
 
 /**
- * @since 3.3
+ * @since 1.1
  */
 public class JavaBeanObservableListTest extends AbstractDefaultRealmTestCase {
-	private JavaBeanObservableList observableList;
+	private JavaBeanObservableList list;
+
 	private PropertyDescriptor propertyDescriptor;
-	private Bean bean;
+
+	private NonStandardBean bean;
+
 	private String propertyName;
-	
+
 	/*
 	 * (non-Javadoc)
 	 * 
@@ -37,52 +46,422 @@
 	 */
 	protected void setUp() throws Exception {
 		super.setUp();
-		
+
 		propertyName = "list";
-		propertyDescriptor = new PropertyDescriptor(propertyName, Bean.class);
-		bean = new Bean();
-		
-		observableList = new JavaBeanObservableList(SWTObservables
-				.getRealm(Display.getDefault()), bean, propertyDescriptor,
-				Bean.class);
+		propertyDescriptor = new PropertyDescriptor(propertyName, NonStandardBean.class);
+		bean = new NonStandardBean(new ArrayList());
+
+		list = new JavaBeanObservableList(SWTObservables.getRealm(Display
+				.getDefault()), bean, propertyDescriptor, NonStandardBean.class);
 	}
 
 	public void testGetObserved() throws Exception {
-		assertEquals(bean, observableList.getObserved());
+		assertEquals(bean, list.getObserved());
 	}
 
 	public void testGetPropertyDescriptor() throws Exception {
-		assertEquals(propertyDescriptor, observableList.getPropertyDescriptor());
+		assertEquals(propertyDescriptor, list.getPropertyDescriptor());
+	}
+
+	public void testRegistersListenerAfterFirstListenerIsAdded()
+			throws Exception {
+		assertFalse(bean.changeSupport.hasListeners(propertyName));
+		list.addListChangeListener(new ListChangeEventTracker());
+		assertTrue(bean.changeSupport.hasListeners(propertyName));
+	}
+
+	public void testRemovesListenerAfterLastListenerIsRemoved()
+			throws Exception {
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertTrue(bean.changeSupport.hasListeners(propertyName));
+		list.removeListChangeListener(listener);
+		assertFalse(bean.changeSupport.hasListeners(propertyName));
+	}
+
+	public void testFiresListChangeEvents() throws Exception {
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		bean.setList(Arrays.asList(new String[] { "value" }));
+		assertEquals(1, listener.count);
+	}
+
+	public void testAddAddsElement() throws Exception {
+		int count = list.size();
+		String element = "1";
+
+		assertEquals(0, count);
+		list.add(element);
+		assertEquals(count + 1, list.size());
+		assertEquals(element, bean.getList().get(count));
+	}
+
+	public void testAddListChangeEvent() throws Exception {
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		String element = "1";
+
+		list.add(element);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], true, 0, element);
+	}
+
+	public void testAddFiresPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.add("0");
+			}			
+		});
+	}
+
+	public void testAddAtIndex() throws Exception {
+		String element = "1";
+		assertEquals(0, list.size());
+
+		list.add(0, element);
+		assertEquals(element, bean.getList().get(0));
+	}
+
+	public void testAddAtIndexListChangeEvent() throws Exception {
+		String element = "1";
+		assertEquals(0, list.size());
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		list.add(0, element);
+
+		ListChangeEvent event = listener.event;
+		assertEntry(event.diff.getDifferences()[0], true, 0, element);
 	}
 	
-	public void testRegistersListenerAfterFirstListenerIsAdded() throws Exception {
-		assertFalse(bean.changeSupport.hasListeners(propertyName));
-		observableList.addListChangeListener(new ListChangeListener());
-		assertTrue(bean.changeSupport.hasListeners(propertyName));
+	public void testAddAtIndexPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.add(0, "0");
+			}			
+		});
 	}
-    
-    public void testRemovesListenerAfterLastListenerIsRemoved() throws Exception {
-    	ListChangeListener listener = new ListChangeListener();
-		observableList.addListChangeListener(listener);
+
+	public void testRemove() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(1, bean.getList().size());
+		list.remove(element);
+		assertEquals(0, bean.getList().size());
+	}
+
+	public void testRemoveListChangeEvent() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(1, list.size());
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		list.remove(element);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 0, element);
+	}
+	
+	public void testRemovePropertyChangeEvent() throws Exception {
+		list.add("0");
 		
-		assertTrue(bean.changeSupport.hasListeners(propertyName));
-		observableList.removeListChangeListener(listener);
-		assertFalse(bean.changeSupport.hasListeners(propertyName));
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.remove("0");
+			}			
+		});
 	}
-    
-    public void testFiresListChangeEvents() throws Exception {
-    	ListChangeListener listener = new ListChangeListener();
-    	observableList.addListChangeListener(listener);
-    	
-    	assertEquals(0, listener.count);
-    	bean.setList(Arrays.asList(new String[] {"value"}));
-    	assertEquals(1, listener.count);
+
+	public void testRemoveAtIndex() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(element, bean.getList().get(0));
+
+		list.remove(0);
+		assertEquals(0, bean.getList().size());
 	}
-    
-    static class ListChangeListener implements IListChangeListener {
-    	int count;
-		public void handleListChange(ListChangeEvent event) {
+
+	public void testRemoveAtIndexListChangeEvent() throws Exception {
+		String element = "1";
+		list.add(element);
+
+		assertEquals(1, list.size());
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		list.remove(0);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 0, element);
+	}
+	
+	public void testRemoveAtIndexPropertyChangeEvent() throws Exception {
+		list.add("0");
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.remove(0);
+			}			
+		});
+	}
+
+	public void testAddAll() throws Exception {
+		Collection elements = Arrays.asList(new String[] { "1", "2" });
+		assertEquals(0, list.size());
+
+		list.addAll(elements);
+
+		assertEquals(2, bean.getList().size());
+	}
+
+	public void testAddAllListChangEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		assertEquals(0, list.size());
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+		assertEquals(0, listener.count);
+
+		list.addAll(elements);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+
+		assertEntry(event.diff.getDifferences()[0], true, 0, elements.get(0));
+		assertEntry(event.diff.getDifferences()[1], true, 1, elements.get(1));
+	}
+	
+	public void testAddAllPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.addAll(Arrays.asList(new String[] {"0", "1"}));
+			}			
+		});
+	}
+
+	public void testAddAllAtIndex() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+
+		assertEquals(2, list.size());
+
+		list.addAll(2, elements);
+
+		assertEquals(4, bean.getList().size());
+		assertEquals(elements.get(0), bean.getList().get(0));
+		assertEquals(elements.get(1), bean.getList().get(1));
+	}
+
+	public void testAddAllAtIndexListChangeEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+
+		list.addAll(2, elements);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], true, 2, elements.get(0));
+		assertEntry(event.diff.getDifferences()[1], true, 3, elements.get(1));
+	}
+	
+	public void testAddAllAtIndexPropertyChangeEvent() throws Exception {
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.addAll(0, Arrays.asList(new String[] {"1", "2"}));
+			}			
+		});
+	}
+
+	public void testRemoveAll() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+		list.addAll(elements);
+
+		assertEquals(4, bean.getList().size());
+		list.removeAll(elements);
+
+		assertEquals(2, bean.getList().size());
+		assertEquals(elements.get(0), bean.getList().get(0));
+		assertEquals(elements.get(1), bean.getList().get(1));
+	}
+
+	public void testRemoveAllListChangeEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+		list.addAll(elements);
+		list.addAll(elements);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		list.removeAll(elements);
+
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 0, elements.get(0));
+		assertEntry(event.diff.getDifferences()[1], false, 0, elements.get(1));
+	}
+	
+	public void testRemoveAllPropertyChangeEvent() throws Exception {
+		list.add("0");
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.removeAll(Arrays.asList(new String[] {"0"}));
+			}			
+		});
+	}
+
+	public void testRetailAll() throws Exception {
+		List elements = Arrays.asList(new String[] { "0", "1", "2", "3" });
+		list.addAll(elements);
+
+		assertEquals(4, bean.getList().size());
+
+		list.retainAll(elements.subList(0, 2));
+		assertEquals(2, bean.getList().size());
+
+		assertEquals(elements.get(0), bean.getList().get(0));
+		assertEquals(elements.get(1), bean.getList().get(1));
+	}
+
+	public void testRetainAllListChangeEvent() throws Exception {
+		List elements = Arrays.asList(new String[] { "0", "1", "2", "3" });
+		list.addAll(elements);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		list.retainAll(elements.subList(0, 2));
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], false, 2, elements.get(2));
+		assertEntry(event.diff.getDifferences()[1], false, 2, elements.get(3));
+	}
+	
+	public void testRetainAllPropertyChangeEvent() throws Exception {
+		list.addAll(Arrays.asList(new String[] {"0", "1"}));
+		
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.retainAll(Arrays.asList(new String[] {"0"}));
+			}			
+		});
+	}
+
+	public void testSet() throws Exception {
+		String oldElement = "old";
+		String newElement = "new";
+		list.add(oldElement);
+
+		assertEquals(oldElement, bean.getList().get(0));
+
+		list.set(0, newElement);
+		assertEquals(newElement, bean.getList().get(0));
+	}
+
+	public void testSetListChangeEvent() throws Exception {
+		String oldElement = "old";
+		String newElement = "new";
+		list.add(oldElement);
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+		assertEquals(0, listener.count);
+
+		list.set(0, newElement);
+
+		assertEquals(1, listener.count);
+		ListChangeEvent event = listener.event;
+		assertEquals(list, event.getObservableList());
+		assertEntry(event.diff.getDifferences()[0], true, 0, newElement);
+		assertEntry(event.diff.getDifferences()[1], false, 1, oldElement);
+	}
+
+	public void testSetPropertyChangeEvent() throws Exception {
+		list.add("0");
+		assertPropertyChangeEvent(bean, new Runnable() {
+			public void run() {
+				list.set(0, "1");
+			}			
+		});
+	}
+
+	public void testListChangeEventFiresWhenNewListIsSet() throws Exception {
+		List elements = Arrays.asList(new String[] { "1", "2" });
+
+		ListChangeEventTracker listener = new ListChangeEventTracker();
+		list.addListChangeListener(listener);
+
+		assertEquals(0, listener.count);
+		bean.setList(elements);
+		assertEquals(1, listener.count);
+	}
+
+	private static void assertEntry(ListDiffEntry entry, boolean addition,
+			int position, Object element) {
+		assertEquals("addition", addition, entry.isAddition());
+		assertEquals("position", position, entry.getPosition());
+		assertEquals("element", element, entry.getElement());
+	}
+
+	private static void assertPropertyChangeEvent(NonStandardBean bean, Runnable runnable) {
+		PropertyChangeTracker listener = new PropertyChangeTracker();
+		bean.addPropertyChangeListener(listener);
+		
+		List old = bean.getList();
+		assertEquals(0, listener.count);
+		
+		runnable.run();
+		
+		PropertyChangeEvent event = listener.evt;
+		assertEquals("event did not fire", 1, listener.count);
+		assertEquals("list", event.getPropertyName());
+		assertEquals("old value", old, event.getOldValue());
+		assertEquals("new value", bean.getList(), event.getNewValue());
+		assertFalse("lists are equal", bean.getList().equals(old));
+	}
+	
+	private static class PropertyChangeTracker implements
+			PropertyChangeListener {
+		int count;
+
+		PropertyChangeEvent evt;
+
+		/*
+		 * (non-Javadoc)
+		 * 
+		 * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
+		 */
+		public void propertyChange(PropertyChangeEvent evt) {
 			count++;
+			this.evt = evt;
 		}
-    }
+	}
 }
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/NonStandardBean.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/NonStandardBean.java
new file mode 100644
index 0000000..2ee5b1a
--- /dev/null
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/core/tests/internal/databinding/internal/beans/NonStandardBean.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2007 Brad Reynolds and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     Brad Reynolds - initial API and implementation
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.databinding.internal.beans;
+
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeSupport;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Simple non-standard (java.util.List-based property) Java Bean for testing.
+ * 
+ * @since 3.3
+ */
+public class NonStandardBean {
+	/* package */PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);
+	private String value;
+	private List list = new ArrayList();
+	private Set set;
+
+	public NonStandardBean() {
+	}
+
+	public NonStandardBean(String value) {
+		this.value = value;
+	}
+
+	public NonStandardBean(List list) {
+		this.list = list;
+	}
+
+	public NonStandardBean(Set set) {
+		this.set = set;
+	}
+
+	public void addPropertyChangeListener(PropertyChangeListener listener) {
+		changeSupport.addPropertyChangeListener(listener);
+	}
+
+	public void removePropertyChangeListener(PropertyChangeListener listener) {
+		changeSupport.removePropertyChangeListener(listener);
+	}
+
+	public String getValue() {
+		return value;
+	}
+
+	public void setValue(String value) {
+		changeSupport.firePropertyChange("value", this.value, this.value = value);
+	}
+
+	public List getList() {
+		return list;
+	}
+
+	public void setList(List list) {
+		changeSupport.firePropertyChange("list", this.list, this.list = list);
+	}
+
+	public Set getSet() {
+		return set;
+	}
+
+	public void setSet(Set set) {
+		changeSupport.firePropertyChange("set", this.set, this.set = set);
+	}
+}
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java
index c8c6ebe..a1987d6 100644
--- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java
@@ -77,6 +77,7 @@
 import org.eclipse.core.tests.internal.databinding.internal.beans.BeanObservableListDecoratorTest;
 import org.eclipse.core.tests.internal.databinding.internal.beans.BeanObservableSetDecoratorTest;
 import org.eclipse.core.tests.internal.databinding.internal.beans.BeanObservableValueDecoratorTest;
+import org.eclipse.core.tests.internal.databinding.internal.beans.JavaBeanObservableArrayBasedListTest;
 import org.eclipse.core.tests.internal.databinding.internal.beans.JavaBeanObservableListTest;
 import org.eclipse.core.tests.internal.databinding.internal.beans.JavaBeanObservableMapTest;
 import org.eclipse.core.tests.internal.databinding.internal.beans.JavaBeanObservableSetTest;
@@ -212,6 +213,7 @@
 		addTestSuite(BeanObservableSetDecoratorTest.class);
 		addTestSuite(BeanObservableValueDecoratorTest.class);
 		addTestSuite(BeanObservableListDecoratorTest.class);
+		addTestSuite(JavaBeanObservableArrayBasedListTest.class);
 		addTestSuite(JavaBeanObservableListTest.class);
 		addTestSuite(JavaBeanObservableMapTest.class);
 		addTestSuite(JavaBeanObservableSetTest.class);
diff --git a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/EventTrackers.java b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/EventTrackers.java
index 4208316..bf1d7a6 100644
--- a/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/EventTrackers.java
+++ b/tests/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/EventTrackers.java
@@ -13,6 +13,8 @@
 
 import org.eclipse.core.databinding.observable.ChangeEvent;
 import org.eclipse.core.databinding.observable.IChangeListener;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
 import org.eclipse.core.databinding.observable.map.IMapChangeListener;
 import org.eclipse.core.databinding.observable.map.MapChangeEvent;
 import org.eclipse.core.databinding.observable.value.IValueChangeListener;
@@ -54,4 +56,14 @@
 			this.event = event;
 		}		
 	}
+	
+	public static class ListChangeEventTracker implements IListChangeListener {
+		public int count;
+		public ListChangeEvent event;
+		
+		public void handleListChange(ListChangeEvent event) {
+			count++;
+			this.event = event;
+		}	
+	}
 }