| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.util.prefs; |
| |
| import java.io.IOException; |
| import java.io.OutputStream; |
| import java.nio.charset.Charsets; |
| import java.util.Collection; |
| import java.util.EventListener; |
| import java.util.EventObject; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| import org.apache.harmony.luni.util.Base64; |
| |
| /** |
| * This abstract class is a partial implementation of the abstract class |
| * Preferences, which can be used to simplify {@code Preferences} provider's |
| * implementation. This class defines nine abstract SPI methods, which must be |
| * implemented by a preference provider. |
| * |
| * @since 1.4 |
| * @see Preferences |
| */ |
| public abstract class AbstractPreferences extends Preferences { |
| /* |
| * ----------------------------------------------------------- |
| * Class fields |
| * ----------------------------------------------------------- |
| */ |
| /** the unhandled events collection */ |
| private static final List<EventObject> events = new LinkedList<EventObject>(); |
| /** the event dispatcher thread */ |
| private static final EventDispatcher dispatcher = new EventDispatcher("Preference Event Dispatcher"); |
| |
| /* |
| * ----------------------------------------------------------- |
| * Class initializer |
| * ----------------------------------------------------------- |
| */ |
| static { |
| dispatcher.setDaemon(true); |
| dispatcher.start(); |
| Runtime.getRuntime().addShutdownHook(new Thread() { |
| @Override |
| public void run() { |
| Preferences uroot = Preferences.userRoot(); |
| Preferences sroot = Preferences.systemRoot(); |
| try { |
| uroot.flush(); |
| } catch (BackingStoreException e) { |
| // ignore |
| } |
| try { |
| sroot.flush(); |
| } catch (BackingStoreException e) { |
| // ignore |
| } |
| } |
| }); |
| } |
| |
| /* |
| * ----------------------------------------------------------- |
| * Instance fields (package-private) |
| * ----------------------------------------------------------- |
| */ |
| /** true if this node is in user preference hierarchy */ |
| boolean userNode; |
| |
| /* |
| * ----------------------------------------------------------- |
| * Instance fields (private) |
| * ----------------------------------------------------------- |
| */ |
| /** Marker class for 'lock' field. */ |
| private static class Lock {} |
| |
| /** |
| * The object used to lock this node. |
| */ |
| protected final Object lock; |
| |
| /** |
| * This field is true if this node is created while it doesn't exist in the |
| * backing store. This field's default value is false, and it is checked |
| * when the node creation is completed, and if it is true, the node change |
| * event will be fired for this node's parent. |
| */ |
| protected boolean newNode; |
| |
| /** cached child nodes */ |
| private Map<String, AbstractPreferences> cachedNode; |
| |
| //the collections of listeners |
| private List<EventListener> nodeChangeListeners; |
| private List<EventListener> preferenceChangeListeners; |
| |
| //this node's name |
| private String nodeName; |
| |
| //handler to this node's parent |
| private AbstractPreferences parentPref; |
| |
| //true if this node has been removed |
| private boolean isRemoved; |
| |
| //handler to this node's root node |
| private AbstractPreferences root; |
| |
| /* |
| * ----------------------------------------------------------- |
| * Constructors |
| * ----------------------------------------------------------- |
| */ |
| /** |
| * Constructs a new {@code AbstractPreferences} instance using the given |
| * parent node and node name. |
| * |
| * @param parent |
| * the parent node of the new node or {@code null} to indicate |
| * that the new node is a root node. |
| * @param name |
| * the name of the new node or an empty string to indicate that |
| * this node is called "root". |
| * @throws IllegalArgumentException |
| * if the name contains a slash character or is empty if {@code |
| * parent} is not {@code null}. |
| */ |
| protected AbstractPreferences(AbstractPreferences parent, String name) { |
| if ((null == parent ^ name.length() == 0) || name.indexOf("/") >= 0) { |
| throw new IllegalArgumentException(); |
| } |
| root = null == parent ? this : parent.root; |
| nodeChangeListeners = new LinkedList<EventListener>(); |
| preferenceChangeListeners = new LinkedList<EventListener>(); |
| isRemoved = false; |
| cachedNode = new HashMap<String, AbstractPreferences>(); |
| nodeName = name; |
| parentPref = parent; |
| lock = new Lock(); |
| userNode = root.userNode; |
| } |
| |
| /* |
| * ----------------------------------------------------------- |
| * Methods |
| * ----------------------------------------------------------- |
| */ |
| /** |
| * Returns an array of all cached child nodes. |
| * |
| * @return the array of cached child nodes. |
| */ |
| protected final AbstractPreferences[] cachedChildren() { |
| return cachedNode.values().toArray(new AbstractPreferences[cachedNode.size()]); |
| } |
| |
| /** |
| * Returns the child node with the specified name or {@code null} if it |
| * doesn't exist. Implementers can assume that the name supplied to this |
| * method will be a valid node name string (conforming to the node naming |
| * format) and will not correspond to a node that has been cached or |
| * removed. |
| * |
| * @param name |
| * the name of the desired child node. |
| * @return the child node with the given name or {@code null} if it doesn't |
| * exist. |
| * @throws BackingStoreException |
| * if the backing store is unavailable or causes an operation |
| * failure. |
| */ |
| protected AbstractPreferences getChild(String name) |
| throws BackingStoreException { |
| synchronized (lock) { |
| checkState(); |
| AbstractPreferences result = null; |
| String[] childrenNames = childrenNames(); |
| for (int i = 0; i < childrenNames.length; i++) { |
| if (childrenNames[i].equals(name)) { |
| result = childSpi(name); |
| break; |
| } |
| } |
| return result; |
| } |
| |
| } |
| |
| /** |
| * Returns whether this node has been removed by invoking the method {@code |
| * removeNode()}. |
| * |
| * @return {@code true}, if this node has been removed, {@code false} |
| * otherwise. |
| */ |
| protected boolean isRemoved() { |
| synchronized (lock) { |
| return isRemoved; |
| } |
| } |
| |
| /** |
| * Flushes changes of this node to the backing store. This method should |
| * only flush this node and should not include the descendant nodes. Any |
| * implementation that wants to provide functionality to flush all nodes |
| * at once should override the method {@link #flush() flush()}. |
| * |
| * @throws BackingStoreException |
| * if the backing store is unavailable or causes an operation |
| * failure. |
| */ |
| protected abstract void flushSpi() throws BackingStoreException; |
| |
| /** |
| * Returns the names of all of the child nodes of this node or an empty |
| * array if this node has no children. The names of cached children are not |
| * required to be returned. |
| * |
| * @return the names of this node's children. |
| * @throws BackingStoreException |
| * if the backing store is unavailable or causes an operation |
| * failure. |
| */ |
| protected abstract String[] childrenNamesSpi() throws BackingStoreException; |
| |
| /** |
| * Returns the child preference node with the given name, creating it |
| * if it does not exist. The caller of this method should ensure that the |
| * given name is valid and that this node has not been removed or cached. |
| * If the named node has just been removed, the implementation |
| * of this method must create a new one instead of reactivating the removed |
| * one. |
| * <p> |
| * The new creation is not required to be persisted immediately until the |
| * flush method will be invoked. |
| * </p> |
| * |
| * @param name |
| * the name of the child preference to be returned. |
| * @return the child preference node. |
| */ |
| protected abstract AbstractPreferences childSpi(String name); |
| |
| |
| /** |
| * Puts the given key-value pair into this node. Caller of this method |
| * should ensure that both of the given values are valid and that this |
| * node has not been removed. |
| * |
| * @param name |
| * the given preference key. |
| * @param value |
| * the given preference value. |
| */ |
| protected abstract void putSpi(String name, String value); |
| |
| /** |
| * Gets the preference value mapped to the given key. The caller of this |
| * method should ensure that the given key is valid and that this node has |
| * not been removed. This method should not throw any exceptions but if it |
| * does, the caller will ignore the exception, regarding it as a {@code |
| * null} return value. |
| * |
| * @param key |
| * the given key to be searched for. |
| * @return the preference value mapped to the given key. |
| */ |
| protected abstract String getSpi(String key); |
| |
| |
| /** |
| * Returns an array of all preference keys of this node or an empty array if |
| * no preferences have been found. The caller of this method should ensure |
| * that this node has not been removed. |
| * |
| * @return the array of all preference keys. |
| * @throws BackingStoreException |
| * if the backing store is unavailable or causes an operation |
| * failure. |
| */ |
| protected abstract String[] keysSpi() throws BackingStoreException; |
| |
| /** |
| * Removes this node from the preference hierarchy tree. The caller of this |
| * method should ensure that this node has no child nodes, which means the |
| * method {@link Preferences#removeNode() Preferences.removeNode()} should |
| * invoke this method multiple-times in bottom-up pattern. The removal is |
| * not required to be persisted until after it is flushed. |
| * |
| * @throws BackingStoreException |
| * if the backing store is unavailable or causes an operation |
| * failure. |
| */ |
| protected abstract void removeNodeSpi() throws BackingStoreException; |
| |
| /** |
| * Removes the preference with the specified key. The caller of this method |
| * should ensure that the given key is valid and that this node has not been |
| * removed. |
| * |
| * @param key |
| * the key of the preference that is to be removed. |
| */ |
| protected abstract void removeSpi(String key); |
| |
| /** |
| * Synchronizes this node with the backing store. This method should only |
| * synchronize this node and should not include the descendant nodes. An |
| * implementation that wants to provide functionality to synchronize all |
| * nodes at once should override the method {@link #sync() sync()}. |
| * |
| * @throws BackingStoreException |
| * if the backing store is unavailable or causes an operation |
| * failure. |
| */ |
| protected abstract void syncSpi() throws BackingStoreException; |
| |
| /* |
| * ----------------------------------------------------------- |
| * Methods inherited from Preferences |
| * ----------------------------------------------------------- |
| */ |
| @Override |
| public String absolutePath() { |
| if (parentPref == null) { |
| return "/"; |
| } else if (parentPref == root) { |
| return "/" + nodeName; |
| } |
| return parentPref.absolutePath() + "/" + nodeName; |
| } |
| |
| @Override |
| public String[] childrenNames() throws BackingStoreException { |
| synchronized (lock) { |
| checkState(); |
| TreeSet<String> result = new TreeSet<String>(cachedNode.keySet()); |
| String[] names = childrenNamesSpi(); |
| for (int i = 0; i < names.length; i++) { |
| result.add(names[i]); |
| } |
| return result.toArray(new String[result.size()]); |
| } |
| } |
| |
| @Override |
| public void clear() throws BackingStoreException { |
| synchronized (lock) { |
| String[] keyList = keys(); |
| for (int i = 0; i < keyList.length; i++) { |
| remove(keyList[i]); |
| } |
| } |
| } |
| |
| @Override |
| public void exportNode(OutputStream ostream) throws IOException, BackingStoreException { |
| if (ostream == null) { |
| throw new NullPointerException("Stream is null"); |
| } |
| checkState(); |
| XMLParser.exportPrefs(this, ostream, false); |
| } |
| |
| @Override |
| public void exportSubtree(OutputStream ostream) throws IOException, BackingStoreException { |
| if (ostream == null) { |
| throw new NullPointerException("Stream is null"); |
| } |
| checkState(); |
| XMLParser.exportPrefs(this, ostream, true); |
| } |
| |
| @Override |
| public void flush() throws BackingStoreException { |
| synchronized (lock) { |
| flushSpi(); |
| } |
| AbstractPreferences[] cc = cachedChildren(); |
| int i; |
| for (i = 0; i < cc.length; i++) { |
| cc[i].flush(); |
| } |
| } |
| |
| @Override |
| public String get(String key, String deflt) { |
| if (key == null) { |
| throw new NullPointerException(); |
| } |
| String result = null; |
| synchronized (lock) { |
| checkState(); |
| try { |
| result = getSpi(key); |
| } catch (Exception e) { |
| // ignored |
| } |
| } |
| return (result == null ? deflt : result); |
| } |
| |
| @Override |
| public boolean getBoolean(String key, boolean deflt) { |
| String result = get(key, null); |
| if (result == null) { |
| return deflt; |
| } |
| if ("true".equalsIgnoreCase(result)) { |
| return true; |
| } else if ("false".equalsIgnoreCase(result)) { |
| return false; |
| } else { |
| return deflt; |
| } |
| } |
| |
| @Override |
| public byte[] getByteArray(String key, byte[] deflt) { |
| String svalue = get(key, null); |
| if (svalue == null) { |
| return deflt; |
| } |
| if (svalue.length() == 0) { |
| return new byte[0]; |
| } |
| try { |
| byte[] bavalue = svalue.getBytes(Charsets.US_ASCII); |
| if (bavalue.length % 4 != 0) { |
| return deflt; |
| } |
| return Base64.decode(bavalue); |
| } catch (Exception e) { |
| return deflt; |
| } |
| } |
| |
| @Override |
| public double getDouble(String key, double deflt) { |
| String result = get(key, null); |
| if (result == null) { |
| return deflt; |
| } |
| try { |
| return Double.parseDouble(result); |
| } catch (NumberFormatException e) { |
| return deflt; |
| } |
| } |
| |
| @Override |
| public float getFloat(String key, float deflt) { |
| String result = get(key, null); |
| if (result == null) { |
| return deflt; |
| } |
| try { |
| return Float.parseFloat(result); |
| } catch (NumberFormatException e) { |
| return deflt; |
| } |
| } |
| |
| @Override |
| public int getInt(String key, int deflt) { |
| String result = get(key, null); |
| if (result == null) { |
| return deflt; |
| } |
| try { |
| return Integer.parseInt(result); |
| } catch (NumberFormatException e) { |
| return deflt; |
| } |
| } |
| |
| @Override |
| public long getLong(String key, long deflt) { |
| String result = get(key, null); |
| if (result == null) { |
| return deflt; |
| } |
| try { |
| return Long.parseLong(result); |
| } catch (NumberFormatException e) { |
| return deflt; |
| } |
| } |
| |
| @Override |
| public boolean isUserNode() { |
| return root == Preferences.userRoot(); |
| } |
| |
| @Override |
| public String[] keys() throws BackingStoreException { |
| synchronized (lock) { |
| checkState(); |
| return keysSpi(); |
| } |
| } |
| |
| @Override |
| public String name() { |
| return nodeName; |
| } |
| |
| @Override |
| public Preferences node(String name) { |
| AbstractPreferences startNode = null; |
| synchronized (lock) { |
| checkState(); |
| validateName(name); |
| if (name.isEmpty()) { |
| return this; |
| } else if ("/".equals(name)) { |
| return root; |
| } |
| if (name.startsWith("/")) { |
| startNode = root; |
| name = name.substring(1); |
| } else { |
| startNode = this; |
| } |
| } |
| try { |
| return startNode.nodeImpl(name, true); |
| } catch (BackingStoreException e) { |
| // should not happen |
| return null; |
| } |
| } |
| |
| private void validateName(String name) { |
| if (name.endsWith("/") && name.length() > 1) { |
| throw new IllegalArgumentException("Name cannot end with '/'"); |
| } |
| if (name.indexOf("//") >= 0) { |
| throw new IllegalArgumentException("Name cannot contain consecutive '/' characters"); |
| } |
| } |
| |
| private AbstractPreferences nodeImpl(String path, boolean createNew) |
| throws BackingStoreException { |
| String[] names = path.split("/"); |
| AbstractPreferences currentNode = this; |
| AbstractPreferences temp = null; |
| if (null != currentNode) { |
| for (int i = 0; i < names.length; i++) { |
| String name = names[i]; |
| synchronized (currentNode.lock) { |
| temp = currentNode.cachedNode.get(name); |
| if (temp == null) { |
| temp = getNodeFromBackend(createNew, currentNode, name); |
| } |
| } |
| currentNode = temp; |
| if (null == currentNode) { |
| break; |
| } |
| } |
| } |
| return currentNode; |
| } |
| |
| private AbstractPreferences getNodeFromBackend(boolean createNew, |
| AbstractPreferences currentNode, String name) throws BackingStoreException { |
| if (name.length() > MAX_NAME_LENGTH) { |
| throw new IllegalArgumentException("Name '" + name + "' too long"); |
| } |
| AbstractPreferences temp; |
| if (createNew) { |
| temp = currentNode.childSpi(name); |
| currentNode.cachedNode.put(name, temp); |
| if (temp.newNode && currentNode.nodeChangeListeners.size() > 0) { |
| currentNode.notifyChildAdded(temp); |
| } |
| } else { |
| temp = currentNode.getChild(name); |
| } |
| return temp; |
| } |
| |
| @Override |
| public boolean nodeExists(String name) throws BackingStoreException { |
| if (null == name) { |
| throw new NullPointerException(); |
| } |
| AbstractPreferences startNode = null; |
| synchronized (lock) { |
| if (isRemoved()) { |
| if (name.isEmpty()) { |
| return false; |
| } |
| throw new IllegalStateException("This node has been removed"); |
| } |
| validateName(name); |
| if (name.isEmpty() || "/".equals(name)) { |
| return true; |
| } |
| if (name.startsWith("/")) { |
| startNode = root; |
| name = name.substring(1); |
| } else { |
| startNode = this; |
| } |
| } |
| try { |
| Preferences result = startNode.nodeImpl(name, false); |
| return null == result ? false : true; |
| } catch(IllegalArgumentException e) { |
| return false; |
| } |
| } |
| |
| @Override |
| public Preferences parent() { |
| checkState(); |
| return parentPref; |
| } |
| |
| private void checkState() { |
| if (isRemoved()) { |
| throw new IllegalStateException("This node has been removed"); |
| } |
| } |
| |
| @Override |
| public void put(String key, String value) { |
| if (null == key || null == value) { |
| throw new NullPointerException(); |
| } |
| if (key.length() > MAX_KEY_LENGTH || value.length() > MAX_VALUE_LENGTH) { |
| throw new IllegalArgumentException(); |
| } |
| synchronized (lock) { |
| checkState(); |
| putSpi(key, value); |
| } |
| notifyPreferenceChange(key, value); |
| } |
| |
| @Override |
| public void putBoolean(String key, boolean value) { |
| String sval = String.valueOf(value); |
| put(key, sval); |
| } |
| |
| @Override |
| public void putByteArray(String key, byte[] value) { |
| put(key, Base64.encode(value, Charsets.US_ASCII)); |
| } |
| |
| @Override |
| public void putDouble(String key, double value) { |
| String sval = Double.toString(value); |
| put(key, sval); |
| } |
| |
| @Override |
| public void putFloat(String key, float value) { |
| String sval = Float.toString(value); |
| put(key, sval); |
| } |
| |
| @Override |
| public void putInt(String key, int value) { |
| String sval = Integer.toString(value); |
| put(key, sval); |
| } |
| |
| @Override |
| public void putLong(String key, long value) { |
| String sval = Long.toString(value); |
| put(key, sval); |
| } |
| |
| @Override |
| public void remove(String key) { |
| synchronized (lock) { |
| checkState(); |
| removeSpi(key); |
| } |
| notifyPreferenceChange(key, null); |
| } |
| |
| @Override |
| public void removeNode() throws BackingStoreException { |
| if (root == this) { |
| throw new UnsupportedOperationException("Cannot remove root node"); |
| } |
| synchronized (parentPref.lock) { |
| removeNodeImpl(); |
| } |
| } |
| |
| private void removeNodeImpl() throws BackingStoreException { |
| synchronized (lock) { |
| checkState(); |
| String[] childrenNames = childrenNamesSpi(); |
| for (int i = 0; i < childrenNames.length; i++) { |
| if (null == cachedNode.get(childrenNames[i])) { |
| AbstractPreferences child = childSpi(childrenNames[i]); |
| cachedNode.put(childrenNames[i], child); |
| } |
| } |
| |
| final Collection<AbstractPreferences> values = cachedNode.values(); |
| final AbstractPreferences[] children = values.toArray(new AbstractPreferences[values.size()]); |
| for (AbstractPreferences child : children) { |
| child.removeNodeImpl(); |
| } |
| removeNodeSpi(); |
| isRemoved = true; |
| parentPref.cachedNode.remove(nodeName); |
| } |
| if (parentPref.nodeChangeListeners.size() > 0) { |
| parentPref.notifyChildRemoved(this); |
| } |
| } |
| |
| @Override |
| public void addNodeChangeListener(NodeChangeListener ncl) { |
| if (null == ncl) { |
| throw new NullPointerException(); |
| } |
| checkState(); |
| synchronized (nodeChangeListeners) { |
| nodeChangeListeners.add(ncl); |
| } |
| } |
| |
| @Override |
| public void addPreferenceChangeListener(PreferenceChangeListener pcl) { |
| if (null == pcl) { |
| throw new NullPointerException(); |
| } |
| checkState(); |
| synchronized (preferenceChangeListeners) { |
| preferenceChangeListeners.add(pcl); |
| } |
| } |
| |
| @Override |
| public void removeNodeChangeListener(NodeChangeListener ncl) { |
| checkState(); |
| synchronized (nodeChangeListeners) { |
| int pos; |
| if ((pos = nodeChangeListeners.indexOf(ncl)) == -1) { |
| throw new IllegalArgumentException(); |
| } |
| nodeChangeListeners.remove(pos); |
| } |
| } |
| |
| @Override |
| public void removePreferenceChangeListener(PreferenceChangeListener pcl) { |
| checkState(); |
| synchronized (preferenceChangeListeners) { |
| int pos; |
| if ((pos = preferenceChangeListeners.indexOf(pcl)) == -1) { |
| throw new IllegalArgumentException(); |
| } |
| preferenceChangeListeners.remove(pos); |
| } |
| } |
| |
| @Override |
| public void sync() throws BackingStoreException { |
| synchronized (lock) { |
| checkState(); |
| syncSpi(); |
| } |
| for (AbstractPreferences child : cachedChildren()) { |
| child.sync(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return (isUserNode() ? "User" : "System") + " Preference Node: " + absolutePath(); |
| } |
| |
| private void notifyChildAdded(Preferences child) { |
| NodeChangeEvent nce = new NodeAddEvent(this, child); |
| synchronized (events) { |
| events.add(nce); |
| events.notifyAll(); |
| } |
| } |
| |
| private void notifyChildRemoved(Preferences child) { |
| NodeChangeEvent nce = new NodeRemoveEvent(this, child); |
| synchronized (events) { |
| events.add(nce); |
| events.notifyAll(); |
| } |
| } |
| |
| private void notifyPreferenceChange(String key, String newValue) { |
| PreferenceChangeEvent pce = new PreferenceChangeEvent(this, key, newValue); |
| synchronized (events) { |
| events.add(pce); |
| events.notifyAll(); |
| } |
| } |
| |
| private static class EventDispatcher extends Thread { |
| EventDispatcher(String name){ |
| super(name); |
| } |
| |
| @Override |
| public void run() { |
| while (true) { |
| EventObject event = null; |
| try { |
| event = getEventObject(); |
| } catch (InterruptedException e) { |
| e.printStackTrace(); |
| continue; |
| } |
| AbstractPreferences pref = (AbstractPreferences) event |
| .getSource(); |
| if (event instanceof NodeAddEvent) { |
| dispatchNodeAdd((NodeChangeEvent) event, |
| pref.nodeChangeListeners); |
| } else if (event instanceof NodeRemoveEvent) { |
| dispatchNodeRemove((NodeChangeEvent) event, |
| pref.nodeChangeListeners); |
| } else if (event instanceof PreferenceChangeEvent) { |
| dispatchPrefChange((PreferenceChangeEvent) event, |
| pref.preferenceChangeListeners); |
| } |
| } |
| } |
| |
| private EventObject getEventObject() throws InterruptedException { |
| synchronized (events) { |
| if (events.isEmpty()) { |
| events.wait(); |
| } |
| EventObject event = events.get(0); |
| events.remove(0); |
| return event; |
| } |
| } |
| |
| private void dispatchPrefChange(PreferenceChangeEvent event, |
| List<EventListener> preferenceChangeListeners) { |
| synchronized (preferenceChangeListeners) { |
| Iterator<EventListener> i = preferenceChangeListeners.iterator(); |
| while (i.hasNext()) { |
| PreferenceChangeListener pcl = (PreferenceChangeListener) i |
| .next(); |
| pcl.preferenceChange(event); |
| } |
| } |
| } |
| |
| private void dispatchNodeRemove(NodeChangeEvent event, |
| List<EventListener> nodeChangeListeners) { |
| synchronized (nodeChangeListeners) { |
| Iterator<EventListener> i = nodeChangeListeners.iterator(); |
| while (i.hasNext()) { |
| NodeChangeListener ncl = (NodeChangeListener) i.next(); |
| ncl.childRemoved(event); |
| } |
| } |
| } |
| |
| private void dispatchNodeAdd(NodeChangeEvent event, |
| List<EventListener> nodeChangeListeners) { |
| synchronized (nodeChangeListeners) { |
| Iterator<EventListener> i = nodeChangeListeners.iterator(); |
| while (i.hasNext()) { |
| NodeChangeListener ncl = (NodeChangeListener) i.next(); |
| ncl.childAdded(event); |
| } |
| } |
| } |
| } |
| |
| private static class NodeAddEvent extends NodeChangeEvent { |
| //The base class is NOT serializable, so this class isn't either. |
| private static final long serialVersionUID = 1L; |
| |
| public NodeAddEvent(Preferences p, Preferences c) { |
| super(p, c); |
| } |
| } |
| |
| private static class NodeRemoveEvent extends NodeChangeEvent { |
| //The base class is NOT serializable, so this class isn't either. |
| private static final long serialVersionUID = 1L; |
| |
| public NodeRemoveEvent(Preferences p, Preferences c) { |
| super(p, c); |
| } |
| } |
| } |