001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.util.prefs;
019:
020: import java.io.IOException;
021: import java.io.OutputStream;
022: import java.io.UnsupportedEncodingException;
023: import java.util.EventListener;
024: import java.util.EventObject;
025: import java.util.HashMap;
026: import java.util.Iterator;
027: import java.util.LinkedList;
028: import java.util.List;
029: import java.util.Map;
030: import java.util.StringTokenizer;
031: import java.util.TreeSet;
032:
033: import org.apache.harmony.luni.util.Base64;
034: import org.apache.harmony.prefs.internal.nls.Messages;
035:
036: /**
037: * This class is partly implementation of <code>Preferences</code>, which can be
038: * used to simplify <code>Preferences</code> provider's implementation.
039: * <p>
040: * This class define nine abstract SPI methods, which must be implemented by
041: * preference provider. And provider can also override other methods of this
042: * class. Some SPI methods will throw <code>BackingStoreException</code>,
043: * including <code>childrenNamesSpi()</code>, <code>flushSpi()</code>,
044: * <code>keysSpi()</code>, <code>removeNodeSpi()</code>,
045: * <code>syncSpi()</code>; <code>getSpi(String, String)</code> never throws any
046: * exceptions; the last SPI methods, <code>putSpi(String)</code>,
047: * <code>removeSpi(String)</code> and <code>childSpi(String)</code> won't throw
048: * <code>BackingStoreException</code>, but in some implementations, they may
049: * throw <code>SecurityException</code> due to lacking the permission to access
050: * backing end storage.</p>
051: *
052: * @since 1.4
053: * @see Preferences
054: */
055: public abstract class AbstractPreferences extends Preferences {
056: /*
057: * -----------------------------------------------------------
058: * Class fields
059: * -----------------------------------------------------------
060: */
061: /** the unhandled events collection */
062: private static final List<EventObject> events = new LinkedList<EventObject>();
063: /** the event dispatcher thread */
064: private static final EventDispatcher dispatcher = new EventDispatcher(
065: "Preference Event Dispatcher"); //$NON-NLS-1$
066:
067: /*
068: * -----------------------------------------------------------
069: * Class initializer
070: * -----------------------------------------------------------
071: */
072: static {
073: dispatcher.setDaemon(true);
074: dispatcher.start();
075: Runtime.getRuntime().addShutdownHook(new Thread() {
076: @Override
077: public void run() {
078: Preferences uroot = Preferences.userRoot();
079: Preferences sroot = Preferences.systemRoot();
080: try {
081: uroot.flush();
082: } catch (BackingStoreException e) {//ignore
083: }
084: try {
085: sroot.flush();
086: } catch (BackingStoreException e) {//ignore
087: }
088: }
089: });
090: }
091:
092: /*
093: * -----------------------------------------------------------
094: * Instance fields (package-private)
095: * -----------------------------------------------------------
096: */
097: /** true if this node is in user preference hierarchy */
098: boolean userNode;
099:
100: /*
101: * -----------------------------------------------------------
102: * Instance fields (private)
103: * -----------------------------------------------------------
104: */
105: /** Marker class for 'lock' field. */
106: private static class Lock {
107: }
108:
109: /** The object used to lock this node. */
110: protected final Object lock;
111:
112: /**
113: * This field is true if this node is created while it doesn't exist in the
114: * backing store. This field's default value is false, and it is checked when
115: * the node creation is completed, and if it is true, the node change event
116: * will be fired for this node's parent.
117: */
118: protected boolean newNode;
119:
120: /** cached child nodes */
121: private Map<String, AbstractPreferences> cachedNode;
122:
123: //the collections of listeners
124: private List<EventListener> nodeChangeListeners;
125: private List<EventListener> preferenceChangeListeners;
126:
127: //this node's name
128: private String nodeName;
129:
130: //handler to this node's parent
131: private AbstractPreferences parentPref;
132:
133: //true if this node has been removed
134: private boolean isRemoved;
135:
136: //handler to this node's root node
137: private AbstractPreferences root;
138:
139: /*
140: * -----------------------------------------------------------
141: * Constructors
142: * -----------------------------------------------------------
143: */
144: /**
145: * Construct a new <code>AbstractPreferences</code> instance using given
146: * parent node and node name.
147: *
148: * @param parent
149: * the parent node of this node, can be null, which means this
150: * node is root
151: * @param name
152: * the name of this node, can be empty(""), which means this
153: * node is root
154: * @throws IllegalArgumentException
155: * if name contains slash, or be empty if parent is not null
156: */
157: protected AbstractPreferences(AbstractPreferences parent,
158: String name) {
159: if ((null == parent ^ name.length() == 0)
160: || name.indexOf("/") >= 0) { //$NON-NLS-1$
161: throw new IllegalArgumentException();
162: }
163: root = null == parent ? this : parent.root;
164: nodeChangeListeners = new LinkedList<EventListener>();
165: preferenceChangeListeners = new LinkedList<EventListener>();
166: isRemoved = false;
167: cachedNode = new HashMap<String, AbstractPreferences>();
168: nodeName = name;
169: parentPref = parent;
170: lock = new Lock();
171: userNode = root.userNode;
172: }
173:
174: /*
175: * -----------------------------------------------------------
176: * Methods
177: * -----------------------------------------------------------
178: */
179: /**
180: * Return arrays of all cached children node.
181: *
182: * @return arrays of all cached children node.
183: */
184: protected final AbstractPreferences[] cachedChildren() {
185: return cachedNode.values().toArray(
186: new AbstractPreferences[cachedNode.size()]);
187: }
188:
189: /**
190: * Return the child node with given name, or null if it doesn't exist. The
191: * given name must be valid and this node cannot be removed. Invocation of
192: * this method implies that the node with given name is not cached(or, has
193: * been removed.)
194: *
195: * @param name the given child name to be got
196: * @return the child node with given name, or null if it doesn't exist
197: * @throws BackingStoreException
198: * if backing store is unavailable or causes operation failure
199: */
200: protected AbstractPreferences getChild(String name)
201: throws BackingStoreException {
202: synchronized (lock) {
203: checkState();
204: AbstractPreferences result = null;
205: String[] childrenNames = childrenNames();
206: for (int i = 0; i < childrenNames.length; i++) {
207: if (childrenNames[i].equals(name)) {
208: result = childSpi(name);
209: break;
210: }
211: }
212: return result;
213: }
214:
215: }
216:
217: /**
218: * Return true if and only if this node has been removed by invoking
219: * {@link #removeNode() removeNode}.
220: *
221: * @return true if and only if this node has been removed by invoking
222: * {@link #removeNode() removeNode}
223: */
224: protected boolean isRemoved() {
225: synchronized (lock) {
226: return isRemoved;
227: }
228: }
229:
230: /**
231: * Flush changes of this node to the backing store. This method should only
232: * flush this node, and should not include the descendant nodes. The
233: * implementation which want to flush all nodes at once should override
234: * {@link #flush() flush()} method.
235: *
236: * @throws BackingStoreException
237: * if backing store is unavailable or causes operation failure
238: */
239: protected abstract void flushSpi() throws BackingStoreException;
240:
241: /**
242: * Return names of this node's all children , or empty array if this node has
243: * no child. Cached children name is not required to be returned.
244: *
245: * @return names of this node's all children
246: * @throws BackingStoreException
247: * if backing store is unavailable or causes operation failure
248: */
249: protected abstract String[] childrenNamesSpi()
250: throws BackingStoreException;
251:
252: /**
253: * Return the child preference node with the given name, and create new one if
254: * it does not exist. Invoker of this method should assure that the given name
255: * are valid as well as this node is not removed. Invocation of this method
256: * implies that the node with given name is not cached(or, has been removed.)
257: * If the named node has just been removed, implementation of this method must
258: * create a new one instead of reactivated the removed one.
259: * <p>
260: * The new creation is not required to be persisted immediately until the flush
261: * method is invoked.</p>
262: *
263: * @param name
264: * @return AbstractPreferences
265: */
266: protected abstract AbstractPreferences childSpi(String name);
267:
268: /**
269: * Put the given key-value pair into this node. Invoker of this method should
270: * assure that both the given values are valid as well as this node
271: * is not removed.
272: *
273: * @param name the given preference key
274: * @param value the given preference value
275: */
276: protected abstract void putSpi(String name, String value);
277:
278: /**
279: * Get the preference value mapped to the given key. Invoker of this method
280: * should assure that given key are valid as well as this node is not removed.
281: * This method should not throw exceptions, but if it does, the invoker should
282: * catch it and deal with it as null return value.
283: *
284: * @param key the given key to be searched for
285: * @return the preference value mapped to the given key
286: */
287: protected abstract String getSpi(String key);
288:
289: /**
290: * Return all keys of this node's preferences, or empty array if no preference
291: * found on this node. Invoker of this method should assure that this node is
292: * not removed.
293: *
294: * @return all keys of this node's preferences
295: * @throws BackingStoreException
296: * if backing store is unavailable or causes operation failure
297: */
298: protected abstract String[] keysSpi() throws BackingStoreException;
299:
300: /**
301: * Remove this node from the preference hierarchy tree. The invoker of this
302: * method should assure that this node has no child node, which means the
303: * {@link Preferences#removeNode() Preferences.removeNode()} should invoke
304: * this method multi-times in bottom-up pattern. The removal is not required
305: * to be persisted at once until the it is flushed.
306: *
307: * @throws BackingStoreException
308: * if backing store is unavailable or causes operation failure
309: */
310: protected abstract void removeNodeSpi()
311: throws BackingStoreException;
312:
313: /**
314: * Remove the preference with the given key. Invoker of this method
315: * should assure that given key are valid as well as this node is not removed.
316: *
317: * @param key the given key to removed
318: */
319: protected abstract void removeSpi(String key);
320:
321: /**
322: * Synchronize this node with the backing store. This method should only
323: * synchronize this node, and should not include the descendant nodes. The
324: * implementation which want to synchronize all nodes at once should override
325: * {@link #sync() sync()} method.
326: *
327: * @throws BackingStoreException
328: * if backing store is unavailable or causes operation failure
329: */
330: protected abstract void syncSpi() throws BackingStoreException;
331:
332: /*
333: * -----------------------------------------------------------
334: * Methods inherited from Preferences
335: * -----------------------------------------------------------
336: */
337: @Override
338: public String absolutePath() {
339: if (parentPref == null) {
340: return "/"; //$NON-NLS-1$
341: } else if (parentPref == root) {
342: return "/" + nodeName; //$NON-NLS-1$
343: }
344: return parentPref.absolutePath() + "/" + nodeName; //$NON-NLS-1$
345: }
346:
347: @Override
348: public String[] childrenNames() throws BackingStoreException {
349: synchronized (lock) {
350: checkState();
351: TreeSet<String> result = new TreeSet<String>(cachedNode
352: .keySet());
353: String[] names = childrenNamesSpi();
354: for (int i = 0; i < names.length; i++) {
355: result.add(names[i]);
356: }
357: return result.toArray(new String[0]);
358: }
359: }
360:
361: @Override
362: public void clear() throws BackingStoreException {
363: synchronized (lock) {
364: String[] keyList = keys();
365: for (int i = 0; i < keyList.length; i++) {
366: remove(keyList[i]);
367: }
368: }
369: }
370:
371: @Override
372: public void exportNode(OutputStream ostream) throws IOException,
373: BackingStoreException {
374: if (ostream == null) {
375: // prefs.5=Stream is null
376: throw new NullPointerException(Messages
377: .getString("prefs.5")); //$NON-NLS-1$
378: }
379: checkState();
380: XMLParser.exportPrefs(this , ostream, false);
381:
382: }
383:
384: @Override
385: public void exportSubtree(OutputStream ostream) throws IOException,
386: BackingStoreException {
387: if (ostream == null) {
388: // prefs.5=Stream is null
389: throw new NullPointerException(Messages
390: .getString("prefs.5")); //$NON-NLS-1$
391: }
392: checkState();
393: XMLParser.exportPrefs(this , ostream, true);
394: }
395:
396: @Override
397: public void flush() throws BackingStoreException {
398: synchronized (lock) {
399: flushSpi();
400: }
401: AbstractPreferences[] cc = cachedChildren();
402: int i;
403: for (i = 0; i < cc.length; i++) {
404: cc[i].flush();
405: }
406: }
407:
408: @Override
409: public String get(String key, String deflt) {
410: if (key == null) {
411: throw new NullPointerException();
412: }
413: String result;
414: synchronized (lock) {
415: checkState();
416: try {
417: result = getSpi(key);
418: } catch (Exception e) {
419: result = null;
420: }
421: }
422: return (result == null ? deflt : result);
423: }
424:
425: @Override
426: public boolean getBoolean(String key, boolean deflt) {
427: String result = get(key, null);
428: if (result == null) {
429: return deflt;
430: } else if (result.equalsIgnoreCase("true")) { //$NON-NLS-1$
431: return true;
432: } else if (result.equalsIgnoreCase("false")) { //$NON-NLS-1$
433: return false;
434: } else {
435: return deflt;
436: }
437: }
438:
439: @Override
440: public byte[] getByteArray(String key, byte[] deflt) {
441: String svalue = get(key, null);
442: if (svalue == null) {
443: return deflt;
444: }
445: if (svalue.length() == 0) {
446: return new byte[0];
447: }
448: byte[] dres;
449: try {
450: byte[] bavalue = svalue.getBytes("US-ASCII"); //$NON-NLS-1$
451: if (bavalue.length % 4 != 0) {
452: return deflt;
453: }
454: dres = Base64.decode(bavalue);
455: } catch (Exception e) {
456: dres = deflt;
457: }
458: return dres;
459: }
460:
461: @Override
462: public double getDouble(String key, double deflt) {
463: String result = get(key, null);
464: if (result == null) {
465: return deflt;
466: }
467: double dres;
468: try {
469: dres = Double.parseDouble(result);
470: } catch (NumberFormatException e) {
471: dres = deflt;
472: }
473: return dres;
474: }
475:
476: @Override
477: public float getFloat(String key, float deflt) {
478: String result = get(key, null);
479: if (result == null) {
480: return deflt;
481: }
482: float fres;
483: try {
484: fres = Float.parseFloat(result);
485: } catch (NumberFormatException e) {
486: fres = deflt;
487: }
488: return fres;
489: }
490:
491: @Override
492: public int getInt(String key, int deflt) {
493: String result = get(key, null);
494: if (result == null) {
495: return deflt;
496: }
497: int ires;
498: try {
499: ires = Integer.parseInt(result);
500: } catch (NumberFormatException e) {
501: ires = deflt;
502: }
503: return ires;
504: }
505:
506: @Override
507: public long getLong(String key, long deflt) {
508: String result = get(key, null);
509: if (result == null) {
510: return deflt;
511: }
512: long lres;
513: try {
514: lres = Long.parseLong(result);
515: } catch (NumberFormatException e) {
516: lres = deflt;
517: }
518: return lres;
519: }
520:
521: @Override
522: public boolean isUserNode() {
523: return root == Preferences.userRoot();
524: }
525:
526: @Override
527: public String[] keys() throws BackingStoreException {
528: synchronized (lock) {
529: checkState();
530: return keysSpi();
531: }
532: }
533:
534: @Override
535: public String name() {
536: return nodeName;
537: }
538:
539: @Override
540: public Preferences node(String name) {
541: AbstractPreferences startNode = null;
542: synchronized (lock) {
543: checkState();
544: validateName(name);
545: if ("".equals(name)) { //$NON-NLS-1$
546: return this ;
547: } else if ("/".equals(name)) { //$NON-NLS-1$
548: return root;
549: }
550: if (name.startsWith("/")) { //$NON-NLS-1$
551: startNode = root;
552: name = name.substring(1);
553: } else {
554: startNode = this ;
555: }
556: }
557: Preferences result = null;
558: try {
559: result = startNode.nodeImpl(name, true);
560: } catch (BackingStoreException e) {
561: //should not happen
562: }
563: return result;
564: }
565:
566: private void validateName(String name) {
567: if (name.endsWith("/") && name.length() > 1) { //$NON-NLS-1$
568: // prefs.6=Name cannot end with '/'\!
569: throw new IllegalArgumentException(Messages
570: .getString("prefs.6")); //$NON-NLS-1$
571: }
572: if (name.indexOf("//") >= 0) { //$NON-NLS-1$
573: // prefs.7=Name cannot contains consecutive '/'\!
574: throw new IllegalArgumentException(Messages
575: .getString("prefs.7")); //$NON-NLS-1$
576: }
577: }
578:
579: private AbstractPreferences nodeImpl(String path, boolean createNew)
580: throws BackingStoreException {
581: StringTokenizer st = new StringTokenizer(path, "/"); //$NON-NLS-1$
582: AbstractPreferences currentNode = this ;
583: AbstractPreferences temp = null;
584: while (st.hasMoreTokens() && null != currentNode) {
585: String name = st.nextToken();
586: synchronized (currentNode.lock) {
587: temp = currentNode.cachedNode.get(name);
588: if (temp == null) {
589: temp = getNodeFromBackend(createNew, currentNode,
590: name);
591: }
592: }
593:
594: currentNode = temp;
595: }
596: return currentNode;
597: }
598:
599: private AbstractPreferences getNodeFromBackend(boolean createNew,
600: AbstractPreferences currentNode, String name)
601: throws BackingStoreException {
602: AbstractPreferences temp;
603: if (name.length() > MAX_NAME_LENGTH) {
604: // prefs.8=Name length is too long: {0}
605: throw new IllegalArgumentException(Messages.getString(
606: "prefs.8", //$NON-NLS-1$
607: name));
608: }
609: if (createNew) {
610: temp = currentNode.childSpi(name);
611: currentNode.cachedNode.put(name, temp);
612: if (temp.newNode
613: && currentNode.nodeChangeListeners.size() > 0) {
614: currentNode.notifyChildAdded(temp);
615: }
616: } else {
617: temp = currentNode.getChild(name);
618: }
619: return temp;
620: }
621:
622: @Override
623: public boolean nodeExists(String name) throws BackingStoreException {
624: AbstractPreferences startNode = null;
625: synchronized (lock) {
626: if (isRemoved()) {
627: if ("".equals(name)) { //$NON-NLS-1$
628: return false;
629: }
630: // prefs.9=This node has been removed\!
631: throw new IllegalStateException(Messages
632: .getString("prefs.9")); //$NON-NLS-1$
633: }
634: validateName(name);
635: if ("".equals(name) || "/".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
636: return true;
637: }
638: if (name.startsWith("/")) { //$NON-NLS-1$
639: startNode = root;
640: name = name.substring(1);
641: } else {
642: startNode = this ;
643: }
644: }
645: try {
646: Preferences result = startNode.nodeImpl(name, false);
647: return null == result ? false : true;
648: } catch (IllegalArgumentException e) {
649: return false;
650: }
651: }
652:
653: @Override
654: public Preferences parent() {
655: checkState();
656: return parentPref;
657: }
658:
659: private void checkState() {
660: if (isRemoved()) {
661: // prefs.9=This node has been removed\!
662: throw new IllegalStateException(Messages
663: .getString("prefs.9")); //$NON-NLS-1$
664: }
665: }
666:
667: @Override
668: public void put(String key, String value) {
669: if (null == key || null == value) {
670: throw new NullPointerException();
671: }
672: if (key.length() > MAX_KEY_LENGTH
673: || value.length() > MAX_VALUE_LENGTH) {
674: throw new IllegalArgumentException();
675: }
676: synchronized (lock) {
677: checkState();
678: putSpi(key, value);
679: }
680: notifyPreferenceChange(key, value);
681: }
682:
683: @Override
684: public void putBoolean(String key, boolean value) {
685: String sval = String.valueOf(value);
686: put(key, sval);
687: }
688:
689: @Override
690: public void putByteArray(String key, byte[] value) {
691: try {
692: put(key, Base64.encode(value, "US-ASCII")); //$NON-NLS-1$
693: } catch (UnsupportedEncodingException e) {
694: throw new AssertionError(e);
695: }
696: }
697:
698: @Override
699: public void putDouble(String key, double value) {
700: String sval = Double.toString(value);
701: put(key, sval);
702: }
703:
704: @Override
705: public void putFloat(String key, float value) {
706: String sval = Float.toString(value);
707: put(key, sval);
708: }
709:
710: @Override
711: public void putInt(String key, int value) {
712: String sval = Integer.toString(value);
713: put(key, sval);
714: }
715:
716: @Override
717: public void putLong(String key, long value) {
718: String sval = Long.toString(value);
719: put(key, sval);
720: }
721:
722: @Override
723: public void remove(String key) {
724: synchronized (lock) {
725: checkState();
726: removeSpi(key);
727: }
728: notifyPreferenceChange(key, null);
729: }
730:
731: @Override
732: public void removeNode() throws BackingStoreException {
733: if (root == this ) {
734: // prefs.A=Cannot remove root node\!
735: throw new UnsupportedOperationException(Messages
736: .getString("prefs.A")); //$NON-NLS-1$
737: }
738: synchronized (parentPref.lock) {
739: removeNodeImpl();
740: }
741: }
742:
743: private void removeNodeImpl() throws BackingStoreException {
744: synchronized (lock) {
745: checkState();
746: String[] childrenNames = childrenNamesSpi();
747: for (int i = 0; i < childrenNames.length; i++) {
748: if (null == cachedNode.get(childrenNames[i])) {
749: AbstractPreferences child = childSpi(childrenNames[i]);
750: cachedNode.put(childrenNames[i], child);
751: }
752: }
753: AbstractPreferences[] children = cachedNode.values()
754: .toArray(new AbstractPreferences[0]);
755: for (int i = 0; i < children.length; i++) {
756: children[i].removeNodeImpl();
757: }
758: removeNodeSpi();
759: isRemoved = true;
760: parentPref.cachedNode.remove(nodeName);
761: }
762: if (parentPref.nodeChangeListeners.size() > 0) {
763: parentPref.notifyChildRemoved(this );
764: }
765: }
766:
767: @Override
768: public void addNodeChangeListener(NodeChangeListener ncl) {
769: if (null == ncl) {
770: throw new NullPointerException();
771: }
772: checkState();
773: synchronized (nodeChangeListeners) {
774: nodeChangeListeners.add(ncl);
775: }
776: }
777:
778: @Override
779: public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
780: if (null == pcl) {
781: throw new NullPointerException();
782: }
783: checkState();
784: synchronized (preferenceChangeListeners) {
785: preferenceChangeListeners.add(pcl);
786: }
787: }
788:
789: @Override
790: public void removeNodeChangeListener(NodeChangeListener ncl) {
791: checkState();
792: synchronized (nodeChangeListeners) {
793: int pos;
794: if ((pos = nodeChangeListeners.indexOf(ncl)) == -1) {
795: throw new IllegalArgumentException();
796: }
797: nodeChangeListeners.remove(pos);
798: }
799: }
800:
801: @Override
802: public void removePreferenceChangeListener(
803: PreferenceChangeListener pcl) {
804: checkState();
805: synchronized (preferenceChangeListeners) {
806: int pos;
807: if ((pos = preferenceChangeListeners.indexOf(pcl)) == -1) {
808: throw new IllegalArgumentException();
809: }
810: preferenceChangeListeners.remove(pos);
811: }
812: }
813:
814: @Override
815: public void sync() throws BackingStoreException {
816: synchronized (lock) {
817: checkState();
818: syncSpi();
819: }
820: AbstractPreferences[] cc = cachedChildren();
821: int i;
822: for (i = 0; i < cc.length; i++) {
823: cc[i].sync();
824: }
825: }
826:
827: @Override
828: public String toString() {
829: StringBuffer sb = new StringBuffer();
830: sb.append(isUserNode() ? "User" : "System"); //$NON-NLS-1$ //$NON-NLS-2$
831: sb.append(" Preference Node: "); //$NON-NLS-1$
832: sb.append(absolutePath());
833: return sb.toString();
834: }
835:
836: private void notifyChildAdded(Preferences child) {
837: NodeChangeEvent nce = new NodeAddEvent(this , child);
838: synchronized (events) {
839: events.add(nce);
840: events.notifyAll();
841: }
842: }
843:
844: private void notifyChildRemoved(Preferences child) {
845: NodeChangeEvent nce = new NodeRemoveEvent(this , child);
846: synchronized (events) {
847: events.add(nce);
848: events.notifyAll();
849: }
850: }
851:
852: private void notifyPreferenceChange(String key, String newValue) {
853: PreferenceChangeEvent pce = new PreferenceChangeEvent(this ,
854: key, newValue);
855: synchronized (events) {
856: events.add(pce);
857: events.notifyAll();
858: }
859: }
860:
861: private static class EventDispatcher extends Thread {
862: EventDispatcher(String name) {
863: super (name);
864: }
865:
866: @Override
867: public void run() {
868: while (true) {
869: EventObject event = null;
870: try {
871: event = getEventObject();
872: } catch (InterruptedException e) {
873: e.printStackTrace();
874: continue;
875: }
876: AbstractPreferences pref = (AbstractPreferences) event
877: .getSource();
878: if (event instanceof NodeAddEvent) {
879: dispatchNodeAdd((NodeChangeEvent) event,
880: pref.nodeChangeListeners);
881: } else if (event instanceof NodeRemoveEvent) {
882: dispatchNodeRemove((NodeChangeEvent) event,
883: pref.nodeChangeListeners);
884: } else if (event instanceof PreferenceChangeEvent) {
885: dispatchPrefChange((PreferenceChangeEvent) event,
886: pref.preferenceChangeListeners);
887: }
888: }
889: }
890:
891: private EventObject getEventObject()
892: throws InterruptedException {
893: synchronized (events) {
894: if (events.isEmpty()) {
895: events.wait();
896: }
897: EventObject event = events.get(0);
898: events.remove(0);
899: return event;
900: }
901: }
902:
903: private void dispatchPrefChange(PreferenceChangeEvent event,
904: List<EventListener> preferenceChangeListeners) {
905: synchronized (preferenceChangeListeners) {
906: Iterator<EventListener> i = preferenceChangeListeners
907: .iterator();
908: while (i.hasNext()) {
909: PreferenceChangeListener pcl = (PreferenceChangeListener) i
910: .next();
911: pcl.preferenceChange(event);
912: }
913: }
914: }
915:
916: private void dispatchNodeRemove(NodeChangeEvent event,
917: List<EventListener> nodeChangeListeners) {
918: synchronized (nodeChangeListeners) {
919: Iterator<EventListener> i = nodeChangeListeners
920: .iterator();
921: while (i.hasNext()) {
922: NodeChangeListener ncl = (NodeChangeListener) i
923: .next();
924: ncl.childRemoved(event);
925: }
926: }
927: }
928:
929: private void dispatchNodeAdd(NodeChangeEvent event,
930: List<EventListener> nodeChangeListeners) {
931: synchronized (nodeChangeListeners) {
932: Iterator<EventListener> i = nodeChangeListeners
933: .iterator();
934: while (i.hasNext()) {
935: NodeChangeListener ncl = (NodeChangeListener) i
936: .next();
937: ncl.childAdded(event);
938: }
939: }
940: }
941: }
942:
943: private static class NodeAddEvent extends NodeChangeEvent {
944: //The base class is NOT serializable, so this class isn't either.
945: private static final long serialVersionUID = 1L;
946:
947: public NodeAddEvent(Preferences p, Preferences c) {
948: super (p, c);
949: }
950: }
951:
952: private static class NodeRemoveEvent extends NodeChangeEvent {
953: //The base class is NOT serializable, so this class isn't either.
954: private static final long serialVersionUID = 1L;
955:
956: public NodeRemoveEvent(Preferences p, Preferences c) {
957: super(p, c);
958: }
959: }
960: }
|