JDK-8135017 : ArrayIndexOutOfBoundsException with SortedList
  • Type: Bug
  • Component: javafx
  • Sub-Component: base
  • Affected Version: 8u60
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: windows_7
  • CPU: x86_64
  • Submitted: 2015-08-31
  • Updated: 2015-09-08
  • Resolved: 2015-09-08
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :
java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)

ADDITIONAL OS VERSION INFORMATION :
Microsoft Windows [Version 6.1.7601]

A DESCRIPTION OF THE PROBLEM :
SortedList causes uncaught ArrayIndexOutOfBoundsException when resorting.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the code provided

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Expect it not to throw uncatchable exception
ACTUAL -
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -6
	at javafx.collections.transformation.SortedList.findPosition(SortedList.java:318)
	at javafx.collections.transformation.SortedList.removeFromMapping(SortedList.java:359)
	at javafx.collections.transformation.SortedList.addRemove(SortedList.java:389)
	at javafx.collections.transformation.SortedList.sourceChanged(SortedList.java:105)
	at javafx.collections.transformation.TransformationList.lambda$getListener$15(TransformationList.java:106)
	at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
	at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
	at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
	at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
	at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
	at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
	at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
	at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:163)
	at test.collections.TestListUpdate.lambda$3(TestListUpdate.java:119)
	at com.sun.javafx.collections.MapListenerHelper$SingleChange.fireValueChangedEvent(MapListenerHelper.java:163)
	at com.sun.javafx.collections.MapListenerHelper.fireValueChangedEvent(MapListenerHelper.java:72)
	at com.sun.javafx.collections.ObservableMapWrapper.callObservers(ObservableMapWrapper.java:115)
	at com.sun.javafx.collections.ObservableMapWrapper.put(ObservableMapWrapper.java:173)
	at test.collections.TestListUpdate$Person.setAge(TestListUpdate.java:22)
	at test.collections.TestListUpdate.main(TestListUpdate.java:136)

ERROR MESSAGES/STACK TRACES THAT OCCUR :
added 0x1e80bfe8 [bob, 0yo]
added 0x50040f0c [john, 0yo]
return -8; [bob, 0yo] < [john, 0yo]
added 0x2dda6444 [joe, 0yo]
return -8; [bob, 0yo] < [joe, 0yo]
return 3; [john, 0yo] > [joe, 0yo]
added 0x5e9f23b4 [ed, 0yo]
return 5; [joe, 0yo] > [ed, 0yo]
return -3; [bob, 0yo] < [ed, 0yo]
added 0x4783da3f [fred, 0yo]
return -1; [ed, 0yo] < [fred, 0yo]
return 4; [joe, 0yo] > [fred, 0yo]
--------------------------------------------------------
get person @ index 3
set age of 'ed' @ index 3 to 73
'ed' @ 3; 'age':[73] added
reset 'ed' @ 3
replaced 0x5e9f23b4 [ed, 73yo] with 0x5e9f23b4 [ed, 73yo]
return -73; [fred, 0yo] < [ed, 73yo]
return -73; [joe, 0yo] < [ed, 73yo]
return -73; [john, 0yo] < [ed, 73yo]
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -6
	at javafx.collections.transformation.SortedList.findPosition(SortedList.java:318)
	at javafx.collections.transformation.SortedList.removeFromMapping(SortedList.java:359)
	at javafx.collections.transformation.SortedList.addRemove(SortedList.java:389)
	at javafx.collections.transformation.SortedList.sourceChanged(SortedList.java:105)
	at javafx.collections.transformation.TransformationList.lambda$getListener$15(TransformationList.java:106)
	at javafx.collections.WeakListChangeListener.onChanged(WeakListChangeListener.java:88)
	at com.sun.javafx.collections.ListListenerHelper$Generic.fireValueChangedEvent(ListListenerHelper.java:329)
	at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
	at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
	at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
	at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
	at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
	at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:163)
	at test.collections.TestListUpdate.lambda$3(TestListUpdate.java:119)
	at com.sun.javafx.collections.MapListenerHelper$SingleChange.fireValueChangedEvent(MapListenerHelper.java:163)
	at com.sun.javafx.collections.MapListenerHelper.fireValueChangedEvent(MapListenerHelper.java:72)
	at com.sun.javafx.collections.ObservableMapWrapper.callObservers(ObservableMapWrapper.java:115)
	at com.sun.javafx.collections.ObservableMapWrapper.put(ObservableMapWrapper.java:173)
	at test.collections.TestListUpdate$Person.setAge(TestListUpdate.java:22)
	at test.collections.TestListUpdate.main(TestListUpdate.java:136)

