Simplify AbstractPreferences.
There's no need for a complicated re-implementation of copy
on write lists. Simple ArrayLists will suffice; all operations
are guarded by |lock| and we can return a copy of the list to
the Event processor thread to avoid concurrent modification of
the list while that thread is dispatching events.
Fixes various preference tests related to adding / removing
duplicate listeners.
A notable difference in behaviour here is that listeners are
now compared using their equals() method and not using reference
equality.
bug: 26023763
bug: 25996530
Change-Id: I57ee723cf926786e1f119ec1e7740c66162ab749
diff --git a/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java b/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
index f4a7e5e..0abb8b0 100755
--- a/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
+++ b/ojluni/src/main/java/java/util/prefs/AbstractPreferences.java
@@ -166,13 +166,12 @@
/**
* Registered preference change listeners.
*/
- private PreferenceChangeListener[] prefListeners =
- new PreferenceChangeListener[0];
+ private final ArrayList<PreferenceChangeListener> prefListeners = new ArrayList<>();
/**
* Registered node change listeners.
*/
- private NodeChangeListener[] nodeListeners = new NodeChangeListener[0];
+ private final ArrayList<NodeChangeListener> nodeListeners = new ArrayList<>();
/**
* An object whose monitor is used to lock this node. This object
@@ -1034,11 +1033,7 @@
if (removed)
throw new IllegalStateException("Node has been removed.");
- // Copy-on-write
- PreferenceChangeListener[] old = prefListeners;
- prefListeners = new PreferenceChangeListener[old.length + 1];
- System.arraycopy(old, 0, prefListeners, 0, old.length);
- prefListeners[old.length] = pcl;
+ prefListeners.add(pcl);
}
startEventDispatchThreadIfNecessary();
}
@@ -1047,21 +1042,12 @@
synchronized(lock) {
if (removed)
throw new IllegalStateException("Node has been removed.");
- if ((prefListeners == null) || (prefListeners.length == 0))
- throw new IllegalArgumentException("Listener not registered.");
- // Copy-on-write
- PreferenceChangeListener[] newPl =
- new PreferenceChangeListener[prefListeners.length - 1];
- int i = 0;
- while (i < newPl.length && prefListeners[i] != pcl)
- newPl[i] = prefListeners[i++];
-
- if (i == newPl.length && prefListeners[i] != pcl)
+ if (!prefListeners.contains(pcl)) {
throw new IllegalArgumentException("Listener not registered.");
- while (i < newPl.length)
- newPl[i] = prefListeners[++i];
- prefListeners = newPl;
+ }
+
+ prefListeners.remove(pcl);
}
}
@@ -1072,16 +1058,7 @@
if (removed)
throw new IllegalStateException("Node has been removed.");
- // Copy-on-write
- if (nodeListeners == null) {
- nodeListeners = new NodeChangeListener[1];
- nodeListeners[0] = ncl;
- } else {
- NodeChangeListener[] old = nodeListeners;
- nodeListeners = new NodeChangeListener[old.length + 1];
- System.arraycopy(old, 0, nodeListeners, 0, old.length);
- nodeListeners[old.length] = ncl;
- }
+ nodeListeners.add(ncl);
}
startEventDispatchThreadIfNecessary();
}
@@ -1090,23 +1067,12 @@
synchronized(lock) {
if (removed)
throw new IllegalStateException("Node has been removed.");
- if ((nodeListeners == null) || (nodeListeners.length == 0))
- throw new IllegalArgumentException("Listener not registered.");
- // Copy-on-write
- int i = 0;
- while (i < nodeListeners.length && nodeListeners[i] != ncl)
- i++;
- if (i == nodeListeners.length)
+ if (!nodeListeners.contains(ncl)) {
throw new IllegalArgumentException("Listener not registered.");
- NodeChangeListener[] newNl =
- new NodeChangeListener[nodeListeners.length - 1];
- if (i != 0)
- System.arraycopy(nodeListeners, 0, newNl, 0, i);
- if (i != newNl.length)
- System.arraycopy(nodeListeners, i + 1,
- newNl, i, newNl.length - i);
- nodeListeners = newNl;
+ }
+
+ nodeListeners.remove(ncl);
}
}
@@ -1521,19 +1487,19 @@
}
/**
- * Return this node's preference/node change listeners. Even though
- * we're using a copy-on-write lists, we use synchronized accessors to
- * ensure information transmission from the writing thread to the
- * reading thread.
+ * Return this node's preference/node change listeners. All accesses to prefListeners
+ * and nodeListeners are guarded by |lock|. We return a copy of the list so that the
+ * EventQueue thread will iterate over a fixed snapshot of the listeners at the time of
+ * this call.
*/
PreferenceChangeListener[] prefListeners() {
synchronized(lock) {
- return prefListeners;
+ return prefListeners.toArray(new PreferenceChangeListener[prefListeners.size()]);
}
}
NodeChangeListener[] nodeListeners() {
synchronized(lock) {
- return nodeListeners;
+ return nodeListeners.toArray(new NodeChangeListener[nodeListeners.size()]);
}
}
@@ -1543,7 +1509,7 @@
* listeners. Invoked with this.lock held.
*/
private void enqueuePreferenceChangeEvent(String key, String newValue) {
- if (prefListeners.length != 0) {
+ if (!prefListeners.isEmpty()) {
synchronized(eventQueue) {
eventQueue.add(new PreferenceChangeEvent(this, key, newValue));
eventQueue.notify();
@@ -1557,7 +1523,7 @@
* this.lock held.
*/
private void enqueueNodeAddedEvent(Preferences child) {
- if (nodeListeners.length != 0) {
+ if (!nodeListeners.isEmpty()) {
synchronized(eventQueue) {
eventQueue.add(new NodeAddedEvent(this, child));
eventQueue.notify();
@@ -1571,7 +1537,7 @@
* this.lock held.
*/
private void enqueueNodeRemovedEvent(Preferences child) {
- if (nodeListeners.length != 0) {
+ if (!nodeListeners.isEmpty()) {
synchronized(eventQueue) {
eventQueue.add(new NodeRemovedEvent(this, child));
eventQueue.notify();