| GNU Classpath (0.97.2) | |
| Frames | No Frames |
1: /* AbstractPreferences -- Partial implementation of a Preference node 2: Copyright (C) 2001, 2003, 2004, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 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. 10: 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. 15: 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., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 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. 25: 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. */ 37: 38: 39: package java.util.prefs; 40: 41: import gnu.java.util.prefs.EventDispatcher; 42: import gnu.java.util.prefs.NodeWriter; 43: 44: import java.io.ByteArrayOutputStream; 45: import java.io.IOException; 46: import java.io.OutputStream; 47: import java.util.ArrayList; 48: import java.util.Collection; 49: import java.util.HashMap; 50: import java.util.Iterator; 51: import java.util.TreeSet; 52: 53: /** 54: * Partial implementation of a Preference node. 55: * 56: * @since 1.4 57: * @author Mark Wielaard (mark@klomp.org) 58: */ 59: public abstract class AbstractPreferences extends Preferences { 60: 61: // protected fields 62: 63: /** 64: * Object used to lock this preference node. Any thread only locks nodes 65: * downwards when it has the lock on the current node. No method should 66: * synchronize on the lock of any of its parent nodes while holding the 67: * lock on the current node. 68: */ 69: protected final Object lock = new Object(); 70: 71: /** 72: * Set to true in the contructor if the node did not exist in the backing 73: * store when this preference node object was created. Should be set in 74: * the constructor of a subclass. Defaults to false. Used to fire node 75: * changed events. 76: */ 77: protected boolean newNode = false; 78: 79: // private fields 80: 81: /** 82: * The parent preferences node or null when this is the root node. 83: */ 84: private final AbstractPreferences parent; 85: 86: /** 87: * The name of this node. 88: * Only when this is a root node (parent == null) the name is empty. 89: * It has a maximum of 80 characters and cannot contain any '/' characters. 90: */ 91: private final String name; 92: 93: /** True when this node has been remove, false otherwise. */ 94: private boolean removed = false; 95: 96: /** 97: * Holds all the child names and nodes of this node that have been 98: * accessed by earlier <code>getChild()</code> or <code>childSpi()</code> 99: * invocations and that have not been removed. 100: */ 101: private HashMap<String, AbstractPreferences> childCache 102: = new HashMap<String, AbstractPreferences>(); 103: 104: /** 105: * A list of all the registered NodeChangeListener objects. 106: */ 107: private ArrayList<NodeChangeListener> nodeListeners; 108: 109: /** 110: * A list of all the registered PreferenceChangeListener objects. 111: */ 112: private ArrayList<PreferenceChangeListener> preferenceListeners; 113: 114: // constructor 115: 116: /** 117: * Creates a new AbstractPreferences node with the given parent and name. 118: * 119: * @param parent the parent of this node or null when this is the root node 120: * @param name the name of this node, can not be null, only 80 characters 121: * maximum, must be empty when parent is null and cannot 122: * contain any '/' characters 123: * @exception IllegalArgumentException when name is null, greater then 80 124: * characters, not the empty string but parent is null or 125: * contains a '/' character 126: */ 127: protected AbstractPreferences(AbstractPreferences parent, String name) { 128: if ( (name == null) // name should be given 129: || (name.length() > MAX_NAME_LENGTH) // 80 characters max 130: || (parent == null && name.length() != 0) // root has no name 131: || (parent != null && name.length() == 0) // all other nodes do 132: || (name.indexOf('/') != -1)) // must not contain '/' 133: throw new IllegalArgumentException("Illegal name argument '" 134: + name 135: + "' (parent is " 136: + (parent == null ? "" : "not ") 137: + "null)"); 138: this.parent = parent; 139: this.name = name; 140: } 141: 142: // identification methods 143: 144: /** 145: * Returns the absolute path name of this preference node. 146: * The absolute path name of a node is the path name of its parent node 147: * plus a '/' plus its own name. If the node is the root node and has no 148: * parent then its path name is "" and its absolute path name is "/". 149: */ 150: public String absolutePath() { 151: if (parent == null) 152: return "/"; 153: else 154: return parent.path() + '/' + name; 155: } 156: 157: /** 158: * Private helper method for absolutePath. Returns the empty string for a 159: * root node and otherwise the parentPath of its parent plus a '/'. 160: */ 161: private String path() { 162: if (parent == null) 163: return ""; 164: else 165: return parent.path() + '/' + name; 166: } 167: 168: /** 169: * Returns true if this node comes from the user preferences tree, false 170: * if it comes from the system preferences tree. 171: */ 172: public boolean isUserNode() { 173: AbstractPreferences root = this; 174: while (root.parent != null) 175: root = root.parent; 176: return root == Preferences.userRoot(); 177: } 178: 179: /** 180: * Returns the name of this preferences node. The name of the node cannot 181: * be null, can be mostly 80 characters and cannot contain any '/' 182: * characters. The root node has as name "". 183: */ 184: public String name() { 185: return name; 186: } 187: 188: /** 189: * Returns the String given by 190: * <code> 191: * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath() 192: * </code> 193: */ 194: public String toString() { 195: return (isUserNode() ? "User":"System") 196: + " Preference Node: " 197: + absolutePath(); 198: } 199: 200: /** 201: * Returns all known unremoved children of this node. 202: * 203: * @return All known unremoved children of this node 204: */ 205: protected final AbstractPreferences[] cachedChildren() 206: { 207: Collection<AbstractPreferences> vals = childCache.values(); 208: return vals.toArray(new AbstractPreferences[vals.size()]); 209: } 210: 211: /** 212: * Returns all the direct sub nodes of this preferences node. 213: * Needs access to the backing store to give a meaningfull answer. 214: * <p> 215: * This implementation locks this node, checks if the node has not yet 216: * been removed and throws an <code>IllegalStateException</code> when it 217: * has been. Then it creates a new <code>TreeSet</code> and adds any 218: * already cached child nodes names. To get any uncached names it calls 219: * <code>childrenNamesSpi()</code> and adds the result to the set. Finally 220: * it calls <code>toArray()</code> on the created set. When the call to 221: * <code>childrenNamesSpi</code> thows an <code>BackingStoreException</code> 222: * this method will not catch that exception but propagate the exception 223: * to the caller. 224: * 225: * @exception BackingStoreException when the backing store cannot be 226: * reached 227: * @exception IllegalStateException when this node has been removed 228: */ 229: public String[] childrenNames() throws BackingStoreException { 230: synchronized(lock) { 231: if (isRemoved()) 232: throw new IllegalStateException("Node removed"); 233: 234: TreeSet<String> childrenNames = new TreeSet<String>(); 235: 236: // First get all cached node names 237: childrenNames.addAll(childCache.keySet()); 238: 239: // Then add any others 240: String names[] = childrenNamesSpi(); 241: for (int i = 0; i < names.length; i++) { 242: childrenNames.add(names[i]); 243: } 244: 245: // And return the array of names 246: String[] children = new String[childrenNames.size()]; 247: childrenNames.toArray(children); 248: return children; 249: 250: } 251: } 252: 253: /** 254: * Returns a sub node of this preferences node if the given path is 255: * relative (does not start with a '/') or a sub node of the root 256: * if the path is absolute (does start with a '/'). 257: * <p> 258: * This method first locks this node and checks if the node has not been 259: * removed, if it has been removed it throws an exception. Then if the 260: * path is relative (does not start with a '/') it checks if the path is 261: * legal (does not end with a '/' and has no consecutive '/' characters). 262: * Then it recursively gets a name from the path, gets the child node 263: * from the child-cache of this node or calls the <code>childSpi()</code> 264: * method to create a new child sub node. This is done recursively on the 265: * newly created sub node with the rest of the path till the path is empty. 266: * If the path is absolute (starts with a '/') the lock on this node is 267: * droped and this method is called on the root of the preferences tree 268: * with as argument the complete path minus the first '/'. 269: * 270: * @exception IllegalStateException if this node has been removed 271: * @exception IllegalArgumentException if the path contains two or more 272: * consecutive '/' characters, ends with a '/' charactor and is not the 273: * string "/" (indicating the root node) or any name on the path is more 274: * than 80 characters long 275: */ 276: public Preferences node(String path) { 277: synchronized(lock) { 278: if (isRemoved()) 279: throw new IllegalStateException("Node removed"); 280: 281: // Is it a relative path? 282: if (!path.startsWith("/")) { 283: 284: // Check if it is a valid path 285: if (path.indexOf("//") != -1 || path.endsWith("/")) 286: throw new IllegalArgumentException(path); 287: 288: return getNode(path); 289: } 290: } 291: 292: // path started with a '/' so it is absolute 293: // we drop the lock and start from the root (omitting the first '/') 294: Preferences root = isUserNode() ? userRoot() : systemRoot(); 295: return root.node(path.substring(1)); 296: 297: } 298: 299: /** 300: * Private helper method for <code>node()</code>. Called with this node 301: * locked. Returns this node when path is the empty string, if it is not 302: * empty the next node name is taken from the path (all chars till the 303: * next '/' or end of path string) and the node is either taken from the 304: * child-cache of this node or the <code>childSpi()</code> method is called 305: * on this node with the name as argument. Then this method is called 306: * recursively on the just constructed child node with the rest of the 307: * path. 308: * 309: * @param path should not end with a '/' character and should not contain 310: * consecutive '/' characters 311: * @exception IllegalArgumentException if path begins with a name that is 312: * larger then 80 characters. 313: */ 314: private Preferences getNode(String path) { 315: // if mark is dom then goto end 316: 317: // Empty String "" indicates this node 318: if (path.length() == 0) 319: return this; 320: 321: // Calculate child name and rest of path 322: String childName; 323: String childPath; 324: int nextSlash = path.indexOf('/'); 325: if (nextSlash == -1) { 326: childName = path; 327: childPath = ""; 328: } else { 329: childName = path.substring(0, nextSlash); 330: childPath = path.substring(nextSlash+1); 331: } 332: 333: // Get the child node 334: AbstractPreferences child; 335: child = (AbstractPreferences)childCache.get(childName); 336: if (child == null) { 337: 338: if (childName.length() > MAX_NAME_LENGTH) 339: throw new IllegalArgumentException(childName); 340: 341: // Not in childCache yet so create a new sub node 342: child = childSpi(childName); 343: childCache.put(childName, child); 344: if (child.newNode && nodeListeners != null) 345: fire(new NodeChangeEvent(this, child), true); 346: } 347: 348: // Lock the child and go down 349: synchronized(child.lock) { 350: return child.getNode(childPath); 351: } 352: } 353: 354: /** 355: * Returns true if the node that the path points to exists in memory or 356: * in the backing store. Otherwise it returns false or an exception is 357: * thrown. When this node is removed the only valid parameter is the 358: * empty string (indicating this node), the return value in that case 359: * will be false. 360: * 361: * @exception BackingStoreException when the backing store cannot be 362: * reached 363: * @exception IllegalStateException if this node has been removed 364: * and the path is not the empty string (indicating this node) 365: * @exception IllegalArgumentException if the path contains two or more 366: * consecutive '/' characters, ends with a '/' charactor and is not the 367: * string "/" (indicating the root node) or any name on the path is more 368: * then 80 characters long 369: */ 370: public boolean nodeExists(String path) throws BackingStoreException { 371: synchronized(lock) { 372: if (isRemoved() && path.length() != 0) 373: throw new IllegalStateException("Node removed"); 374: 375: // Is it a relative path? 376: if (!path.startsWith("/")) { 377: 378: // Check if it is a valid path 379: if (path.indexOf("//") != -1 || path.endsWith("/")) 380: throw new IllegalArgumentException(path); 381: 382: return existsNode(path); 383: } 384: } 385: 386: // path started with a '/' so it is absolute 387: // we drop the lock and start from the root (omitting the first '/') 388: Preferences root = isUserNode() ? userRoot() : systemRoot(); 389: return root.nodeExists(path.substring(1)); 390: 391: } 392: 393: private boolean existsNode(String path) throws BackingStoreException { 394: 395: // Empty String "" indicates this node 396: if (path.length() == 0) 397: return(!isRemoved()); 398: 399: // Calculate child name and rest of path 400: String childName; 401: String childPath; 402: int nextSlash = path.indexOf('/'); 403: if (nextSlash == -1) { 404: childName = path; 405: childPath = ""; 406: } else { 407: childName = path.substring(0, nextSlash); 408: childPath = path.substring(nextSlash+1); 409: } 410: 411: // Get the child node 412: AbstractPreferences child; 413: child = (AbstractPreferences)childCache.get(childName); 414: if (child == null) { 415: 416: if (childName.length() > MAX_NAME_LENGTH) 417: throw new IllegalArgumentException(childName); 418: 419: // Not in childCache yet so create a new sub node 420: child = getChild(childName); 421: 422: if (child == null) 423: return false; 424: 425: childCache.put(childName, child); 426: } 427: 428: // Lock the child and go down 429: synchronized(child.lock) { 430: return child.existsNode(childPath); 431: } 432: } 433: 434: /** 435: * Returns the child sub node if it exists in the backing store or null 436: * if it does not exist. Called (indirectly) by <code>nodeExists()</code> 437: * when a child node name can not be found in the cache. 438: * <p> 439: * Gets the lock on this node, calls <code>childrenNamesSpi()</code> to 440: * get an array of all (possibly uncached) children and compares the 441: * given name with the names in the array. If the name is found in the 442: * array <code>childSpi()</code> is called to get an instance, otherwise 443: * null is returned. 444: * 445: * @exception BackingStoreException when the backing store cannot be 446: * reached 447: */ 448: protected AbstractPreferences getChild(String name) 449: throws BackingStoreException 450: { 451: synchronized(lock) { 452: // Get all the names (not yet in the cache) 453: String[] names = childrenNamesSpi(); 454: for (int i=0; i < names.length; i++) 455: if (name.equals(names[i])) 456: return childSpi(name); 457: 458: // No child with that name found 459: return null; 460: } 461: } 462: 463: /** 464: * Returns true if this node has been removed with the 465: * <code>removeNode()</code> method, false otherwise. 466: * <p> 467: * Gets the lock on this node and then returns a boolean field set by 468: * <code>removeNode</code> methods. 469: */ 470: protected boolean isRemoved() { 471: synchronized(lock) { 472: return removed; 473: } 474: } 475: 476: /** 477: * Returns the parent preferences node of this node or null if this is 478: * the root of the preferences tree. 479: * <p> 480: * Gets the lock on this node, checks that the node has not been removed 481: * and returns the parent given to the constructor. 482: * 483: * @exception IllegalStateException if this node has been removed 484: */ 485: public Preferences parent() { 486: synchronized(lock) { 487: if (isRemoved()) 488: throw new IllegalStateException("Node removed"); 489: 490: return parent; 491: } 492: } 493: 494: // export methods 495: 496: // Inherit javadoc. 497: public void exportNode(OutputStream os) 498: throws BackingStoreException, 499: IOException 500: { 501: NodeWriter nodeWriter = new NodeWriter(this, os); 502: nodeWriter.writePrefs(); 503: } 504: 505: // Inherit javadoc. 506: public void exportSubtree(OutputStream os) 507: throws BackingStoreException, 508: IOException 509: { 510: NodeWriter nodeWriter = new NodeWriter(this, os); 511: nodeWriter.writePrefsTree(); 512: } 513: 514: // preference entry manipulation methods 515: 516: /** 517: * Returns an (possibly empty) array with all the keys of the preference 518: * entries of this node. 519: * <p> 520: * This method locks this node and checks if the node has not been 521: * removed, if it has been removed it throws an exception, then it returns 522: * the result of calling <code>keysSpi()</code>. 523: * 524: * @exception BackingStoreException when the backing store cannot be 525: * reached 526: * @exception IllegalStateException if this node has been removed 527: */ 528: public String[] keys() throws BackingStoreException { 529: synchronized(lock) { 530: if (isRemoved()) 531: throw new IllegalStateException("Node removed"); 532: 533: return keysSpi(); 534: } 535: } 536: 537: 538: /** 539: * Returns the value associated with the key in this preferences node. If 540: * the default value of the key cannot be found in the preferences node 541: * entries or something goes wrong with the backing store the supplied 542: * default value is returned. 543: * <p> 544: * Checks that key is not null and not larger then 80 characters, 545: * locks this node, and checks that the node has not been removed. 546: * Then it calls <code>keySpi()</code> and returns 547: * the result of that method or the given default value if it returned 548: * null or throwed an exception. 549: * 550: * @exception IllegalArgumentException if key is larger then 80 characters 551: * @exception IllegalStateException if this node has been removed 552: * @exception NullPointerException if key is null 553: */ 554: public String get(String key, String defaultVal) { 555: if (key.length() > MAX_KEY_LENGTH) 556: throw new IllegalArgumentException(key); 557: 558: synchronized(lock) { 559: if (isRemoved()) 560: throw new IllegalStateException("Node removed"); 561: 562: String value; 563: try { 564: value = getSpi(key); 565: } catch (ThreadDeath death) { 566: throw death; 567: } catch (Throwable t) { 568: value = null; 569: } 570: 571: if (value != null) { 572: return value; 573: } else { 574: return defaultVal; 575: } 576: } 577: } 578: 579: /** 580: * Convenience method for getting the given entry as a boolean. 581: * When the string representation of the requested entry is either 582: * "true" or "false" (ignoring case) then that value is returned, 583: * otherwise the given default boolean value is returned. 584: * 585: * @exception IllegalArgumentException if key is larger then 80 characters 586: * @exception IllegalStateException if this node has been removed 587: * @exception NullPointerException if key is null 588: */ 589: public boolean getBoolean(String key, boolean defaultVal) { 590: String value = get(key, null); 591: 592: if ("true".equalsIgnoreCase(value)) 593: return true; 594: 595: if ("false".equalsIgnoreCase(value)) 596: return false; 597: 598: return defaultVal; 599: } 600: 601: /** 602: * Convenience method for getting the given entry as a byte array. 603: * When the string representation of the requested entry is a valid 604: * Base64 encoded string (without any other characters, such as newlines) 605: * then the decoded Base64 string is returned as byte array, 606: * otherwise the given default byte array value is returned. 607: * 608: * @exception IllegalArgumentException if key is larger then 80 characters 609: * @exception IllegalStateException if this node has been removed 610: * @exception NullPointerException if key is null 611: */ 612: public byte[] getByteArray(String key, byte[] defaultVal) { 613: String value = get(key, null); 614: 615: byte[] b = null; 616: if (value != null) { 617: b = decode64(value); 618: } 619: 620: if (b != null) 621: return b; 622: else 623: return defaultVal; 624: } 625: 626: /** 627: * Helper method for decoding a Base64 string as an byte array. 628: * Returns null on encoding error. This method does not allow any other 629: * characters present in the string then the 65 special base64 chars. 630: */ 631: private static byte[] decode64(String s) { 632: ByteArrayOutputStream bs = new ByteArrayOutputStream((s.length()/4)*3); 633: char[] c = new char[s.length()]; 634: s.getChars(0, s.length(), c, 0); 635: 636: // Convert from base64 chars 637: int endchar = -1; 638: for(int j = 0; j < c.length && endchar == -1; j++) { 639: if (c[j] >= 'A' && c[j] <= 'Z') { 640: c[j] -= 'A'; 641: } else if (c[j] >= 'a' && c[j] <= 'z') { 642: c[j] = (char) (c[j] + 26 - 'a'); 643: } else if (c[j] >= '0' && c[j] <= '9') { 644: c[j] = (char) (c[j] + 52 - '0'); 645: } else if (c[j] == '+') { 646: c[j] = 62; 647: } else if (c[j] == '/') { 648: c[j] = 63; 649: } else if (c[j] == '=') { 650: endchar = j; 651: } else { 652: return null; // encoding exception 653: } 654: } 655: 656: int remaining = endchar == -1 ? c.length : endchar; 657: int i = 0; 658: while (remaining > 0) { 659: // Four input chars (6 bits) are decoded as three bytes as 660: // 000000 001111 111122 222222 661: 662: byte b0 = (byte) (c[i] << 2); 663: if (remaining >= 2) { 664: b0 += (c[i+1] & 0x30) >> 4; 665: } 666: bs.write(b0); 667: 668: if (remaining >= 3) { 669: byte b1 = (byte) ((c[i+1] & 0x0F) << 4); 670: b1 += (byte) ((c[i+2] & 0x3C) >> 2); 671: bs.write(b1); 672: } 673: 674: if (remaining >= 4) { 675: byte b2 = (byte) ((c[i+2] & 0x03) << 6); 676: b2 += c[i+3]; 677: bs.write(b2); 678: } 679: 680: i += 4; 681: remaining -= 4; 682: } 683: 684: return bs.toByteArray(); 685: } 686: 687: /** 688: * Convenience method for getting the given entry as a double. 689: * When the string representation of the requested entry can be decoded 690: * with <code>Double.parseDouble()</code> then that double is returned, 691: * otherwise the given default double value is returned. 692: * 693: * @exception IllegalArgumentException if key is larger then 80 characters 694: * @exception IllegalStateException if this node has been removed 695: * @exception NullPointerException if key is null 696: */ 697: public double getDouble(String key, double defaultVal) { 698: String value = get(key, null); 699: 700: if (value != null) { 701: try { 702: return Double.parseDouble(value); 703: } catch (NumberFormatException nfe) { /* ignore */ } 704: } 705: 706: return defaultVal; 707: } 708: 709: /** 710: * Convenience method for getting the given entry as a float. 711: * When the string representation of the requested entry can be decoded 712: * with <code>Float.parseFloat()</code> then that float is returned, 713: * otherwise the given default float value is returned. 714: * 715: * @exception IllegalArgumentException if key is larger then 80 characters 716: * @exception IllegalStateException if this node has been removed 717: * @exception NullPointerException if key is null 718: */ 719: public float getFloat(String key, float defaultVal) { 720: String value = get(key, null); 721: 722: if (value != null) { 723: try { 724: return Float.parseFloat(value); 725: } catch (NumberFormatException nfe) { /* ignore */ } 726: } 727: 728: return defaultVal; 729: } 730: 731: /** 732: * Convenience method for getting the given entry as an integer. 733: * When the string representation of the requested entry can be decoded 734: * with <code>Integer.parseInt()</code> then that integer is returned, 735: * otherwise the given default integer value is returned. 736: * 737: * @exception IllegalArgumentException if key is larger then 80 characters 738: * @exception IllegalStateException if this node has been removed 739: * @exception NullPointerException if key is null 740: */ 741: public int getInt(String key, int defaultVal) { 742: String value = get(key, null); 743: 744: if (value != null) { 745: try { 746: return Integer.parseInt(value); 747: } catch (NumberFormatException nfe) { /* ignore */ } 748: } 749: 750: return defaultVal; 751: } 752: 753: /** 754: * Convenience method for getting the given entry as a long. 755: * When the string representation of the requested entry can be decoded 756: * with <code>Long.parseLong()</code> then that long is returned, 757: * otherwise the given default long value is returned. 758: * 759: * @exception IllegalArgumentException if key is larger then 80 characters 760: * @exception IllegalStateException if this node has been removed 761: * @exception NullPointerException if key is null 762: */ 763: public long getLong(String key, long defaultVal) { 764: String value = get(key, null); 765: 766: if (value != null) { 767: try { 768: return Long.parseLong(value); 769: } catch (NumberFormatException nfe) { /* ignore */ } 770: } 771: 772: return defaultVal; 773: } 774: 775: /** 776: * Sets the value of the given preferences entry for this node. 777: * Key and value cannot be null, the key cannot exceed 80 characters 778: * and the value cannot exceed 8192 characters. 779: * <p> 780: * The result will be immediately visible in this VM, but may not be 781: * immediately written to the backing store. 782: * <p> 783: * Checks that key and value are valid, locks this node, and checks that 784: * the node has not been removed. Then it calls <code>putSpi()</code>. 785: * 786: * @exception NullPointerException if either key or value are null 787: * @exception IllegalArgumentException if either key or value are to large 788: * @exception IllegalStateException when this node has been removed 789: */ 790: public void put(String key, String value) { 791: if (key.length() > MAX_KEY_LENGTH 792: || value.length() > MAX_VALUE_LENGTH) 793: throw new IllegalArgumentException("key (" 794: + key.length() + ")" 795: + " or value (" 796: + value.length() + ")" 797: + " to large"); 798: synchronized(lock) { 799: if (isRemoved()) 800: throw new IllegalStateException("Node removed"); 801: 802: putSpi(key, value); 803: 804: if (preferenceListeners != null) 805: fire(new PreferenceChangeEvent(this, key, value)); 806: } 807: 808: } 809: 810: /** 811: * Convenience method for setting the given entry as a boolean. 812: * The boolean is converted with <code>Boolean.toString(value)</code> 813: * and then stored in the preference entry as that string. 814: * 815: * @exception NullPointerException if key is null 816: * @exception IllegalArgumentException if the key length is to large 817: * @exception IllegalStateException when this node has been removed 818: */ 819: public void putBoolean(String key, boolean value) { 820: put(key, Boolean.toString(value)); 821: } 822: 823: /** 824: * Convenience method for setting the given entry as an array of bytes. 825: * The byte array is converted to a Base64 encoded string 826: * and then stored in the preference entry as that string. 827: * <p> 828: * Note that a byte array encoded as a Base64 string will be about 1.3 829: * times larger then the original length of the byte array, which means 830: * that the byte array may not be larger about 6 KB. 831: * 832: * @exception NullPointerException if either key or value are null 833: * @exception IllegalArgumentException if either key or value are to large 834: * @exception IllegalStateException when this node has been removed 835: */ 836: public void putByteArray(String key, byte[] value) { 837: put(key, encode64(value)); 838: } 839: 840: /** 841: * Helper method for encoding an array of bytes as a Base64 String. 842: */ 843: private static String encode64(byte[] b) { 844: StringBuffer sb = new StringBuffer((b.length/3)*4); 845: 846: int i = 0; 847: int remaining = b.length;