REPRODUCIBILITY :
This bug can be reproduced often.

---------- BEGIN SOURCE ----------
package test.collections;

import java.util.Arrays;

import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.MapChangeListener;
import javafx.collections.ObservableList;
import javafx.collections.ObservableMap;

public class TestListUpdate {
	static class Person {
		ObservableMap<String, Object> data = FXCollections.observableHashMap();
		
		public Person(String name) {
			setName(name);
		}
		
		void setName(String name) { data.put("name", name); }
		String getName() { return (String) data.get("name"); }

		void setAge(int age) { data.put("age", age); }
		int getAge() { return (int) data.getOrDefault("age", 0); }
		
		@Override public String toString() { return String.format("%s, %d", getName(), getAge()); }
	}
	
	public static void main(String[] args) {
		// create observable list of people
		ObservableList<Person> people = FXCollections.observableArrayList();

		// add listener to track changes to list (does not contribute to problem)
		people.addListener(
			(ListChangeListener<? super Object>) c -> {
				while (c.next()) {
					if (c.wasReplaced()) {
						System.out.printf(
							"replaced 0x%08x %s with 0x%08x %s\n",
							c.getRemoved().get(0).hashCode(),
							c.getRemoved(), 
							c.getAddedSubList().get(0).hashCode(),
							c.getAddedSubList());
					} else if (c.wasAdded()) {
						System.out.printf(
							"added 0x%08x %s\n", 
							c.getAddedSubList().get(0).hashCode(), 
							c.getAddedSubList());
					} else if (c.wasRemoved()) {
						System.out.printf(
							"removed 0x%08x %s\n", 
							c.getRemoved().hashCode(),
							c.getRemoved());
					}
				}
			});
		
		// create sorted list of people by age/name
		people.sorted(
			(p1, p2) -> {
				int equality = 0;
				
				if (p1.getAge() != p2.getAge()) {
					equality = p1.getAge() - p2.getAge();
				} else {
					equality = p1.getName().compareToIgnoreCase(p2.getName());
				}
			
				System.out.printf(
					"return %d; [%s] %s [%s]\n", 
					equality,
					p1,
					equality < 0 ? "<" : equality > 0 ? ">" : "==",
					p2);
			
				return equality;
			});
		
		// add people to list
		Arrays.asList("bob", "john", "joe", "ed", "fred")
			.forEach(
				n -> {
					Person p = new Person(n);

					// add the person to the list
					people.add(p);
			
					// listen for change to the person
					p.data.addListener(
						(MapChangeListener<? super String, ? super Object>) c -> {
							// get the index of the person in the list
							int index = people.indexOf(p);
							
							if (c.wasRemoved() && c.wasAdded()) {
								System.out.printf(
									String.format(
										"'%s' @ %d; '%s' changed from [%s] to [%s]\n",
										p.getName(),
										index,
										c.getKey(),
										c.getValueRemoved(),
										c.getValueAdded()));
							} else {
								System.out.printf(
									String.format(
										"'%s' @ %d; '%s':[%s] %s\n",
										p.getName(),
										index,
										c.getKey(),
										c.wasAdded() ? c.getValueAdded() : c.getValueRemoved(),
										c.wasAdded() ? "added" : "removed"));
							}
													
							System.out.printf("reset '%s' @ %d\n", p.getName(), index);
							
							// **********************************************************************
							// SOMETHING ABOUT THE PERSON CHANGED, SO RESET THE PERSON IN THE
							// LIST TO TRIGGER AN UPDATE TO THE PERSON LIST
							// **********************************************************************
							people.set(index, p);
						});
				});

		// randomly chose a person in the list and change their age to cause an update to the
		// list of people
		while (true) {
			int index = (int) ((people.size() - 1) * Math.random());
					
			System.out.println("--------------------------------------------------------");
			System.out.printf("get person @ index %d\n", index);
			
			Person p = people.get(index);
			int age = (int) (Math.random() * 100); 
			
			System.out.printf("set age of '%s' @ index %d to %d\n", p.getName(), index, age);
			
			p.setAge(age);
			
			// **********************************************************************
			// an exception in SortList will randomly occur in this loop
			// **********************************************************************
		}
	}
}

---------- END SOURCE ----------


Comments
Basically this is a duplicate of JDK-8087508. One should probably use the extractor version of the FXCollection.observableArrayList method to use update mechanism instead of replace. So using this call people = observableArrayList(p -> new Observable[]{p.data}); and removing people.set(index, p); from the p.data listener solves the issue.
08-09-2015