Merge from mainline (gomp-merge-2005-02-26).
[official-gcc.git] / libjava / java / util / prefs / AbstractPreferences.java
blob159a887c831db223e3eee2c274ea38a67757db81
1 /* AbstractPreferences -- Partial implementation of a Preference node
2 Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
39 package java.util.prefs;
41 import gnu.java.util.prefs.NodeWriter;
43 import java.io.ByteArrayOutputStream;
44 import java.io.IOException;
45 import java.io.OutputStream;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.TreeSet;
50 /**
51 * Partial implementation of a Preference node.
53 * @since 1.4
54 * @author Mark Wielaard (mark@klomp.org)
56 public abstract class AbstractPreferences extends Preferences {
58 // protected fields
60 /**
61 * Object used to lock this preference node. Any thread only locks nodes
62 * downwards when it has the lock on the current node. No method should
63 * synchronize on the lock of any of its parent nodes while holding the
64 * lock on the current node.
66 protected final Object lock = new Object();
68 /**
69 * Set to true in the contructor if the node did not exist in the backing
70 * store when this preference node object was created. Should be set in
71 * the contructor of a subclass. Defaults to false. Used to fire node
72 * changed events.
74 protected boolean newNode = false;
76 // private fields
78 /**
79 * The parent preferences node or null when this is the root node.
81 private final AbstractPreferences parent;
83 /**
84 * The name of this node.
85 * Only when this is a root node (parent == null) the name is empty.
86 * It has a maximum of 80 characters and cannot contain any '/' characters.
88 private final String name;
90 /** True when this node has been remove, false otherwise. */
91 private boolean removed = false;
93 /**
94 * Holds all the child names and nodes of this node that have been
95 * accessed by earlier <code>getChild()</code> or <code>childSpi()</code>
96 * invocations and that have not been removed.
98 private HashMap childCache = new HashMap();
100 // constructor
103 * Creates a new AbstractPreferences node with the given parent and name.
105 * @param parent the parent of this node or null when this is the root node
106 * @param name the name of this node, can not be null, only 80 characters
107 * maximum, must be empty when parent is null and cannot
108 * contain any '/' characters
109 * @exception IllegalArgumentException when name is null, greater then 80
110 * characters, not the empty string but parent is null or
111 * contains a '/' character
113 protected AbstractPreferences(AbstractPreferences parent, String name) {
114 if ( (name == null) // name should be given
115 || (name.length() > MAX_NAME_LENGTH) // 80 characters max
116 || (parent == null && name.length() != 0) // root has no name
117 || (parent != null && name.length() == 0) // all other nodes do
118 || (name.indexOf('/') != -1)) // must not contain '/'
119 throw new IllegalArgumentException("Illegal name argument '"
120 + name
121 + "' (parent is "
122 + (parent == null ? "" : "not ")
123 + "null)");
124 this.parent = parent;
125 this.name = name;
128 // identification methods
131 * Returns the absolute path name of this preference node.
132 * The absolute path name of a node is the path name of its parent node
133 * plus a '/' plus its own name. If the node is the root node and has no
134 * parent then its path name is "" and its absolute path name is "/".
136 public String absolutePath() {
137 if (parent == null)
138 return "/";
139 else
140 return parent.path() + '/' + name;
144 * Private helper method for absolutePath. Returns the empty string for a
145 * root node and otherwise the parentPath of its parent plus a '/'.
147 private String path() {
148 if (parent == null)
149 return "";
150 else
151 return parent.path() + '/' + name;
155 * Returns true if this node comes from the user preferences tree, false
156 * if it comes from the system preferences tree.
158 public boolean isUserNode() {
159 AbstractPreferences root = this;
160 while (root.parent != null)
161 root = root.parent;
162 return root == Preferences.userRoot();
166 * Returns the name of this preferences node. The name of the node cannot
167 * be null, can be mostly 80 characters and cannot contain any '/'
168 * characters. The root node has as name "".
170 public String name() {
171 return name;
175 * Returns the String given by
176 * <code>
177 * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath()
178 * </code>
180 public String toString() {
181 return (isUserNode() ? "User":"System")
182 + " Preference Node: "
183 + absolutePath();
187 * Returns all known unremoved children of this node.
189 * @return All known unremoved children of this node
191 protected final AbstractPreferences[] cachedChildren()
193 return (AbstractPreferences[]) childCache.values().toArray();
197 * Returns all the direct sub nodes of this preferences node.
198 * Needs access to the backing store to give a meaningfull answer.
199 * <p>
200 * This implementation locks this node, checks if the node has not yet
201 * been removed and throws an <code>IllegalStateException</code> when it
202 * has been. Then it creates a new <code>TreeSet</code> and adds any
203 * already cached child nodes names. To get any uncached names it calls
204 * <code>childrenNamesSpi()</code> and adds the result to the set. Finally
205 * it calls <code>toArray()</code> on the created set. When the call to
206 * <code>childrenNamesSpi</code> thows an <code>BackingStoreException</code>
207 * this method will not catch that exception but propagate the exception
208 * to the caller.
210 * @exception BackingStoreException when the backing store cannot be
211 * reached
212 * @exception IllegalStateException when this node has been removed
214 public String[] childrenNames() throws BackingStoreException {
215 synchronized(lock) {
216 if (isRemoved())
217 throw new IllegalStateException("Node removed");
219 TreeSet childrenNames = new TreeSet();
221 // First get all cached node names
222 childrenNames.addAll(childCache.keySet());
224 // Then add any others
225 String names[] = childrenNamesSpi();
226 for (int i = 0; i < names.length; i++) {
227 childrenNames.add(names[i]);
230 // And return the array of names
231 String[] children = new String[childrenNames.size()];
232 childrenNames.toArray(children);
233 return children;
239 * Returns a sub node of this preferences node if the given path is
240 * relative (does not start with a '/') or a sub node of the root
241 * if the path is absolute (does start with a '/').
242 * <p>
243 * This method first locks this node and checks if the node has not been
244 * removed, if it has been removed it throws an exception. Then if the
245 * path is relative (does not start with a '/') it checks if the path is
246 * legal (does not end with a '/' and has no consecutive '/' characters).
247 * Then it recursively gets a name from the path, gets the child node
248 * from the child-cache of this node or calls the <code>childSpi()</code>
249 * method to create a new child sub node. This is done recursively on the
250 * newly created sub node with the rest of the path till the path is empty.
251 * If the path is absolute (starts with a '/') the lock on this node is
252 * droped and this method is called on the root of the preferences tree
253 * with as argument the complete path minus the first '/'.
255 * @exception IllegalStateException if this node has been removed
256 * @exception IllegalArgumentException if the path contains two or more
257 * consecutive '/' characters, ends with a '/' charactor and is not the
258 * string "/" (indicating the root node) or any name on the path is more
259 * then 80 characters long
261 public Preferences node(String path) {
262 synchronized(lock) {
263 if (isRemoved())
264 throw new IllegalStateException("Node removed");
266 // Is it a relative path?
267 if (!path.startsWith("/")) {
269 // Check if it is a valid path
270 if (path.indexOf("//") != -1 || path.endsWith("/"))
271 throw new IllegalArgumentException(path);
273 return getNode(path);
277 // path started with a '/' so it is absolute
278 // we drop the lock and start from the root (omitting the first '/')
279 Preferences root = isUserNode() ? userRoot() : systemRoot();
280 return root.node(path.substring(1));
285 * Private helper method for <code>node()</code>. Called with this node
286 * locked. Returns this node when path is the empty string, if it is not
287 * empty the next node name is taken from the path (all chars till the
288 * next '/' or end of path string) and the node is either taken from the
289 * child-cache of this node or the <code>childSpi()</code> method is called
290 * on this node with the name as argument. Then this method is called
291 * recursively on the just constructed child node with the rest of the
292 * path.
294 * @param path should not end with a '/' character and should not contain
295 * consecutive '/' characters
296 * @exception IllegalArgumentException if path begins with a name that is
297 * larger then 80 characters.
299 private Preferences getNode(String path) {
300 // if mark is dom then goto end
302 // Empty String "" indicates this node
303 if (path.length() == 0)
304 return this;
306 // Calculate child name and rest of path
307 String childName;
308 String childPath;
309 int nextSlash = path.indexOf('/');
310 if (nextSlash == -1) {
311 childName = path;
312 childPath = "";
313 } else {
314 childName = path.substring(0, nextSlash);
315 childPath = path.substring(nextSlash+1);
318 // Get the child node
319 AbstractPreferences child;
320 child = (AbstractPreferences)childCache.get(childName);
321 if (child == null) {
323 if (childName.length() > MAX_NAME_LENGTH)
324 throw new IllegalArgumentException(childName);
326 // Not in childCache yet so create a new sub node
327 child = childSpi(childName);
328 // XXX - check if node is new
329 childCache.put(childName, child);
332 // Lock the child and go down
333 synchronized(child.lock) {
334 return child.getNode(childPath);
339 * Returns true if the node that the path points to exists in memory or
340 * in the backing store. Otherwise it returns false or an exception is
341 * thrown. When this node is removed the only valid parameter is the
342 * empty string (indicating this node), the return value in that case
343 * will be false.
345 * @exception BackingStoreException when the backing store cannot be
346 * reached
347 * @exception IllegalStateException if this node has been removed
348 * and the path is not the empty string (indicating this node)
349 * @exception IllegalArgumentException if the path contains two or more
350 * consecutive '/' characters, ends with a '/' charactor and is not the
351 * string "/" (indicating the root node) or any name on the path is more
352 * then 80 characters long
354 public boolean nodeExists(String path) throws BackingStoreException {
355 synchronized(lock) {
356 if (isRemoved() && path.length() != 0)
357 throw new IllegalStateException("Node removed");
359 // Is it a relative path?
360 if (!path.startsWith("/")) {
362 // Check if it is a valid path
363 if (path.indexOf("//") != -1 || path.endsWith("/"))
364 throw new IllegalArgumentException(path);
366 return existsNode(path);
370 // path started with a '/' so it is absolute
371 // we drop the lock and start from the root (omitting the first '/')
372 Preferences root = isUserNode() ? userRoot() : systemRoot();
373 return root.nodeExists(path.substring(1));
377 private boolean existsNode(String path) throws BackingStoreException {
379 // Empty String "" indicates this node
380 if (path.length() == 0)
381 return(!isRemoved());
383 // Calculate child name and rest of path
384 String childName;
385 String childPath;
386 int nextSlash = path.indexOf('/');
387 if (nextSlash == -1) {
388 childName = path;
389 childPath = "";
390 } else {
391 childName = path.substring(0, nextSlash);
392 childPath = path.substring(nextSlash+1);
395 // Get the child node
396 AbstractPreferences child;
397 child = (AbstractPreferences)childCache.get(childName);
398 if (child == null) {
400 if (childName.length() > MAX_NAME_LENGTH)
401 throw new IllegalArgumentException(childName);
403 // Not in childCache yet so create a new sub node
404 child = getChild(childName);
406 if (child == null)
407 return false;
409 childCache.put(childName, child);
412 // Lock the child and go down
413 synchronized(child.lock) {
414 return child.existsNode(childPath);
419 * Returns the child sub node if it exists in the backing store or null
420 * if it does not exist. Called (indirectly) by <code>nodeExists()</code>
421 * when a child node name can not be found in the cache.
422 * <p>
423 * Gets the lock on this node, calls <code>childrenNamesSpi()</code> to
424 * get an array of all (possibly uncached) children and compares the
425 * given name with the names in the array. If the name is found in the
426 * array <code>childSpi()</code> is called to get an instance, otherwise
427 * null is returned.
429 * @exception BackingStoreException when the backing store cannot be
430 * reached
432 protected AbstractPreferences getChild(String name)
433 throws BackingStoreException
435 synchronized(lock) {
436 // Get all the names (not yet in the cache)
437 String[] names = childrenNamesSpi();
438 for (int i=0; i < names.length; i++)
439 if (name.equals(names[i]))
440 return childSpi(name);
442 // No child with that name found
443 return null;
448 * Returns true if this node has been removed with the
449 * <code>removeNode()</code> method, false otherwise.
450 * <p>
451 * Gets the lock on this node and then returns a boolean field set by
452 * <code>removeNode</code> methods.
454 protected boolean isRemoved() {
455 synchronized(lock) {
456 return removed;
461 * Returns the parent preferences node of this node or null if this is
462 * the root of the preferences tree.
463 * <p>
464 * Gets the lock on this node, checks that the node has not been removed
465 * and returns the parent given to the constructor.
467 * @exception IllegalStateException if this node has been removed
469 public Preferences parent() {
470 synchronized(lock) {
471 if (isRemoved())
472 throw new IllegalStateException("Node removed");
474 return parent;
478 // export methods
481 * XXX
483 public void exportNode(OutputStream os)
484 throws BackingStoreException,
485 IOException
487 NodeWriter nodeWriter = new NodeWriter(this, os);
488 nodeWriter.writePrefs();
492 * XXX
494 public void exportSubtree(OutputStream os)
495 throws BackingStoreException,
496 IOException
498 NodeWriter nodeWriter = new NodeWriter(this, os);
499 nodeWriter.writePrefsTree();
502 // preference entry manipulation methods
505 * Returns an (possibly empty) array with all the keys of the preference
506 * entries of this node.
507 * <p>
508 * This method locks this node and checks if the node has not been
509 * removed, if it has been removed it throws an exception, then it returns
510 * the result of calling <code>keysSpi()</code>.
512 * @exception BackingStoreException when the backing store cannot be
513 * reached
514 * @exception IllegalStateException if this node has been removed
516 public String[] keys() throws BackingStoreException {
517 synchronized(lock) {
518 if (isRemoved())
519 throw new IllegalStateException("Node removed");
521 return keysSpi();
527 * Returns the value associated with the key in this preferences node. If
528 * the default value of the key cannot be found in the preferences node
529 * entries or something goes wrong with the backing store the supplied
530 * default value is returned.
531 * <p>
532 * Checks that key is not null and not larger then 80 characters,
533 * locks this node, and checks that the node has not been removed.
534 * Then it calls <code>keySpi()</code> and returns
535 * the result of that method or the given default value if it returned
536 * null or throwed an exception.
538 * @exception IllegalArgumentException if key is larger then 80 characters
539 * @exception IllegalStateException if this node has been removed
540 * @exception NullPointerException if key is null
542 public String get(String key, String defaultVal) {
543 if (key.length() > MAX_KEY_LENGTH)
544 throw new IllegalArgumentException(key);
546 synchronized(lock) {
547 if (isRemoved())
548 throw new IllegalStateException("Node removed");
550 String value;
551 try {
552 value = getSpi(key);
553 } catch (ThreadDeath death) {
554 throw death;
555 } catch (Throwable t) {
556 value = null;
559 if (value != null) {
560 return value;
561 } else {
562 return defaultVal;
568 * Convenience method for getting the given entry as a boolean.
569 * When the string representation of the requested entry is either
570 * "true" or "false" (ignoring case) then that value is returned,
571 * otherwise the given default boolean value is returned.
573 * @exception IllegalArgumentException if key is larger then 80 characters
574 * @exception IllegalStateException if this node has been removed
575 * @exception NullPointerException if key is null
577 public boolean getBoolean(String key, boolean defaultVal) {
578 String value = get(key, null);
580 if ("true".equalsIgnoreCase(value))
581 return true;
583 if ("false".equalsIgnoreCase(value))
584 return false;
586 return defaultVal;
590 * Convenience method for getting the given entry as a byte array.
591 * When the string representation of the requested entry is a valid
592 * Base64 encoded string (without any other characters, such as newlines)
593 * then the decoded Base64 string is returned as byte array,
594 * otherwise the given default byte array value is returned.
596 * @exception IllegalArgumentException if key is larger then 80 characters
597 * @exception IllegalStateException if this node has been removed
598 * @exception NullPointerException if key is null
600 public byte[] getByteArray(String key, byte[] defaultVal) {
601 String value = get(key, null);
603 byte[] b = null;
604 if (value != null) {
605 b = decode64(value);
608 if (b != null)
609 return b;
610 else
611 return defaultVal;
615 * Helper method for decoding a Base64 string as an byte array.
616 * Returns null on encoding error. This method does not allow any other
617 * characters present in the string then the 65 special base64 chars.
619 private static byte[] decode64(String s) {
620 ByteArrayOutputStream bs = new ByteArrayOutputStream((s.length()/4)*3);
621 char[] c = new char[s.length()];
622 s.getChars(0, s.length(), c, 0);
624 // Convert from base64 chars
625 int endchar = -1;
626 for(int j = 0; j < c.length && endchar == -1; j++) {
627 if (c[j] >= 'A' && c[j] <= 'Z') {
628 c[j] -= 'A';
629 } else if (c[j] >= 'a' && c[j] <= 'z') {
630 c[j] = (char) (c[j] + 26 - 'a');
631 } else if (c[j] >= '0' && c[j] <= '9') {
632 c[j] = (char) (c[j] + 52 - '0');
633 } else if (c[j] == '+') {
634 c[j] = 62;
635 } else if (c[j] == '/') {
636 c[j] = 63;
637 } else if (c[j] == '=') {
638 endchar = j;
639 } else {
640 return null; // encoding exception
644 int remaining = endchar == -1 ? c.length : endchar;
645 int i = 0;
646 while (remaining > 0) {
647 // Four input chars (6 bits) are decoded as three bytes as
648 // 000000 001111 111122 222222
650 byte b0 = (byte) (c[i] << 2);
651 if (remaining >= 2) {
652 b0 += (c[i+1] & 0x30) >> 4;
654 bs.write(b0);
656 if (remaining >= 3) {
657 byte b1 = (byte) ((c[i+1] & 0x0F) << 4);
658 b1 += (byte) ((c[i+2] & 0x3C) >> 2);
659 bs.write(b1);
662 if (remaining >= 4) {
663 byte b2 = (byte) ((c[i+2] & 0x03) << 6);
664 b2 += c[i+3];
665 bs.write(b2);
668 i += 4;
669 remaining -= 4;
672 return bs.toByteArray();
676 * Convenience method for getting the given entry as a double.
677 * When the string representation of the requested entry can be decoded
678 * with <code>Double.parseDouble()</code> then that double is returned,
679 * otherwise the given default double value is returned.
681 * @exception IllegalArgumentException if key is larger then 80 characters
682 * @exception IllegalStateException if this node has been removed
683 * @exception NullPointerException if key is null
685 public double getDouble(String key, double defaultVal) {
686 String value = get(key, null);
688 if (value != null) {
689 try {
690 return Double.parseDouble(value);
691 } catch (NumberFormatException nfe) { /* ignore */ }
694 return defaultVal;
698 * Convenience method for getting the given entry as a float.
699 * When the string representation of the requested entry can be decoded
700 * with <code>Float.parseFloat()</code> then that float is returned,
701 * otherwise the given default float value is returned.
703 * @exception IllegalArgumentException if key is larger then 80 characters
704 * @exception IllegalStateException if this node has been removed
705 * @exception NullPointerException if key is null
707 public float getFloat(String key, float defaultVal) {
708 String value = get(key, null);
710 if (value != null) {
711 try {
712 return Float.parseFloat(value);
713 } catch (NumberFormatException nfe) { /* ignore */ }
716 return defaultVal;
720 * Convenience method for getting the given entry as an integer.
721 * When the string representation of the requested entry can be decoded
722 * with <code>Integer.parseInt()</code> then that integer is returned,
723 * otherwise the given default integer value is returned.
725 * @exception IllegalArgumentException if key is larger then 80 characters
726 * @exception IllegalStateException if this node has been removed
727 * @exception NullPointerException if key is null
729 public int getInt(String key, int defaultVal) {
730 String value = get(key, null);
732 if (value != null) {
733 try {
734 return Integer.parseInt(value);
735 } catch (NumberFormatException nfe) { /* ignore */ }
738 return defaultVal;
742 * Convenience method for getting the given entry as a long.
743 * When the string representation of the requested entry can be decoded
744 * with <code>Long.parseLong()</code> then that long is returned,
745 * otherwise the given default long value is returned.
747 * @exception IllegalArgumentException if key is larger then 80 characters
748 * @exception IllegalStateException if this node has been removed
749 * @exception NullPointerException if key is null
751 public long getLong(String key, long defaultVal) {
752 String value = get(key, null);
754 if (value != null) {
755 try {
756 return Long.parseLong(value);
757 } catch (NumberFormatException nfe) { /* ignore */ }
760 return defaultVal;
764 * Sets the value of the given preferences entry for this node.
765 * Key and value cannot be null, the key cannot exceed 80 characters
766 * and the value cannot exceed 8192 characters.
767 * <p>
768 * The result will be immediatly visible in this VM, but may not be
769 * immediatly written to the backing store.
770 * <p>
771 * Checks that key and value are valid, locks this node, and checks that
772 * the node has not been removed. Then it calls <code>putSpi()</code>.
774 * @exception NullPointerException if either key or value are null
775 * @exception IllegalArgumentException if either key or value are to large
776 * @exception IllegalStateException when this node has been removed
778 public void put(String key, String value) {
779 if (key.length() > MAX_KEY_LENGTH
780 || value.length() > MAX_VALUE_LENGTH)
781 throw new IllegalArgumentException("key ("
782 + key.length() + ")"
783 + " or value ("
784 + value.length() + ")"
785 + " to large");
786 synchronized(lock) {
787 if (isRemoved())
788 throw new IllegalStateException("Node removed");
790 putSpi(key, value);
792 // XXX - fire events
798 * Convenience method for setting the given entry as a boolean.
799 * The boolean is converted with <code>Boolean.toString(value)</code>
800 * and then stored in the preference entry as that string.
802 * @exception NullPointerException if key is null
803 * @exception IllegalArgumentException if the key length is to large
804 * @exception IllegalStateException when this node has been removed
806 public void putBoolean(String key, boolean value) {
807 put(key, String.valueOf(value));
808 // XXX - Use when using 1.4 compatible Boolean
809 // put(key, Boolean.toString(value));
813 * Convenience method for setting the given entry as an array of bytes.
814 * The byte array is converted to a Base64 encoded string
815 * and then stored in the preference entry as that string.
816 * <p>
817 * Note that a byte array encoded as a Base64 string will be about 1.3
818 * times larger then the original length of the byte array, which means
819 * that the byte array may not be larger about 6 KB.
821 * @exception NullPointerException if either key or value are null
822 * @exception IllegalArgumentException if either key or value are to large
823 * @exception IllegalStateException when this node has been removed
825 public void putByteArray(String key, byte[] value) {
826 put(key, encode64(value));
830 * Helper method for encoding an array of bytes as a Base64 String.
832 private static String encode64(byte[] b) {
833 StringBuffer sb = new StringBuffer((b.length/3)*4);
835 int i = 0;
836 int remaining = b.length;
837 char c[] = new char[4];
838 while (remaining > 0) {
839 // Three input bytes are encoded as four chars (6 bits) as
840 // 00000011 11112222 22333333
842 c[0] = (char) ((b[i] & 0xFC) >> 2);
843 c[1] = (char) ((b[i] & 0x03) << 4);
844 if (remaining >= 2) {
845 c[1] += (char) ((b[i+1] & 0xF0) >> 4);
846 c[2] = (char) ((b[i+1] & 0x0F) << 2);
847 if (remaining >= 3) {
848 c[2] += (char) ((b[i+2] & 0xC0) >> 6);
849 c[3] = (char) (b[i+2] & 0x3F);
850 } else {
851 c[3] = 64;
853 } else {
854 c[2] = 64;
855 c[3] = 64;
858 // Convert to base64 chars
859 for(int j = 0; j < 4; j++) {
860 if (c[j] < 26) {
861 c[j] += 'A';
862 } else if (c[j] < 52) {
863 c[j] = (char) (c[j] - 26 + 'a');
864 } else if (c[j] < 62) {
865 c[j] = (char) (c[j] - 52 + '0');
866 } else if (c[j] == 62) {
867 c[j] = '+';
868 } else if (c[j] == 63) {
869 c[j] = '/';
870 } else {
871 c[j] = '=';
875 sb.append(c);
876 i += 3;
877 remaining -= 3;
880 return sb.toString();
884 * Convenience method for setting the given entry as a double.
885 * The double is converted with <code>Double.toString(double)</code>
886 * and then stored in the preference entry as that string.
888 * @exception NullPointerException if the key is null
889 * @exception IllegalArgumentException if the key length is to large
890 * @exception IllegalStateException when this node has been removed
892 public void putDouble(String key, double value) {
893 put(key, Double.toString(value));
897 * Convenience method for setting the given entry as a float.
898 * The float is converted with <code>Float.toString(float)</code>
899 * and then stored in the preference entry as that string.
901 * @exception NullPointerException if the key is null
902 * @exception IllegalArgumentException if the key length is to large
903 * @exception IllegalStateException when this node has been removed
905 public void putFloat(String key, float value) {
906 put(key, Float.toString(value));
910 * Convenience method for setting the given entry as an integer.
911 * The integer is converted with <code>Integer.toString(int)</code>
912 * and then stored in the preference entry as that string.
914 * @exception NullPointerException if the key is null
915 * @exception IllegalArgumentException if the key length is to large
916 * @exception IllegalStateException when this node has been removed
918 public void putInt(String key, int value) {
919 put(key, Integer.toString(value));
923 * Convenience method for setting the given entry as a long.
924 * The long is converted with <code>Long.toString(long)</code>
925 * and then stored in the preference entry as that string.
927 * @exception NullPointerException if the key is null
928 * @exception IllegalArgumentException if the key length is to large
929 * @exception IllegalStateException when this node has been removed
931 public void putLong(String key, long value) {
932 put(key, Long.toString(value));
936 * Removes the preferences entry from this preferences node.
937 * <p>
938 * The result will be immediatly visible in this VM, but may not be
939 * immediatly written to the backing store.
940 * <p>
941 * This implementation checks that the key is not larger then 80
942 * characters, gets the lock of this node, checks that the node has
943 * not been removed and calls <code>removeSpi</code> with the given key.
945 * @exception NullPointerException if the key is null
946 * @exception IllegalArgumentException if the key length is to large
947 * @exception IllegalStateException when this node has been removed
949 public void remove(String key) {
950 if (key.length() > MAX_KEY_LENGTH)
951 throw new IllegalArgumentException(key);
953 synchronized(lock) {
954 if (isRemoved())
955 throw new IllegalStateException("Node removed");
957 removeSpi(key);
962 * Removes all entries from this preferences node. May need access to the
963 * backing store to get and clear all entries.
964 * <p>
965 * The result will be immediatly visible in this VM, but may not be
966 * immediatly written to the backing store.
967 * <p>
968 * This implementation locks this node, checks that the node has not been
969 * removed and calls <code>keys()</code> to get a complete array of keys
970 * for this node. For every key found <code>removeSpi()</code> is called.
972 * @exception BackingStoreException when the backing store cannot be
973 * reached
974 * @exception IllegalStateException if this node has been removed
976 public void clear() throws BackingStoreException {
977 synchronized(lock) {
978 if (isRemoved())
979 throw new IllegalStateException("Node Removed");
981 String[] keys = keys();
982 for (int i = 0; i < keys.length; i++) {
983 removeSpi(keys[i]);
989 * Writes all preference changes on this and any subnode that have not
990 * yet been written to the backing store. This has no effect on the
991 * preference entries in this VM, but it makes sure that all changes
992 * are visible to other programs (other VMs might need to call the
993 * <code>sync()</code> method to actually see the changes to the backing
994 * store.
995 * <p>
996 * Locks this node, calls the <code>flushSpi()</code> method, gets all
997 * the (cached - already existing in this VM) subnodes and then calls
998 * <code>flushSpi()</code> on every subnode with this node unlocked and
999 * only that particular subnode locked.
1001 * @exception BackingStoreException when the backing store cannot be
1002 * reached
1004 public void flush() throws BackingStoreException {
1005 flushNode(false);
1009 * Writes and reads all preference changes to and from this and any
1010 * subnodes. This makes sure that all local changes are written to the
1011 * backing store and that all changes to the backing store are visible
1012 * in this preference node (and all subnodes).
1013 * <p>
1014 * Checks that this node is not removed, locks this node, calls the
1015 * <code>syncSpi()</code> method, gets all the subnodes and then calls
1016 * <code>syncSpi()</code> on every subnode with this node unlocked and
1017 * only that particular subnode locked.
1019 * @exception BackingStoreException when the backing store cannot be
1020 * reached
1021 * @exception IllegalStateException if this node has been removed
1023 public void sync() throws BackingStoreException {
1024 flushNode(true);
1029 * Private helper method that locks this node and calls either
1030 * <code>flushSpi()</code> if <code>sync</code> is false, or
1031 * <code>flushSpi()</code> if <code>sync</code> is true. Then it gets all
1032 * the currently cached subnodes. For every subnode it calls this method
1033 * recursively with this node no longer locked.
1034 * <p>
1035 * Called by either <code>flush()</code> or <code>sync()</code>
1037 private void flushNode(boolean sync) throws BackingStoreException {
1038 String[] keys = null;
1039 synchronized(lock) {
1040 if (sync) {
1041 syncSpi();
1042 } else {
1043 flushSpi();
1045 keys = (String[]) childCache.keySet().toArray();
1048 if (keys != null) {
1049 for (int i = 0; i < keys.length; i++) {
1050 // Have to lock this node again to access the childCache
1051 AbstractPreferences subNode;
1052 synchronized(this) {
1053 subNode = (AbstractPreferences) childCache.get(keys[i]);
1056 // The child could already have been removed from the cache
1057 if (subNode != null) {
1058 subNode.flushNode(sync);
1065 * Removes this and all subnodes from the backing store and clears all
1066 * entries. After removal this instance will not be useable (except for
1067 * a few methods that don't throw a <code>InvalidStateException</code>),
1068 * even when a new node with the same path name is created this instance
1069 * will not be usable again.
1070 * <p>
1071 * Checks that this is not a root node. If not it locks the parent node,
1072 * then locks this node and checks that the node has not yet been removed.
1073 * Then it makes sure that all subnodes of this node are in the child cache,
1074 * by calling <code>childSpi()</code> on any children not yet in the cache.
1075 * Then for all children it locks the subnode and removes it. After all
1076 * subnodes have been purged the child cache is cleared, this nodes removed
1077 * flag is set and any listeners are called. Finally this node is removed
1078 * from the child cache of the parent node.
1080 * @exception BackingStoreException when the backing store cannot be
1081 * reached
1082 * @exception IllegalStateException if this node has already been removed
1083 * @exception UnsupportedOperationException if this is a root node
1085 public void removeNode() throws BackingStoreException {
1086 // Check if it is a root node
1087 if (parent == null)
1088 throw new UnsupportedOperationException("Cannot remove root node");
1090 synchronized(parent) {
1091 synchronized(this) {
1092 if (isRemoved())
1093 throw new IllegalStateException("Node Removed");
1095 purge();
1097 parent.childCache.remove(name);
1102 * Private helper method used to completely remove this node.
1103 * Called by <code>removeNode</code> with the parent node and this node
1104 * locked.
1105 * <p>
1106 * Makes sure that all subnodes of this node are in the child cache,
1107 * by calling <code>childSpi()</code> on any children not yet in the
1108 * cache. Then for all children it locks the subnode and calls this method
1109 * on that node. After all subnodes have been purged the child cache is
1110 * cleared, this nodes removed flag is set and any listeners are called.
1112 private void purge() throws BackingStoreException
1114 // Make sure all children have an AbstractPreferences node in cache
1115 String children[] = childrenNamesSpi();
1116 for (int i = 0; i < children.length; i++) {
1117 if (childCache.get(children[i]) == null)
1118 childCache.put(children[i], childSpi(children[i]));
1121 // purge all children
1122 Iterator i = childCache.values().iterator();
1123 while (i.hasNext()) {
1124 AbstractPreferences node = (AbstractPreferences) i.next();
1125 synchronized(node) {
1126 node.purge();
1130 // Cache is empty now
1131 childCache.clear();
1133 // remove this node
1134 removeNodeSpi();
1135 removed = true;
1137 // XXX - check for listeners
1140 // listener methods
1143 * XXX
1145 public void addNodeChangeListener(NodeChangeListener listener) {
1146 // XXX
1149 public void addPreferenceChangeListener(PreferenceChangeListener listener) {
1150 // XXX
1153 public void removeNodeChangeListener(NodeChangeListener listener) {
1154 // XXX
1157 public void removePreferenceChangeListener
1158 (PreferenceChangeListener listener)
1160 // XXX
1163 // abstract spi methods
1166 * Returns the names of the sub nodes of this preference node.
1167 * This method only has to return any not yet cached child names,
1168 * but may return all names if that is easier. It must not return
1169 * null when there are no children, it has to return an empty array
1170 * in that case. Since this method must consult the backing store to
1171 * get all the sub node names it may throw a BackingStoreException.
1172 * <p>
1173 * Called by <code>childrenNames()</code> with this node locked.
1175 protected abstract String[] childrenNamesSpi() throws BackingStoreException;
1178 * Returns a child note with the given name.
1179 * This method is called by the <code>node()</code> method (indirectly
1180 * through the <code>getNode()</code> helper method) with this node locked
1181 * if a sub node with this name does not already exist in the child cache.
1182 * If the child node did not aleady exist in the backing store the boolean
1183 * field <code>newNode</code> of the returned node should be set.
1184 * <p>
1185 * Note that this method should even return a non-null child node if the
1186 * backing store is not available since it may not throw a
1187 * <code>BackingStoreException</code>.
1189 protected abstract AbstractPreferences childSpi(String name);
1192 * Returns an (possibly empty) array with all the keys of the preference
1193 * entries of this node.
1194 * <p>
1195 * Called by <code>keys()</code> with this node locked if this node has
1196 * not been removed. May throw an exception when the backing store cannot
1197 * be accessed.
1199 * @exception BackingStoreException when the backing store cannot be
1200 * reached
1202 protected abstract String[] keysSpi() throws BackingStoreException;
1205 * Returns the value associated with the key in this preferences node or
1206 * null when the key does not exist in this preferences node.
1207 * <p>
1208 * Called by <code>key()</code> with this node locked after checking that
1209 * key is valid, not null and that the node has not been removed.
1210 * <code>key()</code> will catch any exceptions that this method throws.
1212 protected abstract String getSpi(String key);
1215 * Sets the value of the given preferences entry for this node.
1216 * The implementation is not required to propagate the change to the
1217 * backing store immediatly. It may not throw an exception when it tries
1218 * to write to the backing store and that operation fails, the failure
1219 * should be registered so a later invocation of <code>flush()</code>
1220 * or <code>sync()</code> can signal the failure.
1221 * <p>
1222 * Called by <code>put()</code> with this node locked after checking that
1223 * key and value are valid and non-null.
1225 protected abstract void putSpi(String key, String value);
1228 * Removes the given key entry from this preferences node.
1229 * The implementation is not required to propagate the change to the
1230 * backing store immediatly. It may not throw an exception when it tries
1231 * to write to the backing store and that operation fails, the failure
1232 * should be registered so a later invocation of <code>flush()</code>
1233 * or <code>sync()</code> can signal the failure.
1234 * <p>
1235 * Called by <code>remove()</code> with this node locked after checking
1236 * that the key is valid and non-null.
1238 protected abstract void removeSpi(String key);
1241 * Writes all entries of this preferences node that have not yet been
1242 * written to the backing store and possibly creates this node in the
1243 * backing store, if it does not yet exist. Should only write changes to
1244 * this node and not write changes to any subnodes.
1245 * Note that the node can be already removed in this VM. To check if
1246 * that is the case the implementation can call <code>isRemoved()</code>.
1247 * <p>
1248 * Called (indirectly) by <code>flush()</code> with this node locked.
1250 protected abstract void flushSpi() throws BackingStoreException;
1253 * Writes all entries of this preferences node that have not yet been
1254 * written to the backing store and reads any entries that have changed
1255 * in the backing store but that are not yet visible in this VM.
1256 * Should only sync this node and not change any of the subnodes.
1257 * Note that the node can be already removed in this VM. To check if
1258 * that is the case the implementation can call <code>isRemoved()</code>.
1259 * <p>
1260 * Called (indirectly) by <code>sync()</code> with this node locked.
1262 protected abstract void syncSpi() throws BackingStoreException;
1265 * Clears this node from this VM and removes it from the backing store.
1266 * After this method has been called the node is marked as removed.
1267 * <p>
1268 * Called (indirectly) by <code>removeNode()</code> with this node locked
1269 * after all the sub nodes of this node have already been removed.
1271 protected abstract void removeNodeSpi() throws BackingStoreException;