Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)
Frames | No Frames

Source for java.util.prefs.AbstractPreferences

 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;
 848:  char c[] = new char[4];
 849:  while (remaining > 0) {
 850:  // Three input bytes are encoded as four chars (6 bits) as
 851:  // 00000011 11112222 22333333
 852: 
 853:  c[0] = (char) ((b[i] & 0xFC) >> 2);
 854:  c[1] = (char) ((b[i] & 0x03) << 4);
 855:  if (remaining >= 2) {
 856:  c[1] += (char) ((b[i+1] & 0xF0) >> 4);
 857:  c[2] = (char) ((b[i+1] & 0x0F) << 2);
 858:  if (remaining >= 3) {
 859:  c[2] += (char) ((b[i+2] & 0xC0) >> 6);
 860:  c[3] = (char) (b[i+2] & 0x3F);
 861:  } else {
 862:  c[3] = 64;
 863:  }
 864:  } else {
 865:  c[2] = 64;
 866:  c[3] = 64;
 867:  }
 868: 
 869:  // Convert to base64 chars
 870:  for(int j = 0; j < 4; j++) {
 871:  if (c[j] < 26) {
 872:  c[j] += 'A';
 873:  } else if (c[j] < 52) {
 874:  c[j] = (char) (c[j] - 26 + 'a');
 875:  } else if (c[j] < 62) {
 876:  c[j] = (char) (c[j] - 52 + '0');
 877:  } else if (c[j] == 62) {
 878:  c[j] = '+';
 879:  } else if (c[j] == 63) {
 880:  c[j] = '/';
 881:  } else {
 882:  c[j] = '=';
 883:  }
 884:  }
 885: 
 886:  sb.append(c);
 887:  i += 3;
 888:  remaining -= 3;
 889:  }
 890: 
 891:  return sb.toString();
 892:  }
 893: 
 894:  /**
 895:  * Convenience method for setting the given entry as a double.
 896:  * The double is converted with <code>Double.toString(double)</code>
 897:  * and then stored in the preference entry as that string.
 898:  *
 899:  * @exception NullPointerException if the key is null
 900:  * @exception IllegalArgumentException if the key length is to large
 901:  * @exception IllegalStateException when this node has been removed
 902:  */
 903:  public void putDouble(String key, double value) {
 904:  put(key, Double.toString(value));
 905:  }
 906: 
 907:  /**
 908:  * Convenience method for setting the given entry as a float.
 909:  * The float is converted with <code>Float.toString(float)</code>
 910:  * and then stored in the preference entry as that string.
 911:  *
 912:  * @exception NullPointerException if the key is null
 913:  * @exception IllegalArgumentException if the key length is to large
 914:  * @exception IllegalStateException when this node has been removed
 915:  */
 916:  public void putFloat(String key, float value) {
 917:  put(key, Float.toString(value));
 918:  }
 919: 
 920:  /**
 921:  * Convenience method for setting the given entry as an integer.
 922:  * The integer is converted with <code>Integer.toString(int)</code>
 923:  * and then stored in the preference entry as that string.
 924:  *
 925:  * @exception NullPointerException if the key is null
 926:  * @exception IllegalArgumentException if the key length is to large
 927:  * @exception IllegalStateException when this node has been removed
 928:  */
 929:  public void putInt(String key, int value) {
 930:  put(key, Integer.toString(value));
 931:  }
 932: 
 933:  /**
 934:  * Convenience method for setting the given entry as a long.
 935:  * The long is converted with <code>Long.toString(long)</code>
 936:  * and then stored in the preference entry as that string.
 937:  *
 938:  * @exception NullPointerException if the key is null
 939:  * @exception IllegalArgumentException if the key length is to large
 940:  * @exception IllegalStateException when this node has been removed
 941:  */
 942:  public void putLong(String key, long value) {
 943:  put(key, Long.toString(value));
 944:  }
 945: 
 946:  /**
 947:  * Removes the preferences entry from this preferences node.
 948:  * <p> 
 949:  * The result will be immediately visible in this VM, but may not be
 950:  * immediately written to the backing store.
 951:  * <p>
 952:  * This implementation checks that the key is not larger then 80
 953:  * characters, gets the lock of this node, checks that the node has
 954:  * not been removed and calls <code>removeSpi</code> with the given key.
 955:  *
 956:  * @exception NullPointerException if the key is null
 957:  * @exception IllegalArgumentException if the key length is to large
 958:  * @exception IllegalStateException when this node has been removed
 959:  */
 960:  public void remove(String key) {
 961:  if (key.length() > MAX_KEY_LENGTH)
 962:  throw new IllegalArgumentException(key);
 963: 
 964:  synchronized(lock) {
 965:  if (isRemoved())
 966:  throw new IllegalStateException("Node removed");
 967: 
 968:  removeSpi(key);
 969: 
 970:  if (preferenceListeners != null)
 971:  fire(new PreferenceChangeEvent(this, key, null));
 972:  }
 973:  }
 974: 
 975:  /**
 976:  * Removes all entries from this preferences node. May need access to the
 977:  * backing store to get and clear all entries.
 978:  * <p>
 979:  * The result will be immediately visible in this VM, but may not be
 980:  * immediatly written to the backing store.
 981:  * <p>
 982:  * This implementation locks this node, checks that the node has not been
 983:  * removed and calls <code>keys()</code> to get a complete array of keys
 984:  * for this node. For every key found <code>removeSpi()</code> is called.
 985:  *
 986:  * @exception BackingStoreException when the backing store cannot be
 987:  * reached
 988:  * @exception IllegalStateException if this node has been removed
 989:  */
 990:  public void clear() throws BackingStoreException {
 991:  synchronized(lock) {
 992:  if (isRemoved())
 993:  throw new IllegalStateException("Node Removed");
 994: 
 995:  String[] keys = keys();
 996:  for (int i = 0; i < keys.length; i++) {
 997:  removeSpi(keys[i]);
 998:  }
 999:  }
1000:  }
1001: 
1002:  /**
1003:  * Writes all preference changes on this and any subnode that have not
1004:  * yet been written to the backing store. This has no effect on the
1005:  * preference entries in this VM, but it makes sure that all changes
1006:  * are visible to other programs (other VMs might need to call the
1007:  * <code>sync()</code> method to actually see the changes to the backing
1008:  * store.
1009:  * <p>
1010:  * Locks this node, calls the <code>flushSpi()</code> method, gets all
1011:  * the (cached - already existing in this VM) subnodes and then calls
1012:  * <code>flushSpi()</code> on every subnode with this node unlocked and
1013:  * only that particular subnode locked.
1014:  *
1015:  * @exception BackingStoreException when the backing store cannot be
1016:  * reached
1017:  */
1018:  public void flush() throws BackingStoreException {
1019:  flushNode(false);
1020:  }
1021: 
1022:  /**
1023:  * Writes and reads all preference changes to and from this and any
1024:  * subnodes. This makes sure that all local changes are written to the
1025:  * backing store and that all changes to the backing store are visible
1026:  * in this preference node (and all subnodes).
1027:  * <p>
1028:  * Checks that this node is not removed, locks this node, calls the
1029:  * <code>syncSpi()</code> method, gets all the subnodes and then calls
1030:  * <code>syncSpi()</code> on every subnode with this node unlocked and
1031:  * only that particular subnode locked.
1032:  *
1033:  * @exception BackingStoreException when the backing store cannot be
1034:  * reached
1035:  * @exception IllegalStateException if this node has been removed
1036:  */
1037:  public void sync() throws BackingStoreException {
1038:  flushNode(true);
1039:  }
1040:  
1041: 
1042:  /**
1043:  * Private helper method that locks this node and calls either
1044:  * <code>flushSpi()</code> if <code>sync</code> is false, or
1045:  * <code>flushSpi()</code> if <code>sync</code> is true. Then it gets all
1046:  * the currently cached subnodes. For every subnode it calls this method
1047:  * recursively with this node no longer locked.
1048:  * <p>
1049:  * Called by either <code>flush()</code> or <code>sync()</code>
1050:  */
1051:  private void flushNode(boolean sync) throws BackingStoreException {
1052:  String[] keys = null;
1053:  synchronized(lock) {
1054:  if (sync) {
1055:  syncSpi();
1056:  } else {
1057:  flushSpi();
1058:  }
1059:  keys = (String[]) childCache.keySet().toArray(new String[]{});
1060:  }
1061: 
1062:  if (keys != null) {
1063:  for (int i = 0; i < keys.length; i++) {
1064:  // Have to lock this node again to access the childCache
1065:  AbstractPreferences subNode;
1066:  synchronized(lock) {
1067:  subNode = (AbstractPreferences) childCache.get(keys[i]);
1068:  }
1069: 
1070:  // The child could already have been removed from the cache
1071:  if (subNode != null) {
1072:  subNode.flushNode(sync);
1073:  }
1074:  }
1075:  }
1076:  }
1077: 
1078:  /**
1079:  * Removes this and all subnodes from the backing store and clears all
1080:  * entries. After removal this instance will not be useable (except for
1081:  * a few methods that don't throw a <code>InvalidStateException</code>),
1082:  * even when a new node with the same path name is created this instance
1083:  * will not be usable again.
1084:  * <p>
1085:  * Checks that this is not a root node. If not it locks the parent node,
1086:  * then locks this node and checks that the node has not yet been removed.
1087:  * Then it makes sure that all subnodes of this node are in the child cache,
1088:  * by calling <code>childSpi()</code> on any children not yet in the cache.
1089:  * Then for all children it locks the subnode and removes it. After all
1090:  * subnodes have been purged the child cache is cleared, this nodes removed
1091:  * flag is set and any listeners are called. Finally this node is removed
1092:  * from the child cache of the parent node.
1093:  *
1094:  * @exception BackingStoreException when the backing store cannot be
1095:  * reached
1096:  * @exception IllegalStateException if this node has already been removed
1097:  * @exception UnsupportedOperationException if this is a root node
1098:  */
1099:  public void removeNode() throws BackingStoreException {
1100:  // Check if it is a root node
1101:  if (parent == null)
1102:  throw new UnsupportedOperationException("Cannot remove root node");
1103: 
1104:  synchronized (parent.lock) {
1105:  synchronized(this.lock) {
1106:  if (isRemoved())
1107:  throw new IllegalStateException("Node Removed");
1108: 
1109:  purge();
1110:  }
1111:  parent.childCache.remove(name);
1112:  }
1113:  }
1114: 
1115:  /**
1116:  * Private helper method used to completely remove this node.
1117:  * Called by <code>removeNode</code> with the parent node and this node
1118:  * locked.
1119:  * <p>
1120:  * Makes sure that all subnodes of this node are in the child cache,
1121:  * by calling <code>childSpi()</code> on any children not yet in the
1122:  * cache. Then for all children it locks the subnode and calls this method
1123:  * on that node. After all subnodes have been purged the child cache is
1124:  * cleared, this nodes removed flag is set and any listeners are called.
1125:  */
1126:  private void purge() throws BackingStoreException
1127:  {
1128:  // Make sure all children have an AbstractPreferences node in cache
1129:  String children[] = childrenNamesSpi();
1130:  for (int i = 0; i < children.length; i++) {
1131:  if (childCache.get(children[i]) == null)
1132:  childCache.put(children[i], childSpi(children[i]));
1133:  }
1134: 
1135:  // purge all children
1136:  Iterator i = childCache.values().iterator();
1137:  while (i.hasNext()) {
1138:  AbstractPreferences node = (AbstractPreferences) i.next();
1139:  synchronized(node.lock) {
1140:  node.purge();
1141:  }
1142:  }
1143: 
1144:  // Cache is empty now
1145:  childCache.clear();
1146: 
1147:  // remove this node
1148:  removeNodeSpi();
1149:  removed = true;
1150: 
1151:  if (nodeListeners != null)
1152:  fire(new NodeChangeEvent(parent, this), false);
1153:  }
1154: 
1155:  // listener methods
1156: 
1157:  /**
1158:  * Add a listener which is notified when a sub-node of this node
1159:  * is added or removed.
1160:  * @param listener the listener to add
1161:  */
1162:  public void addNodeChangeListener(NodeChangeListener listener)
1163:  {
1164:  synchronized (lock)
1165:  {
1166:  if (isRemoved())
1167:  throw new IllegalStateException("node has been removed");
1168:  if (listener == null)
1169:  throw new NullPointerException("listener is null");
1170:  if (nodeListeners == null)
1171:  nodeListeners = new ArrayList<NodeChangeListener>();
1172:  nodeListeners.add(listener);
1173:  }
1174:  }
1175: 
1176:  /**
1177:  * Add a listener which is notified when a value in this node
1178:  * is added, changed, or removed.
1179:  * @param listener the listener to add
1180:  */
1181:  public void addPreferenceChangeListener(PreferenceChangeListener listener)
1182:  {
1183:  synchronized (lock)
1184:  {
1185:  if (isRemoved())
1186:  throw new IllegalStateException("node has been removed");
1187:  if (listener == null)
1188:  throw new NullPointerException("listener is null");
1189:  if (preferenceListeners == null)
1190:  preferenceListeners = new ArrayList<PreferenceChangeListener>();
1191:  preferenceListeners.add(listener);
1192:  }
1193:  }
1194: 
1195:  /**
1196:  * Remove the indicated node change listener from the list of
1197:  * listeners to notify.
1198:  * @param listener the listener to remove
1199:  */
1200:  public void removeNodeChangeListener(NodeChangeListener listener)
1201:  {
1202:  synchronized (lock)
1203:  {
1204:  if (isRemoved())
1205:  throw new IllegalStateException("node has been removed");
1206:  if (listener == null)
1207:  throw new NullPointerException("listener is null");
1208:  if (nodeListeners != null)
1209:  nodeListeners.remove(listener);
1210:  }
1211:  }
1212: 
1213:  /**
1214:  * Remove the indicated preference change listener from the list of
1215:  * listeners to notify.
1216:  * @param listener the listener to remove
1217:  */
1218:  public void removePreferenceChangeListener (PreferenceChangeListener listener)
1219:  {
1220:  synchronized (lock)
1221:  {
1222:  if (isRemoved())
1223:  throw new IllegalStateException("node has been removed");
1224:  if (listener == null)
1225:  throw new NullPointerException("listener is null");
1226:  if (preferenceListeners != null)
1227:  preferenceListeners.remove(listener);
1228:  }
1229:  }
1230: 
1231:  /**
1232:  * Send a preference change event to all listeners. Note that
1233:  * the caller is responsible for holding the node's lock, and
1234:  * for checking that the list of listeners is not null.
1235:  * @param event the event to send
1236:  */
1237:  private void fire(final PreferenceChangeEvent event)
1238:  {
1239:  Iterator it = preferenceListeners.iterator();
1240:  while (it.hasNext())
1241:  {
1242:  final PreferenceChangeListener l = (PreferenceChangeListener) it.next();
1243:  EventDispatcher.dispatch(new Runnable()
1244:  {
1245:  public void run()
1246:  {
1247:  l.preferenceChange(event);
1248:  }
1249:  });
1250:  }
1251:  }
1252: 
1253:  /**
1254:  * Send a node change event to all listeners. Note that
1255:  * the caller is responsible for holding the node's lock, and
1256:  * for checking that the list of listeners is not null.
1257:  * @param event the event to send
1258:  */
1259:  private void fire(final NodeChangeEvent event, final boolean added)
1260:  {
1261:  Iterator it = nodeListeners.iterator();
1262:  while (it.hasNext())
1263:  {
1264:  final NodeChangeListener l = (NodeChangeListener) it.next();
1265:  EventDispatcher.dispatch(new Runnable()
1266:  {
1267:  public void run()
1268:  {
1269:  if (added)
1270:  l.childAdded(event);
1271:  else
1272:  l.childRemoved(event);
1273:  }
1274:  });
1275:  }
1276:  }
1277: 
1278:  // abstract spi methods
1279: 
1280:  /**
1281:  * Returns the names of the sub nodes of this preference node.
1282:  * This method only has to return any not yet cached child names,
1283:  * but may return all names if that is easier. It must not return
1284:  * null when there are no children, it has to return an empty array
1285:  * in that case. Since this method must consult the backing store to
1286:  * get all the sub node names it may throw a BackingStoreException.
1287:  * <p>
1288:  * Called by <code>childrenNames()</code> with this node locked.
1289:  */
1290:  protected abstract String[] childrenNamesSpi() throws BackingStoreException;
1291: 
1292:  /**
1293:  * Returns a child note with the given name.
1294:  * This method is called by the <code>node()</code> method (indirectly
1295:  * through the <code>getNode()</code> helper method) with this node locked
1296:  * if a sub node with this name does not already exist in the child cache.
1297:  * If the child node did not aleady exist in the backing store the boolean
1298:  * field <code>newNode</code> of the returned node should be set.
1299:  * <p>
1300:  * Note that this method should even return a non-null child node if the
1301:  * backing store is not available since it may not throw a
1302:  * <code>BackingStoreException</code>.
1303:  */
1304:  protected abstract AbstractPreferences childSpi(String name);
1305: 
1306:  /**
1307:  * Returns an (possibly empty) array with all the keys of the preference
1308:  * entries of this node.
1309:  * <p>
1310:  * Called by <code>keys()</code> with this node locked if this node has
1311:  * not been removed. May throw an exception when the backing store cannot
1312:  * be accessed.
1313:  *
1314:  * @exception BackingStoreException when the backing store cannot be 
1315:  * reached
1316:  */
1317:  protected abstract String[] keysSpi() throws BackingStoreException;
1318:  
1319:  /**
1320:  * Returns the value associated with the key in this preferences node or
1321:  * null when the key does not exist in this preferences node.
1322:  * <p>
1323:  * Called by <code>key()</code> with this node locked after checking that
1324:  * key is valid, not null and that the node has not been removed.
1325:  * <code>key()</code> will catch any exceptions that this method throws.
1326:  */
1327:  protected abstract String getSpi(String key);
1328: 
1329:  /**
1330:  * Sets the value of the given preferences entry for this node.
1331:  * The implementation is not required to propagate the change to the
1332:  * backing store immediately. It may not throw an exception when it tries
1333:  * to write to the backing store and that operation fails, the failure
1334:  * should be registered so a later invocation of <code>flush()</code>
1335:  * or <code>sync()</code> can signal the failure.
1336:  * <p>
1337:  * Called by <code>put()</code> with this node locked after checking that
1338:  * key and value are valid and non-null.
1339:  */
1340:  protected abstract void putSpi(String key, String value);
1341: 
1342:  /**
1343:  * Removes the given key entry from this preferences node.
1344:  * The implementation is not required to propagate the change to the
1345:  * backing store immediately. It may not throw an exception when it tries
1346:  * to write to the backing store and that operation fails, the failure
1347:  * should be registered so a later invocation of <code>flush()</code>
1348:  * or <code>sync()</code> can signal the failure.
1349:  * <p>
1350:  * Called by <code>remove()</code> with this node locked after checking
1351:  * that the key is valid and non-null.
1352:  */
1353:  protected abstract void removeSpi(String key);
1354: 
1355:  /**
1356:  * Writes all entries of this preferences node that have not yet been
1357:  * written to the backing store and possibly creates this node in the
1358:  * backing store, if it does not yet exist. Should only write changes to
1359:  * this node and not write changes to any subnodes.
1360:  * Note that the node can be already removed in this VM. To check if
1361:  * that is the case the implementation can call <code>isRemoved()</code>.
1362:  * <p>
1363:  * Called (indirectly) by <code>flush()</code> with this node locked.
1364:  */
1365:  protected abstract void flushSpi() throws BackingStoreException;
1366: 
1367:  /**
1368:  * Writes all entries of this preferences node that have not yet been
1369:  * written to the backing store and reads any entries that have changed
1370:  * in the backing store but that are not yet visible in this VM.
1371:  * Should only sync this node and not change any of the subnodes.
1372:  * Note that the node can be already removed in this VM. To check if
1373:  * that is the case the implementation can call <code>isRemoved()</code>.
1374:  * <p>
1375:  * Called (indirectly) by <code>sync()</code> with this node locked.
1376:  */
1377:  protected abstract void syncSpi() throws BackingStoreException;
1378: 
1379:  /**
1380:  * Clears this node from this VM and removes it from the backing store.
1381:  * After this method has been called the node is marked as removed.
1382:  * <p>
1383:  * Called (indirectly) by <code>removeNode()</code> with this node locked
1384:  * after all the sub nodes of this node have already been removed.
1385:  */
1386:  protected abstract void removeNodeSpi() throws BackingStoreException;
1387: }
Overview Package Class Use Source Tree Index Deprecated About
GNU Classpath (0.95)

AltStyle によって変換されたページ (->オリジナル) /