Frames | No Frames |
1: /* Preferences -- Preference node containing key value entries and subnodes 2: Copyright (C) 2001, 2004, 2005, 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: package java.util.prefs; 39: 40: import gnu.java.util.prefs.NodeReader; 41: 42: import java.io.IOException; 43: import java.io.InputStream; 44: import java.io.OutputStream; 45: import java.security.AccessController; 46: import java.security.Permission; 47: import java.security.PrivilegedAction; 48: 49: /** 50: * Preference node containing key value entries and subnodes. 51: * <p> 52: * There are two preference node trees, a system tree which can be accessed 53: * by calling <code>systemRoot()</code> containing system preferences usefull 54: * for all users, and a user tree that can be accessed by calling 55: * <code>userRoot()</code> containing preferences that can differ between 56: * different users. How different users are identified is implementation 57: * depended. It can be determined by Thread, Access Control Context or Subject. 58: * <p> 59: * This implementation uses the "java.util.prefs.PreferencesFactory" system 60: * property to find a class that implement <code>PreferencesFactory</code> 61: * and initialized that class (if it has a public no arguments contructor) 62: * to get at the actual system or user root. If the system property is not set, 63: * or the class cannot be initialized it uses the default implementation 64: * <code>gnu.java.util.prefs.FileBasedFactory</code>. 65: * <p> 66: * Besides the two static method above to get the roots of the system and user 67: * preference node trees there are also two convenience methods to access the 68: * default preference node for a particular package an object is in. These are 69: * <code>userNodeForPackage()</code> and <code>systemNodeForPackage()</code>. 70: * Both methods take an Object as an argument so accessing preferences values 71: * can be as easy as calling <code>Preferences.userNodeForPackage(this)</code>. 72: * <p> 73: * Note that if a security manager is installed all static methods check for 74: * <code>RuntimePermission("preferences")</code>. But if this permission is 75: * given to the code then it can access and change all (user) preference nodes 76: * and entries. So you should be carefull not to store to sensitive information 77: * or make security decissions based on preference values since there is no 78: * more fine grained control over what preference values can be changed once 79: * code has been given the correct runtime permission. 80: * <p> 81: * XXX 82: * 83: * @since 1.4 84: * @author Mark Wielaard (mark@klomp.org) 85: */ 86: public abstract class Preferences { 87: 88: // Static Fields 89: 90: /** 91: * Default PreferencesFactory class used when the system property 92: * "java.util.prefs.PreferencesFactory" is not set. 93: */ 94: private static final String defaultFactoryClass 95: = "gnu.java.util.prefs.FileBasedFactory"; 96: 97: /** Permission needed to access system or user root. */ 98: private static final Permission prefsPermission 99: = new RuntimePermission("preferences"); 100: 101: /** 102: * The preferences factory object that supplies the system and user root. 103: * Set and returned by the getFactory() method. 104: */ 105: private static PreferencesFactory factory; 106: 107: /** Maximum node name length. 80 characters. */ 108: public static final int MAX_NAME_LENGTH = 80; 109: 110: /** Maximum entry key length. 80 characters. */ 111: public static final int MAX_KEY_LENGTH = 80; 112: 113: /** Maximum entry value length. 8192 characters. */ 114: public static final int MAX_VALUE_LENGTH = 8192; 115: 116: // Constructors 117: 118: /** 119: * Creates a new Preferences node. Can only be used by subclasses. 120: * Empty implementation. 121: */ 122: protected Preferences() {} 123: 124: // Static methods 125: 126: /** 127: * Returns the system preferences root node containing usefull preferences 128: * for all users. It is save to cache this value since it should always 129: * return the same preference node. 130: * 131: * @return the root system preference node 132: * @exception SecurityException when a security manager is installed and 133: * the caller does not have <code>RuntimePermission("preferences")</code>. 134: */ 135: public static Preferences systemRoot() throws SecurityException { 136: // Get the preferences factory and check for permission 137: PreferencesFactory factory = getFactory(); 138: 139: return factory.systemRoot(); 140: } 141: 142: /** 143: * Returns the user preferences root node containing preferences for the 144: * the current user. How different users are identified is implementation 145: * depended. It can be determined by Thread, Access Control Context or 146: * Subject. 147: * 148: * @return the root user preference node 149: * @exception SecurityException when a security manager is installed and 150: * the caller does not have <code>RuntimePermission("preferences")</code>. 151: */ 152: public static Preferences userRoot() throws SecurityException { 153: // Get the preferences factory and check for permission 154: PreferencesFactory factory = getFactory(); 155: return factory.userRoot(); 156: } 157: 158: /** 159: * Private helper method for <code>systemRoot()</code> and 160: * <code>userRoot()</code>. Checks security permission and instantiates the 161: * correct factory if it has not yet been set. 162: * <p> 163: * When the preferences factory has not yet been set this method first 164: * tries to get the system propery "java.util.prefs.PreferencesFactory" 165: * and tries to initializes that class. If the system property is not set 166: * or initialization fails it returns an instance of the default factory 167: * <code>gnu.java.util.prefs.FileBasedPreferencesFactory</code>. 168: * 169: * @return the preferences factory to use 170: * @exception SecurityException when a security manager is installed and 171: * the caller does not have <code>RuntimePermission("preferences")</code>. 172: */ 173: private static PreferencesFactory getFactory() throws SecurityException { 174: 175: // First check for permission 176: SecurityManager sm = System.getSecurityManager(); 177: if (sm != null) { 178: sm.checkPermission(prefsPermission); 179: } 180: 181: // Get the factory 182: if (factory == null) { 183: // Caller might not have enough permissions 184: factory = (PreferencesFactory) AccessController.doPrivileged( 185: new PrivilegedAction() { 186: public Object run() { 187: PreferencesFactory pf = null; 188: String className = System.getProperty 189: ("java.util.prefs.PreferencesFactory"); 190: if (className != null) { 191: try { 192: Class fc = Class.forName(className); 193: Object o = fc.newInstance(); 194: pf = (PreferencesFactory) o; 195: } catch (ClassNotFoundException cnfe) 196: {/*ignore*/} 197: catch (InstantiationException ie) 198: {/*ignore*/} 199: catch (IllegalAccessException iae) 200: {/*ignore*/} 201: catch (ClassCastException cce) 202: {/*ignore*/} 203: } 204: return pf; 205: } 206: }); 207: 208: // Still no factory? Use our default. 209: if (factory == null) 210: { 211: try 212: { 213: Class cls = Class.forName (defaultFactoryClass); 214: factory = (PreferencesFactory) cls.newInstance(); 215: } 216: catch (Exception e) 217: { 218: throw new RuntimeException ("Couldn't load default factory" 219: + " '"+ defaultFactoryClass +"'", e); 220: } 221: } 222: 223: } 224: 225: return factory; 226: } 227: 228: /** 229: * Returns the system preferences node for the package of a class. 230: * The package node name of the class is determined by dropping the 231: * final component of the fully qualified class name and 232: * changing all '.' to '/' in the package name. If the class of the 233: * object has no package then the package node name is "<unnamed>". 234: * The returned node is <code>systemRoot().node(packageNodeName)</code>. 235: * 236: * @param c Object whose default system preference node is requested 237: * @returns system preferences node that should be used by class c 238: * @exception SecurityException when a security manager is installed and 239: * the caller does not have <code>RuntimePermission("preferences")</code>. 240: */ 241: public static Preferences systemNodeForPackage(Class c) 242: throws SecurityException 243: { 244: return nodeForPackage(c, systemRoot()); 245: } 246: 247: /** 248: * Returns the user preferences node for the package of a class. 249: * The package node name of the class is determined by dropping the 250: * final component of the fully qualified class name and 251: * changing all '.' to '/' in the package name. If the class of the 252: * object has no package then the package node name is "<unnamed>". 253: * The returned node is <code>userRoot().node(packageNodeName)</code>. 254: * 255: * @param c Object whose default userpreference node is requested 256: * @returns userpreferences node that should be used by class c 257: * @exception SecurityException when a security manager is installed and 258: * the caller does not have <code>RuntimePermission("preferences")</code>. 259: */ 260: public static Preferences userNodeForPackage(Class c) 261: throws SecurityException 262: { 263: return nodeForPackage(c, userRoot()); 264: } 265: 266: /** 267: * Private helper method for <code>systemNodeForPackage()</code> and 268: * <code>userNodeForPackage()</code>. Given the correct system or user 269: * root it returns the correct Preference node for the package node name 270: * of the given object. 271: */ 272: private static Preferences nodeForPackage(Class c, Preferences root) { 273: // Get the package path 274: String className = c.getName(); 275: String packagePath; 276: int index = className.lastIndexOf('.'); 277: if(index == -1) { 278: packagePath = "<unnamed>"; 279: } else { 280: packagePath = className.substring(0,index).replace('.','/'); 281: } 282: 283: return root.node(packagePath); 284: } 285: 286: /** 287: * Import preferences from the given input stream. This expects 288: * preferences to be represented in XML as emitted by 289: * {@link #exportNode(OutputStream)} and 290: * {@link #exportSubtree(OutputStream)}. 291: * @throws IOException if there is an error while reading 292: * @throws InvalidPreferencesFormatException if the XML is not properly 293: * formatted 294: */ 295: public static void importPreferences(InputStream is) 296: throws InvalidPreferencesFormatException, 297: IOException 298: { 299: PreferencesFactory factory = getFactory(); 300: NodeReader reader = new NodeReader(is, factory); 301: reader.importPreferences(); 302: } 303: 304: // abstract methods (identification) 305: 306: /** 307: * Returns the absolute path name of this preference node. 308: * The absolute path name of a node is the path name of its parent node 309: * plus a '/' plus its own name. If the node is the root node and has no 310: * parent then its name is "" and its absolute path name is "/". 311: */ 312: public abstract String absolutePath(); 313: 314: /** 315: * Returns true if this node comes from the user preferences tree, false 316: * if it comes from the system preferences tree. 317: */ 318: public abstract boolean isUserNode(); 319: 320: /** 321: * Returns the name of this preferences node. The name of the node cannot 322: * be null, can be mostly 80 characters and cannot contain any '/' 323: * characters. The root node has as name "". 324: */ 325: public abstract String name(); 326: 327: /** 328: * Returns the String given by 329: * <code> 330: * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath() 331: * </code> 332: */ 333: public abstract String toString(); 334: 335: // abstract methods (navigation) 336: 337: /** 338: * Returns all the direct sub nodes of this preferences node. 339: * Needs access to the backing store to give a meaningfull answer. 340: * 341: * @exception BackingStoreException when the backing store cannot be 342: * reached 343: * @exception IllegalStateException when this node has been removed 344: */ 345: public abstract String[] childrenNames() throws BackingStoreException; 346: 347: /** 348: * Returns a sub node of this preferences node if the given path is 349: * relative (does not start with a '/') or a sub node of the root 350: * if the path is absolute (does start with a '/'). 351: * 352: * @exception IllegalStateException if this node has been removed 353: * @exception IllegalArgumentException if the path contains two or more 354: * consecutive '/' characters, ends with a '/' charactor and is not the 355: * string "/" (indicating the root node) or any name on the path is more 356: * then 80 characters long 357: */ 358: public abstract Preferences node(String path); 359: 360: /** 361: * Returns true if the node that the path points to exists in memory or 362: * in the backing store. Otherwise it returns false or an exception is 363: * thrown. When this node is removed the only valid parameter is the 364: * empty string (indicating this node), the return value in that case 365: * will be false. 366: * 367: * @exception BackingStoreException when the backing store cannot be 368: * reached 369: * @exception IllegalStateException if this node has been removed 370: * and the path is not the empty string (indicating this node) 371: * @exception IllegalArgumentException if the path contains two or more 372: * consecutive '/' characters, ends with a '/' charactor and is not the 373: * string "/" (indicating the root node) or any name on the path is more 374: * then 80 characters long 375: */ 376: public abstract boolean nodeExists(String path) 377: throws BackingStoreException; 378: 379: /** 380: * Returns the parent preferences node of this node or null if this is 381: * the root of the preferences tree. 382: * 383: * @exception IllegalStateException if this node has been removed 384: */ 385: public abstract Preferences parent(); 386: 387: // abstract methods (export) 388: 389: /** 390: * Export this node, but not its descendants, as XML to the 391: * indicated output stream. The XML will be encoded using UTF-8 392: * and will use a specified document type:<br> 393: * <code><!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"></code><br> 394: * @param os the output stream to which the XML is sent 395: * @throws BackingStoreException if preference data cannot be read 396: * @throws IOException if an error occurs while writing the XML 397: * @throws IllegalStateException if this node or an ancestor has been removed 398: */ 399: public abstract void exportNode(OutputStream os) 400: throws BackingStoreException, 401: IOException; 402: 403: /** 404: * Export this node and all its descendants as XML to the 405: * indicated output stream. The XML will be encoded using UTF-8 406: * and will use a specified document type:<br> 407: * <code><!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"></code><br> 408: * @param os the output stream to which the XML is sent 409: * @throws BackingStoreException if preference data cannot be read 410: * @throws IOException if an error occurs while writing the XML 411: * @throws IllegalStateException if this node or an ancestor has been removed 412: */ 413: public abstract void exportSubtree(OutputStream os) 414: throws BackingStoreException, 415: IOException; 416: 417: // abstract methods (preference entry manipulation) 418: 419: /** 420: * Returns an (possibly empty) array with all the keys of the preference 421: * entries of this node. 422: * 423: * @exception BackingStoreException when the backing store cannot be 424: * reached 425: * @exception IllegalStateException if this node has been removed 426: */ 427: public abstract String[] keys() throws BackingStoreException; 428: 429: /** 430: * Returns the value associated with the key in this preferences node. If 431: * the default value of the key cannot be found in the preferences node 432: * entries or something goes wrong with the backing store the supplied 433: * default value is returned. 434: * 435: * @exception IllegalArgumentException if key is larger then 80 characters 436: * @exception IllegalStateException if this node has been removed 437: * @exception NullPointerException if key is null 438: */ 439: public abstract String get(String key, String defaultVal); 440: 441: /** 442: * Convenience method for getting the given entry as a boolean. 443: * When the string representation of the requested entry is either 444: * "true" or "false" (ignoring case) then that value is returned, 445: * otherwise the given default boolean value is returned. 446: * 447: * @exception IllegalArgumentException if key is larger then 80 characters 448: * @exception IllegalStateException if this node has been removed 449: * @exception NullPointerException if key is null 450: */ 451: public abstract boolean getBoolean(String key, boolean defaultVal); 452: 453: /** 454: * Convenience method for getting the given entry as a byte array. 455: * When the string representation of the requested entry is a valid 456: * Base64 encoded string (without any other characters, such as newlines) 457: * then the decoded Base64 string is returned as byte array, 458: * otherwise the given default byte array value is returned. 459: * 460: * @exception IllegalArgumentException if key is larger then 80 characters 461: * @exception IllegalStateException if this node has been removed 462: * @exception NullPointerException if key is null 463: */ 464: public abstract byte[] getByteArray(String key, byte[] defaultVal); 465: 466: /** 467: * Convenience method for getting the given entry as a double. 468: * When the string representation of the requested entry can be decoded 469: * with <code>Double.parseDouble()</code> then that double is returned, 470: * otherwise the given default double value is returned. 471: * 472: * @exception IllegalArgumentException if key is larger then 80 characters 473: * @exception IllegalStateException if this node has been removed 474: * @exception NullPointerException if key is null 475: */ 476: public abstract double getDouble(String key, double defaultVal); 477: 478: /** 479: * Convenience method for getting the given entry as a float. 480: * When the string representation of the requested entry can be decoded 481: * with <code>Float.parseFloat()</code> then that float is returned, 482: * otherwise the given default float value is returned. 483: * 484: * @exception IllegalArgumentException if key is larger then 80 characters 485: * @exception IllegalStateException if this node has been removed 486: * @exception NullPointerException if key is null 487: */ 488: public abstract float getFloat(String key, float defaultVal); 489: 490: /** 491: * Convenience method for getting the given entry as an integer. 492: * When the string representation of the requested entry can be decoded 493: * with <code>Integer.parseInt()</code> then that integer is returned, 494: * otherwise the given default integer value is returned. 495: * 496: * @exception IllegalArgumentException if key is larger then 80 characters 497: * @exception IllegalStateException if this node has been removed 498: * @exception NullPointerException if key is null 499: */ 500: public abstract int getInt(String key, int defaultVal); 501: 502: /** 503: * Convenience method for getting the given entry as a long. 504: * When the string representation of the requested entry can be decoded 505: * with <code>Long.parseLong()</code> then that long is returned, 506: * otherwise the given default long value is returned. 507: * 508: * @exception IllegalArgumentException if key is larger then 80 characters 509: * @exception IllegalStateException if this node has been removed 510: * @exception NullPointerException if key is null 511: */ 512: public abstract long getLong(String key, long defaultVal); 513: 514: /** 515: * Sets the value of the given preferences entry for this node. 516: * Key and value cannot be null, the key cannot exceed 80 characters 517: * and the value cannot exceed 8192 characters. 518: * <p> 519: * The result will be immediatly visible in this VM, but may not be 520: * immediatly written to the backing store. 521: * 522: * @exception NullPointerException if either key or value are null 523: * @exception IllegalArgumentException if either key or value are to large 524: * @exception IllegalStateException when this node has been removed 525: */ 526: public abstract void put(String key, String value); 527: 528: /** 529: * Convenience method for setting the given entry as a boolean. 530: * The boolean is converted with <code>Boolean.toString(value)</code> 531: * and then stored in the preference entry as that string. 532: * 533: * @exception NullPointerException if key is null 534: * @exception IllegalArgumentException if the key length is to large 535: * @exception IllegalStateException when this node has been removed 536: */ 537: public abstract void putBoolean(String key, boolean value); 538: 539: /** 540: * Convenience method for setting the given entry as an array of bytes. 541: * The byte array is converted to a Base64 encoded string 542: * and then stored in the preference entry as that string. 543: * <p> 544: * Note that a byte array encoded as a Base64 string will be about 1.3 545: * times larger then the original length of the byte array, which means 546: * that the byte array may not be larger about 6 KB. 547: * 548: * @exception NullPointerException if either key or value are null 549: * @exception IllegalArgumentException if either key or value are to large 550: * @exception IllegalStateException when this node has been removed 551: */ 552: public abstract void putByteArray(String key, byte[] value); 553: 554: /** 555: * Convenience method for setting the given entry as a double. 556: * The double is converted with <code>Double.toString(double)</code> 557: * and then stored in the preference entry as that string. 558: * 559: * @exception NullPointerException if the key is null 560: * @exception IllegalArgumentException if the key length is to large 561: * @exception IllegalStateException when this node has been removed 562: */ 563: public abstract void putDouble(String key, double value); 564: 565: /** 566: * Convenience method for setting the given entry as a float. 567: * The float is converted with <code>Float.toString(float)</code> 568: * and then stored in the preference entry as that string. 569: * 570: * @exception NullPointerException if the key is null 571: * @exception IllegalArgumentException if the key length is to large 572: * @exception IllegalStateException when this node has been removed 573: */ 574: public abstract void putFloat(String key, float value); 575: 576: /** 577: * Convenience method for setting the given entry as an integer. 578: * The integer is converted with <code>Integer.toString(int)</code> 579: * and then stored in the preference entry as that string. 580: * 581: * @exception NullPointerException if the key is null 582: * @exception IllegalArgumentException if the key length is to large 583: * @exception IllegalStateException when this node has been removed 584: */ 585: public abstract void putInt(String key, int value); 586: 587: /** 588: * Convenience method for setting the given entry as a long. 589: * The long is converted with <code>Long.toString(long)</code> 590: * and then stored in the preference entry as that string. 591: * 592: * @exception NullPointerException if the key is null 593: * @exception IllegalArgumentException if the key length is to large 594: * @exception IllegalStateException when this node has been removed 595: */ 596: public abstract void putLong(String key, long value); 597: 598: /** 599: * Removes the preferences entry from this preferences node. 600: * <p> 601: * The result will be immediatly visible in this VM, but may not be 602: * immediatly written to the backing store. 603: * 604: * @exception NullPointerException if the key is null 605: * @exception IllegalArgumentException if the key length is to large 606: * @exception IllegalStateException when this node has been removed 607: */ 608: public abstract void remove(String key); 609: 610: // abstract methods (preference node manipulation) 611: 612: /** 613: * Removes all entries from this preferences node. May need access to the 614: * backing store to get and clear all entries. 615: * <p> 616: * The result will be immediatly visible in this VM, but may not be 617: * immediatly written to the backing store. 618: * 619: * @exception BackingStoreException when the backing store cannot be 620: * reached 621: * @exception IllegalStateException if this node has been removed 622: */ 623: public abstract void clear() throws BackingStoreException; 624: 625: /** 626: * Writes all preference changes on this and any subnode that have not 627: * yet been written to the backing store. This has no effect on the 628: * preference entries in this VM, but it makes sure that all changes 629: * are visible to other programs (other VMs might need to call the 630: * <code>sync()</code> method to actually see the changes to the backing 631: * store. 632: * 633: * @exception BackingStoreException when the backing store cannot be 634: * reached 635: * @exception IllegalStateException if this node has been removed 636: */ 637: public abstract void flush() throws BackingStoreException; 638: 639: /** 640: * Writes and reads all preference changes to and from this and any 641: * subnodes. This makes sure that all local changes are written to the 642: * backing store and that all changes to the backing store are visible 643: * in this preference node (and all subnodes). 644: * 645: * @exception BackingStoreException when the backing store cannot be 646: * reached 647: * @exception IllegalStateException if this node has been removed 648: */ 649: public abstract void sync() throws BackingStoreException; 650: 651: /** 652: * Removes this and all subnodes from the backing store and clears all 653: * entries. After removal this instance will not be useable (except for 654: * a few methods that don't throw a <code>InvalidStateException</code>), 655: * even when a new node with the same path name is created this instance 656: * will not be usable again. The root (system or user) may never be removed. 657: * <p> 658: * Note that according to the specification an implementation may delay 659: * removal of the node from the backing store till the <code>flush()</code> 660: * method is called. But the <code>flush()</code> method may throw a 661: * <code>IllegalStateException</code> when the node has been removed. 662: * So most implementations will actually remove the node and any subnodes 663: * from the backing store immediatly. 664: * 665: * @exception BackingStoreException when the backing store cannot be 666: * reached 667: * @exception IllegalStateException if this node has already been removed 668: * @exception UnsupportedOperationException if this is a root node 669: */ 670: public abstract void removeNode() throws BackingStoreException; 671: 672: // abstract methods (listeners) 673: 674: public abstract void addNodeChangeListener(NodeChangeListener listener); 675: 676: public abstract void addPreferenceChangeListener 677: (PreferenceChangeListener listener); 678: 679: public abstract void removeNodeChangeListener(NodeChangeListener listener); 680: 681: public abstract void removePreferenceChangeListener 682: (PreferenceChangeListener listener); 683: }