001/* Preferences -- Preference node containing key value entries and subnodes 002 Copyright (C) 2001, 2004, 2005, 2006 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038package java.util.prefs; 039 040import gnu.classpath.ServiceFactory; 041import gnu.java.util.prefs.NodeReader; 042 043import java.io.IOException; 044import java.io.InputStream; 045import java.io.OutputStream; 046import java.security.AccessController; 047import java.security.Permission; 048import java.security.PrivilegedAction; 049import java.util.Iterator; 050 051/** 052 * Preference node containing key value entries and subnodes. 053 * <p> 054 * There are two preference node trees, a system tree which can be accessed 055 * by calling <code>systemRoot()</code> containing system preferences usefull 056 * for all users, and a user tree that can be accessed by calling 057 * <code>userRoot()</code> containing preferences that can differ between 058 * different users. How different users are identified is implementation 059 * depended. It can be determined by Thread, Access Control Context or Subject. 060 * <p> 061 * This implementation uses the "java.util.prefs.PreferencesFactory" system 062 * property to find a class that implement <code>PreferencesFactory</code> 063 * and initialized that class (if it has a public no arguments contructor) 064 * to get at the actual system or user root. If the system property is not set, 065 * or the class cannot be initialized it uses the default implementation 066 * <code>gnu.java.util.prefs.FileBasedFactory</code>. 067 * <p> 068 * Besides the two static method above to get the roots of the system and user 069 * preference node trees there are also two convenience methods to access the 070 * default preference node for a particular package an object is in. These are 071 * <code>userNodeForPackage()</code> and <code>systemNodeForPackage()</code>. 072 * Both methods take an Object as an argument so accessing preferences values 073 * can be as easy as calling <code>Preferences.userNodeForPackage(this)</code>. 074 * <p> 075 * Note that if a security manager is installed all static methods check for 076 * <code>RuntimePermission("preferences")</code>. But if this permission is 077 * given to the code then it can access and change all (user) preference nodes 078 * and entries. So you should be carefull not to store to sensitive information 079 * or make security decissions based on preference values since there is no 080 * more fine grained control over what preference values can be changed once 081 * code has been given the correct runtime permission. 082 * <p> 083 * XXX 084 * 085 * @since 1.4 086 * @author Mark Wielaard (mark@klomp.org) 087 */ 088public abstract class Preferences { 089 090 // Static Fields 091 092 /** 093 * Default PreferencesFactory class used when the system property 094 * "java.util.prefs.PreferencesFactory" is not set. 095 */ 096 private static final String defaultFactoryClass 097 = "gnu.java.util.prefs.FileBasedFactory"; 098 099 /** Permission needed to access system or user root. */ 100 private static final Permission prefsPermission 101 = new RuntimePermission("preferences"); 102 103 /** 104 * The preferences factory object that supplies the system and user root. 105 * Set and returned by the getFactory() method. 106 */ 107 private static PreferencesFactory factory; 108 109 /** Maximum node name length. 80 characters. */ 110 public static final int MAX_NAME_LENGTH = 80; 111 112 /** Maximum entry key length. 80 characters. */ 113 public static final int MAX_KEY_LENGTH = 80; 114 115 /** Maximum entry value length. 8192 characters. */ 116 public static final int MAX_VALUE_LENGTH = 8192; 117 118 // Constructors 119 120 /** 121 * Creates a new Preferences node. Can only be used by subclasses. 122 * Empty implementation. 123 */ 124 protected Preferences() {} 125 126 // Static methods 127 128 /** 129 * Returns the system preferences root node containing usefull preferences 130 * for all users. It is save to cache this value since it should always 131 * return the same preference node. 132 * 133 * @return the root system preference node 134 * @exception SecurityException when a security manager is installed and 135 * the caller does not have <code>RuntimePermission("preferences")</code>. 136 */ 137 public static Preferences systemRoot() throws SecurityException { 138 // Get the preferences factory and check for permission 139 PreferencesFactory factory = getFactory(); 140 141 return factory.systemRoot(); 142 } 143 144 /** 145 * Returns the user preferences root node containing preferences for the 146 * the current user. How different users are identified is implementation 147 * depended. It can be determined by Thread, Access Control Context or 148 * Subject. 149 * 150 * @return the root user preference node 151 * @exception SecurityException when a security manager is installed and 152 * the caller does not have <code>RuntimePermission("preferences")</code>. 153 */ 154 public static Preferences userRoot() throws SecurityException { 155 // Get the preferences factory and check for permission 156 PreferencesFactory factory = getFactory(); 157 return factory.userRoot(); 158 } 159 160 /** 161 * Private helper method for <code>systemRoot()</code> and 162 * <code>userRoot()</code>. Checks security permission and instantiates the 163 * correct factory if it has not yet been set. 164 * <p> 165 * When the preferences factory has not yet been set this method first 166 * tries to get the system propery "java.util.prefs.PreferencesFactory" 167 * and tries to initializes that class. If the system property is not set 168 * or initialization fails it returns an instance of the default factory 169 * <code>gnu.java.util.prefs.FileBasedPreferencesFactory</code>. 170 * 171 * @return the preferences factory to use 172 * @exception SecurityException when a security manager is installed and 173 * the caller does not have <code>RuntimePermission("preferences")</code>. 174 */ 175 private static PreferencesFactory getFactory() throws SecurityException { 176 177 // First check for permission 178 SecurityManager sm = System.getSecurityManager(); 179 if (sm != null) { 180 sm.checkPermission(prefsPermission); 181 } 182 183 // Get the factory 184 if (factory == null) { 185 // Caller might not have enough permissions 186 factory = AccessController.doPrivileged( 187 new PrivilegedAction<PreferencesFactory>() { 188 public PreferencesFactory run() { 189 PreferencesFactory pf = null; 190 String className = System.getProperty 191 ("java.util.prefs.PreferencesFactory"); 192 if (className != null) { 193 try { 194 Class fc = Class.forName(className); 195 Object o = fc.newInstance(); 196 pf = (PreferencesFactory) o; 197 } catch (ClassNotFoundException cnfe) 198 {/*ignore*/} 199 catch (InstantiationException ie) 200 {/*ignore*/} 201 catch (IllegalAccessException iae) 202 {/*ignore*/} 203 catch (ClassCastException cce) 204 {/*ignore*/} 205 } 206 return pf; 207 } 208 }); 209 210 // Still no factory? Try to see if we have one defined 211 // as a System Preference 212 if (factory == null) 213 { 214 Iterator iter = ServiceFactory.lookupProviders 215 (PreferencesFactory.class, null); 216 217 if (iter != null && iter.hasNext()) 218 factory = (PreferencesFactory) iter.next(); 219 } 220 221 // Still no factory? Use our default. 222 if (factory == null) 223 { 224 try 225 { 226 Class cls = Class.forName (defaultFactoryClass); 227 factory = (PreferencesFactory) cls.newInstance(); 228 } 229 catch (Exception e) 230 { 231 throw new RuntimeException ("Couldn't load default factory" 232 + " '"+ defaultFactoryClass +"'", e); 233 } 234 } 235 236 } 237 238 return factory; 239 } 240 241 /** 242 * Returns the system preferences node for the package of a class. 243 * The package node name of the class is determined by dropping the 244 * final component of the fully qualified class name and 245 * changing all '.' to '/' in the package name. If the class of the 246 * object has no package then the package node name is "<unnamed>". 247 * The returned node is <code>systemRoot().node(packageNodeName)</code>. 248 * 249 * @param c Object whose default system preference node is requested 250 * @returns system preferences node that should be used by class c 251 * @exception SecurityException when a security manager is installed and 252 * the caller does not have <code>RuntimePermission("preferences")</code>. 253 */ 254 public static Preferences systemNodeForPackage(Class<?> c) 255 throws SecurityException 256 { 257 return nodeForPackage(c, systemRoot()); 258 } 259 260 /** 261 * Returns the user preferences node for the package of a class. 262 * The package node name of the class is determined by dropping the 263 * final component of the fully qualified class name and 264 * changing all '.' to '/' in the package name. If the class of the 265 * object has no package then the package node name is "<unnamed>". 266 * The returned node is <code>userRoot().node(packageNodeName)</code>. 267 * 268 * @param c Object whose default userpreference node is requested 269 * @returns userpreferences node that should be used by class c 270 * @exception SecurityException when a security manager is installed and 271 * the caller does not have <code>RuntimePermission("preferences")</code>. 272 */ 273 public static Preferences userNodeForPackage(Class<?> c) 274 throws SecurityException 275 { 276 return nodeForPackage(c, userRoot()); 277 } 278 279 /** 280 * Private helper method for <code>systemNodeForPackage()</code> and 281 * <code>userNodeForPackage()</code>. Given the correct system or user 282 * root it returns the correct Preference node for the package node name 283 * of the given object. 284 */ 285 private static Preferences nodeForPackage(Class c, Preferences root) { 286 // Get the package path 287 String className = c.getName(); 288 String packagePath; 289 int index = className.lastIndexOf('.'); 290 if(index == -1) { 291 packagePath = "<unnamed>"; 292 } else { 293 packagePath = className.substring(0,index).replace('.','/'); 294 } 295 296 return root.node(packagePath); 297 } 298 299 /** 300 * Import preferences from the given input stream. This expects 301 * preferences to be represented in XML as emitted by 302 * {@link #exportNode(OutputStream)} and 303 * {@link #exportSubtree(OutputStream)}. 304 * @throws IOException if there is an error while reading 305 * @throws InvalidPreferencesFormatException if the XML is not properly 306 * formatted 307 */ 308 public static void importPreferences(InputStream is) 309 throws InvalidPreferencesFormatException, 310 IOException 311 { 312 PreferencesFactory factory = getFactory(); 313 NodeReader reader = new NodeReader(is, factory); 314 reader.importPreferences(); 315 } 316 317 // abstract methods (identification) 318 319 /** 320 * Returns the absolute path name of this preference node. 321 * The absolute path name of a node is the path name of its parent node 322 * plus a '/' plus its own name. If the node is the root node and has no 323 * parent then its name is "" and its absolute path name is "/". 324 */ 325 public abstract String absolutePath(); 326 327 /** 328 * Returns true if this node comes from the user preferences tree, false 329 * if it comes from the system preferences tree. 330 */ 331 public abstract boolean isUserNode(); 332 333 /** 334 * Returns the name of this preferences node. The name of the node cannot 335 * be null, can be mostly 80 characters and cannot contain any '/' 336 * characters. The root node has as name "". 337 */ 338 public abstract String name(); 339 340 /** 341 * Returns the String given by 342 * <code> 343 * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath() 344 * </code> 345 */ 346 public abstract String toString(); 347 348 // abstract methods (navigation) 349 350 /** 351 * Returns all the direct sub nodes of this preferences node. 352 * Needs access to the backing store to give a meaningfull answer. 353 * 354 * @exception BackingStoreException when the backing store cannot be 355 * reached 356 * @exception IllegalStateException when this node has been removed 357 */ 358 public abstract String[] childrenNames() throws BackingStoreException; 359 360 /** 361 * Returns a sub node of this preferences node if the given path is 362 * relative (does not start with a '/') or a sub node of the root 363 * if the path is absolute (does start with a '/'). 364 * 365 * @exception IllegalStateException if this node has been removed 366 * @exception IllegalArgumentException if the path contains two or more 367 * consecutive '/' characters, ends with a '/' charactor and is not the 368 * string "/" (indicating the root node) or any name on the path is more 369 * then 80 characters long 370 */ 371 public abstract Preferences node(String path); 372 373 /** 374 * Returns true if the node that the path points to exists in memory or 375 * in the backing store. Otherwise it returns false or an exception is 376 * thrown. When this node is removed the only valid parameter is the 377 * empty string (indicating this node), the return value in that case 378 * will be false. 379 * 380 * @exception BackingStoreException when the backing store cannot be 381 * reached 382 * @exception IllegalStateException if this node has been removed 383 * and the path is not the empty string (indicating this node) 384 * @exception IllegalArgumentException if the path contains two or more 385 * consecutive '/' characters, ends with a '/' charactor and is not the 386 * string "/" (indicating the root node) or any name on the path is more 387 * then 80 characters long 388 */ 389 public abstract boolean nodeExists(String path) 390 throws BackingStoreException; 391 392 /** 393 * Returns the parent preferences node of this node or null if this is 394 * the root of the preferences tree. 395 * 396 * @exception IllegalStateException if this node has been removed 397 */ 398 public abstract Preferences parent(); 399 400 // abstract methods (export) 401 402 /** 403 * Export this node, but not its descendants, as XML to the 404 * indicated output stream. The XML will be encoded using UTF-8 405 * and will use a specified document type:<br> 406 * <code><!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"></code><br> 407 * @param os the output stream to which the XML is sent 408 * @throws BackingStoreException if preference data cannot be read 409 * @throws IOException if an error occurs while writing the XML 410 * @throws IllegalStateException if this node or an ancestor has been removed 411 */ 412 public abstract void exportNode(OutputStream os) 413 throws BackingStoreException, 414 IOException; 415 416 /** 417 * Export this node and all its descendants as XML to the 418 * indicated output stream. The XML will be encoded using UTF-8 419 * and will use a specified document type:<br> 420 * <code><!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd"></code><br> 421 * @param os the output stream to which the XML is sent 422 * @throws BackingStoreException if preference data cannot be read 423 * @throws IOException if an error occurs while writing the XML 424 * @throws IllegalStateException if this node or an ancestor has been removed 425 */ 426 public abstract void exportSubtree(OutputStream os) 427 throws BackingStoreException, 428 IOException; 429 430 // abstract methods (preference entry manipulation) 431 432 /** 433 * Returns an (possibly empty) array with all the keys of the preference 434 * entries of this node. 435 * 436 * @exception BackingStoreException when the backing store cannot be 437 * reached 438 * @exception IllegalStateException if this node has been removed 439 */ 440 public abstract String[] keys() throws BackingStoreException; 441 442 /** 443 * Returns the value associated with the key in this preferences node. If 444 * the default value of the key cannot be found in the preferences node 445 * entries or something goes wrong with the backing store the supplied 446 * default value is returned. 447 * 448 * @exception IllegalArgumentException if key is larger then 80 characters 449 * @exception IllegalStateException if this node has been removed 450 * @exception NullPointerException if key is null 451 */ 452 public abstract String get(String key, String defaultVal); 453 454 /** 455 * Convenience method for getting the given entry as a boolean. 456 * When the string representation of the requested entry is either 457 * "true" or "false" (ignoring case) then that value is returned, 458 * otherwise the given default boolean 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 boolean getBoolean(String key, boolean defaultVal); 465 466 /** 467 * Convenience method for getting the given entry as a byte array. 468 * When the string representation of the requested entry is a valid 469 * Base64 encoded string (without any other characters, such as newlines) 470 * then the decoded Base64 string is returned as byte array, 471 * otherwise the given default byte array value is returned. 472 * 473 * @exception IllegalArgumentException if key is larger then 80 characters 474 * @exception IllegalStateException if this node has been removed 475 * @exception NullPointerException if key is null 476 */ 477 public abstract byte[] getByteArray(String key, byte[] defaultVal); 478 479 /** 480 * Convenience method for getting the given entry as a double. 481 * When the string representation of the requested entry can be decoded 482 * with <code>Double.parseDouble()</code> then that double is returned, 483 * otherwise the given default double value is returned. 484 * 485 * @exception IllegalArgumentException if key is larger then 80 characters 486 * @exception IllegalStateException if this node has been removed 487 * @exception NullPointerException if key is null 488 */ 489 public abstract double getDouble(String key, double defaultVal); 490 491 /** 492 * Convenience method for getting the given entry as a float. 493 * When the string representation of the requested entry can be decoded 494 * with <code>Float.parseFloat()</code> then that float is returned, 495 * otherwise the given default float value is returned. 496 * 497 * @exception IllegalArgumentException if key is larger then 80 characters 498 * @exception IllegalStateException if this node has been removed 499 * @exception NullPointerException if key is null 500 */ 501 public abstract float getFloat(String key, float defaultVal); 502 503 /** 504 * Convenience method for getting the given entry as an integer. 505 * When the string representation of the requested entry can be decoded 506 * with <code>Integer.parseInt()</code> then that integer is returned, 507 * otherwise the given default integer value is returned. 508 * 509 * @exception IllegalArgumentException if key is larger then 80 characters 510 * @exception IllegalStateException if this node has been removed 511 * @exception NullPointerException if key is null 512 */ 513 public abstract int getInt(String key, int defaultVal); 514 515 /** 516 * Convenience method for getting the given entry as a long. 517 * When the string representation of the requested entry can be decoded 518 * with <code>Long.parseLong()</code> then that long is returned, 519 * otherwise the given default long value is returned. 520 * 521 * @exception IllegalArgumentException if key is larger then 80 characters 522 * @exception IllegalStateException if this node has been removed 523 * @exception NullPointerException if key is null 524 */ 525 public abstract long getLong(String key, long defaultVal); 526 527 /** 528 * Sets the value of the given preferences entry for this node. 529 * Key and value cannot be null, the key cannot exceed 80 characters 530 * and the value cannot exceed 8192 characters. 531 * <p> 532 * The result will be immediatly visible in this VM, but may not be 533 * immediatly written to the backing store. 534 * 535 * @exception NullPointerException if either key or value are null 536 * @exception IllegalArgumentException if either key or value are to large 537 * @exception IllegalStateException when this node has been removed 538 */ 539 public abstract void put(String key, String value); 540 541 /** 542 * Convenience method for setting the given entry as a boolean. 543 * The boolean is converted with <code>Boolean.toString(value)</code> 544 * and then stored in the preference entry as that string. 545 * 546 * @exception NullPointerException if key is null 547 * @exception IllegalArgumentException if the key length is to large 548 * @exception IllegalStateException when this node has been removed 549 */ 550 public abstract void putBoolean(String key, boolean value); 551 552 /** 553 * Convenience method for setting the given entry as an array of bytes. 554 * The byte array is converted to a Base64 encoded string 555 * and then stored in the preference entry as that string. 556 * <p> 557 * Note that a byte array encoded as a Base64 string will be about 1.3 558 * times larger then the original length of the byte array, which means 559 * that the byte array may not be larger about 6 KB. 560 * 561 * @exception NullPointerException if either key or value are null 562 * @exception IllegalArgumentException if either key or value are to large 563 * @exception IllegalStateException when this node has been removed 564 */ 565 public abstract void putByteArray(String key, byte[] value); 566 567 /** 568 * Convenience method for setting the given entry as a double. 569 * The double is converted with <code>Double.toString(double)</code> 570 * and then stored in the preference entry as that string. 571 * 572 * @exception NullPointerException if the key is null 573 * @exception IllegalArgumentException if the key length is to large 574 * @exception IllegalStateException when this node has been removed 575 */ 576 public abstract void putDouble(String key, double value); 577 578 /** 579 * Convenience method for setting the given entry as a float. 580 * The float is converted with <code>Float.toString(float)</code> 581 * and then stored in the preference entry as that string. 582 * 583 * @exception NullPointerException if the key is null 584 * @exception IllegalArgumentException if the key length is to large 585 * @exception IllegalStateException when this node has been removed 586 */ 587 public abstract void putFloat(String key, float value); 588 589 /** 590 * Convenience method for setting the given entry as an integer. 591 * The integer is converted with <code>Integer.toString(int)</code> 592 * and then stored in the preference entry as that string. 593 * 594 * @exception NullPointerException if the key is null 595 * @exception IllegalArgumentException if the key length is to large 596 * @exception IllegalStateException when this node has been removed 597 */ 598 public abstract void putInt(String key, int value); 599 600 /** 601 * Convenience method for setting the given entry as a long. 602 * The long is converted with <code>Long.toString(long)</code> 603 * and then stored in the preference entry as that string. 604 * 605 * @exception NullPointerException if the key is null 606 * @exception IllegalArgumentException if the key length is to large 607 * @exception IllegalStateException when this node has been removed 608 */ 609 public abstract void putLong(String key, long value); 610 611 /** 612 * Removes the preferences entry from this preferences node. 613 * <p> 614 * The result will be immediatly visible in this VM, but may not be 615 * immediatly written to the backing store. 616 * 617 * @exception NullPointerException if the key is null 618 * @exception IllegalArgumentException if the key length is to large 619 * @exception IllegalStateException when this node has been removed 620 */ 621 public abstract void remove(String key); 622 623 // abstract methods (preference node manipulation) 624 625 /** 626 * Removes all entries from this preferences node. May need access to the 627 * backing store to get and clear all entries. 628 * <p> 629 * The result will be immediatly visible in this VM, but may not be 630 * immediatly written to the backing store. 631 * 632 * @exception BackingStoreException when the backing store cannot be 633 * reached 634 * @exception IllegalStateException if this node has been removed 635 */ 636 public abstract void clear() throws BackingStoreException; 637 638 /** 639 * Writes all preference changes on this and any subnode that have not 640 * yet been written to the backing store. This has no effect on the 641 * preference entries in this VM, but it makes sure that all changes 642 * are visible to other programs (other VMs might need to call the 643 * <code>sync()</code> method to actually see the changes to the backing 644 * store. 645 * 646 * @exception BackingStoreException when the backing store cannot be 647 * reached 648 * @exception IllegalStateException if this node has been removed 649 */ 650 public abstract void flush() throws BackingStoreException; 651 652 /** 653 * Writes and reads all preference changes to and from this and any 654 * subnodes. This makes sure that all local changes are written to the 655 * backing store and that all changes to the backing store are visible 656 * in this preference node (and all subnodes). 657 * 658 * @exception BackingStoreException when the backing store cannot be 659 * reached 660 * @exception IllegalStateException if this node has been removed 661 */ 662 public abstract void sync() throws BackingStoreException; 663 664 /** 665 * Removes this and all subnodes from the backing store and clears all 666 * entries. After removal this instance will not be useable (except for 667 * a few methods that don't throw a <code>InvalidStateException</code>), 668 * even when a new node with the same path name is created this instance 669 * will not be usable again. The root (system or user) may never be removed. 670 * <p> 671 * Note that according to the specification an implementation may delay 672 * removal of the node from the backing store till the <code>flush()</code> 673 * method is called. But the <code>flush()</code> method may throw a 674 * <code>IllegalStateException</code> when the node has been removed. 675 * So most implementations will actually remove the node and any subnodes 676 * from the backing store immediatly. 677 * 678 * @exception BackingStoreException when the backing store cannot be 679 * reached 680 * @exception IllegalStateException if this node has already been removed 681 * @exception UnsupportedOperationException if this is a root node 682 */ 683 public abstract void removeNode() throws BackingStoreException; 684 685 // abstract methods (listeners) 686 687 public abstract void addNodeChangeListener(NodeChangeListener listener); 688 689 public abstract void addPreferenceChangeListener 690 (PreferenceChangeListener listener); 691 692 public abstract void removeNodeChangeListener(NodeChangeListener listener); 693 694 public abstract void removePreferenceChangeListener 695 (PreferenceChangeListener listener); 696} 697