OpenCores
URL https://opencores.org/ocsvn/scarts/scarts/trunk

Subversion Repositories scarts

[/] [scarts/] [trunk/] [toolchain/] [scarts-gcc/] [gcc-4.1.1/] [libjava/] [classpath/] [java/] [util/] [prefs/] [AbstractPreferences.java] - Blame information for rev 14

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 14 jlechner
/* AbstractPreferences -- Partial implementation of a Preference node
2
   Copyright (C) 2001, 2003, 2004  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.NodeWriter;
42
 
43
import java.io.ByteArrayOutputStream;
44
import java.io.IOException;
45
import java.io.OutputStream;
46
import java.util.HashMap;
47
import java.util.Iterator;
48
import java.util.TreeSet;
49
 
50
/**
51
 * Partial implementation of a Preference node.
52
 *
53
 * @since 1.4
54
 * @author Mark Wielaard (mark@klomp.org)
55
 */
56
public abstract class AbstractPreferences extends Preferences {
57
 
58
    // protected fields
59
 
60
    /**
61
     * Object used to lock this preference node. Any thread only locks nodes
62
     * downwards when it has the lock on the current node. No method should
63
     * synchronize on the lock of any of its parent nodes while holding the
64
     * lock on the current node.
65
     */
66
    protected final Object lock = new Object();
67
 
68
    /**
69
     * Set to true in the contructor if the node did not exist in the backing
70
     * store when this preference node object was created. Should be set in
71
     * the contructor of a subclass. Defaults to false. Used to fire node
72
     * changed events.
73
     */
74
    protected boolean newNode = false;
75
 
76
    // private fields
77
 
78
    /**
79
     * The parent preferences node or null when this is the root node.
80
     */
81
    private final AbstractPreferences parent;
82
 
83
    /**
84
     * The name of this node.
85
     * Only when this is a root node (parent == null) the name is empty.
86
     * It has a maximum of 80 characters and cannot contain any '/' characters.
87
     */
88
    private final String name;
89
 
90
    /** True when this node has been remove, false otherwise. */
91
    private boolean removed = false;
92
 
93
    /**
94
     * Holds all the child names and nodes of this node that have been
95
     * accessed by earlier <code>getChild()</code> or <code>childSpi()</code>
96
     * invocations and that have not been removed.
97
     */
98
    private HashMap childCache = new HashMap();
99
 
100
    // constructor
101
 
102
    /**
103
     * Creates a new AbstractPreferences node with the given parent and name.
104
     *
105
     * @param parent the parent of this node or null when this is the root node
106
     * @param name the name of this node, can not be null, only 80 characters
107
     *             maximum, must be empty when parent is null and cannot
108
     *             contain any '/' characters
109
     * @exception IllegalArgumentException when name is null, greater then 80
110
     *            characters, not the empty string but parent is null or
111
     *            contains a '/' character
112
     */
113
    protected AbstractPreferences(AbstractPreferences parent, String name) {
114
        if (  (name == null)                            // name should be given
115
           || (name.length() > MAX_NAME_LENGTH)         // 80 characters max
116
           || (parent == null && name.length() != 0)    // root has no name
117
           || (parent != null && name.length() == 0)    // all other nodes do
118
           || (name.indexOf('/') != -1))                // must not contain '/'
119
            throw new IllegalArgumentException("Illegal name argument '"
120
                                               + name
121
                                               + "' (parent is "
122
                                               + (parent == null ? "" : "not ")
123
                                               + "null)");
124
        this.parent = parent;
125
        this.name = name;
126
    }
127
 
128
    // identification methods
129
 
130
    /**
131
     * Returns the absolute path name of this preference node.
132
     * The absolute path name of a node is the path name of its parent node
133
     * plus a '/' plus its own name. If the node is the root node and has no
134
     * parent then its path name is "" and its absolute path name is "/".
135
     */
136
    public String absolutePath() {
137
        if (parent == null)
138
            return "/";
139
        else
140
            return parent.path() + '/' + name;
141
    }
142
 
143
    /**
144
     * Private helper method for absolutePath. Returns the empty string for a
145
     * root node and otherwise the parentPath of its parent plus a '/'.
146
     */
147
    private String path() {
148
        if (parent == null)
149
            return "";
150
        else
151
            return parent.path() + '/' + name;
152
    }
153
 
154
    /**
155
     * Returns true if this node comes from the user preferences tree, false
156
     * if it comes from the system preferences tree.
157
     */
158
    public boolean isUserNode() {
159
        AbstractPreferences root = this;
160
        while (root.parent != null)
161
            root = root.parent;
162
        return root == Preferences.userRoot();
163
    }
164
 
165
    /**
166
     * Returns the name of this preferences node. The name of the node cannot
167
     * be null, can be mostly 80 characters and cannot contain any '/'
168
     * characters. The root node has as name "".
169
     */
170
    public String name() {
171
        return name;
172
    }
173
 
174
    /**
175
     * Returns the String given by
176
     * <code>
177
     * (isUserNode() ? "User":"System") + " Preference Node: " + absolutePath()
178
     * </code>
179
     */
180
    public String toString() {
181
        return (isUserNode() ? "User":"System")
182
               + " Preference Node: "
183
               + absolutePath();
184
    }
185
 
186
    /**
187
     * Returns all known unremoved children of this node.
188
     *
189
     * @return All known unremoved children of this node
190
     */
191
    protected final AbstractPreferences[] cachedChildren()
192
    {
193
      return (AbstractPreferences[]) childCache.values().toArray();
194
    }
195
 
196
    /**
197
     * Returns all the direct sub nodes of this preferences node.
198
     * Needs access to the backing store to give a meaningfull answer.
199
     * <p>
200
     * This implementation locks this node, checks if the node has not yet
201
     * been removed and throws an <code>IllegalStateException</code> when it
202
     * has been. Then it creates a new <code>TreeSet</code> and adds any
203
     * already cached child nodes names. To get any uncached names it calls
204
     * <code>childrenNamesSpi()</code> and adds the result to the set. Finally
205
     * it calls <code>toArray()</code> on the created set. When the call to
206
     * <code>childrenNamesSpi</code> thows an <code>BackingStoreException</code>
207
     * this method will not catch that exception but propagate the exception
208
     * to the caller.
209
     *
210
     * @exception BackingStoreException when the backing store cannot be
211
     *            reached
212
     * @exception IllegalStateException when this node has been removed
213
     */
214
    public String[] childrenNames() throws BackingStoreException {
215
        synchronized(lock) {
216
            if (isRemoved())
217
                throw new IllegalStateException("Node removed");
218
 
219
            TreeSet childrenNames = new TreeSet();
220
 
221
            // First get all cached node names
222
            childrenNames.addAll(childCache.keySet());
223
 
224
            // Then add any others
225
            String names[] = childrenNamesSpi();
226
            for (int i = 0; i < names.length; i++) {
227
                childrenNames.add(names[i]);
228
            }
229
 
230
            // And return the array of names
231
            String[] children = new String[childrenNames.size()];
232
            childrenNames.toArray(children);
233
            return children;
234
 
235
        }
236
    }
237
 
238
    /**
239
     * Returns a sub node of this preferences node if the given path is
240
     * relative (does not start with a '/') or a sub node of the root
241
     * if the path is absolute (does start with a '/').
242
     * <p>
243
     * This method first locks this node and checks if the node has not been
244
     * removed, if it has been removed it throws an exception. Then if the
245
     * path is relative (does not start with a '/') it checks if the path is
246
     * legal (does not end with a '/' and has no consecutive '/' characters).
247
     * Then it recursively gets a name from the path, gets the child node
248
     * from the child-cache of this node or calls the <code>childSpi()</code>
249
     * method to create a new child sub node. This is done recursively on the
250
     * newly created sub node with the rest of the path till the path is empty.
251
     * If the path is absolute (starts with a '/') the lock on this node is
252
     * droped and this method is called on the root of the preferences tree
253
     * with as argument the complete path minus the first '/'.
254
     *
255
     * @exception IllegalStateException if this node has been removed
256
     * @exception IllegalArgumentException if the path contains two or more
257
     * consecutive '/' characters, ends with a '/' charactor and is not the
258
     * string "/" (indicating the root node) or any name on the path is more
259
     * then 80 characters long
260
     */
261
    public Preferences node(String path) {
262
        synchronized(lock) {
263
            if (isRemoved())
264
                throw new IllegalStateException("Node removed");
265
 
266
            // Is it a relative path?
267
            if (!path.startsWith("/")) {
268
 
269
                // Check if it is a valid path
270
                if (path.indexOf("//") != -1 || path.endsWith("/"))
271
                    throw new IllegalArgumentException(path);
272
 
273
                return getNode(path);
274
            }
275
        }
276
 
277
        // path started with a '/' so it is absolute
278
        // we drop the lock and start from the root (omitting the first '/')
279
        Preferences root = isUserNode() ? userRoot() : systemRoot();
280
        return root.node(path.substring(1));
281
 
282
    }
283
 
284
    /**
285
     * Private helper method for <code>node()</code>. Called with this node
286
     * locked. Returns this node when path is the empty string, if it is not
287
     * empty the next node name is taken from the path (all chars till the
288
     * next '/' or end of path string) and the node is either taken from the
289
     * child-cache of this node or the <code>childSpi()</code> method is called
290
     * on this node with the name as argument. Then this method is called
291
     * recursively on the just constructed child node with the rest of the
292
     * path.
293
     *
294
     * @param path should not end with a '/' character and should not contain
295
     *        consecutive '/' characters
296
     * @exception IllegalArgumentException if path begins with a name that is
297
     *            larger then 80 characters.
298
     */
299
    private Preferences getNode(String path) {
300
        // if mark is dom then goto end
301
 
302
        // Empty String "" indicates this node
303
        if (path.length() == 0)
304
            return this;
305
 
306
        // Calculate child name and rest of path
307
        String childName;
308
        String childPath;
309
        int nextSlash = path.indexOf('/');
310
        if (nextSlash == -1) {
311
            childName = path;
312
            childPath = "";
313
        } else {
314
            childName = path.substring(0, nextSlash);
315
            childPath = path.substring(nextSlash+1);
316
        }
317
 
318
        // Get the child node
319
        AbstractPreferences child;
320
        child = (AbstractPreferences)childCache.get(childName);
321
        if (child == null) {
322
 
323
            if (childName.length() > MAX_NAME_LENGTH)
324
               throw new IllegalArgumentException(childName);
325
 
326
            // Not in childCache yet so create a new sub node
327
            child = childSpi(childName);
328
            // XXX - check if node is new
329
            childCache.put(childName, child);
330
        }
331
 
332
        // Lock the child and go down
333
        synchronized(child.lock) {
334
            return child.getNode(childPath);
335
        }
336
    }
337
 
338
    /**
339
     * Returns true if the node that the path points to exists in memory or
340
     * in the backing store. Otherwise it returns false or an exception is
341
     * thrown. When this node is removed the only valid parameter is the
342
     * empty string (indicating this node), the return value in that case
343
     * will be false.
344
     *
345
     * @exception BackingStoreException when the backing store cannot be
346
     *            reached
347
     * @exception IllegalStateException if this node has been removed
348
     *            and the path is not the empty string (indicating this node)
349
     * @exception IllegalArgumentException if the path contains two or more
350
     * consecutive '/' characters, ends with a '/' charactor and is not the
351
     * string "/" (indicating the root node) or any name on the path is more
352
     * then 80 characters long
353
     */
354
    public boolean nodeExists(String path) throws BackingStoreException {
355
        synchronized(lock) {
356
            if (isRemoved() && path.length() != 0)
357
                throw new IllegalStateException("Node removed");
358
 
359
            // Is it a relative path?
360
            if (!path.startsWith("/")) {
361
 
362
                // Check if it is a valid path
363
                if (path.indexOf("//") != -1 || path.endsWith("/"))
364
                    throw new IllegalArgumentException(path);
365
 
366
                return existsNode(path);
367
            }
368
        }
369
 
370
        // path started with a '/' so it is absolute
371
        // we drop the lock and start from the root (omitting the first '/')
372
        Preferences root = isUserNode() ? userRoot() : systemRoot();
373
        return root.nodeExists(path.substring(1));
374
 
375
    }
376
 
377
    private boolean existsNode(String path) throws BackingStoreException {
378
 
379
        // Empty String "" indicates this node
380
        if (path.length() == 0)
381
            return(!isRemoved());
382
 
383
        // Calculate child name and rest of path
384
        String childName;
385
        String childPath;
386
        int nextSlash = path.indexOf('/');
387
        if (nextSlash == -1) {
388
            childName = path;
389
            childPath = "";
390
        } else {
391
            childName = path.substring(0, nextSlash);
392
            childPath = path.substring(nextSlash+1);
393
        }
394
 
395
        // Get the child node
396
        AbstractPreferences child;
397
        child = (AbstractPreferences)childCache.get(childName);
398
        if (child == null) {
399
 
400
            if (childName.length() > MAX_NAME_LENGTH)
401
               throw new IllegalArgumentException(childName);
402
 
403
            // Not in childCache yet so create a new sub node
404
            child = getChild(childName);
405
 
406
            if (child == null)
407
                return false;
408
 
409
            childCache.put(childName, child);
410
        }
411
 
412
        // Lock the child and go down
413
        synchronized(child.lock) {
414
            return child.existsNode(childPath);
415
        }
416
    }
417
 
418
    /**
419
     * Returns the child sub node if it exists in the backing store or null
420
     * if it does not exist. Called (indirectly) by <code>nodeExists()</code>
421
     * when a child node name can not be found in the cache.
422
     * <p>
423
     * Gets the lock on this node, calls <code>childrenNamesSpi()</code> to
424
     * get an array of all (possibly uncached) children and compares the
425
     * given name with the names in the array. If the name is found in the
426
     * array <code>childSpi()</code> is called to get an instance, otherwise
427
     * null is returned.
428
     *
429
     * @exception BackingStoreException when the backing store cannot be
430
     *            reached
431
     */
432
    protected AbstractPreferences getChild(String name)
433
                                    throws BackingStoreException
434
    {
435
        synchronized(lock) {
436
            // Get all the names (not yet in the cache)
437
            String[] names = childrenNamesSpi();
438
            for (int i=0; i < names.length; i++)
439
                if (name.equals(names[i]))
440
                    return childSpi(name);
441
 
442
            // No child with that name found
443
            return null;
444
        }
445
    }
446
 
447
    /**
448
     * Returns true if this node has been removed with the
449
     * <code>removeNode()</code> method, false otherwise.
450
     * <p>
451
     * Gets the lock on this node and then returns a boolean field set by
452
     * <code>removeNode</code> methods.
453
     */
454
    protected boolean isRemoved() {
455
        synchronized(lock) {
456
            return removed;
457
        }
458
    }
459
 
460
    /**
461
     * Returns the parent preferences node of this node or null if this is
462
     * the root of the preferences tree.
463
     * <p>
464
     * Gets the lock on this node, checks that the node has not been removed
465
     * and returns the parent given to the constructor.
466
     *
467
     * @exception IllegalStateException if this node has been removed
468
     */
469
    public Preferences parent() {
470
        synchronized(lock) {
471
            if (isRemoved())
472
                throw new IllegalStateException("Node removed");
473
 
474
            return parent;
475
        }
476
    }
477
 
478
    // export methods
479
 
480
    /**
481
     * XXX
482
     */
483
    public void exportNode(OutputStream os)
484
                                    throws BackingStoreException,
485
                                           IOException
486
    {
487
        NodeWriter nodeWriter = new NodeWriter(this, os);
488
        nodeWriter.writePrefs();
489
    }
490
 
491
    /**
492
     * XXX
493
     */
494
    public void exportSubtree(OutputStream os)
495
                                    throws BackingStoreException,
496
                                           IOException
497
    {
498
        NodeWriter nodeWriter = new NodeWriter(this, os);
499
        nodeWriter.writePrefsTree();
500
    }
501
 
502
    // preference entry manipulation methods
503
 
504
    /**
505
     * Returns an (possibly empty) array with all the keys of the preference
506
     * entries of this node.
507
     * <p>
508
     * This method locks this node and checks if the node has not been
509
     * removed, if it has been removed it throws an exception, then it returns
510
     * the result of calling <code>keysSpi()</code>.
511
     *
512
     * @exception BackingStoreException when the backing store cannot be
513
     *            reached
514
     * @exception IllegalStateException if this node has been removed
515
     */
516
    public String[] keys() throws BackingStoreException {
517
        synchronized(lock) {
518
            if (isRemoved())
519
                throw new IllegalStateException("Node removed");
520
 
521
            return keysSpi();
522
        }
523
    }
524
 
525
 
526
    /**
527
     * Returns the value associated with the key in this preferences node. If
528
     * the default value of the key cannot be found in the preferences node
529
     * entries or something goes wrong with the backing store the supplied
530
     * default value is returned.
531
     * <p>
532
     * Checks that key is not null and not larger then 80 characters,
533
     * locks this node, and checks that the node has not been removed.
534
     * Then it calls <code>keySpi()</code> and returns
535
     * the result of that method or the given default value if it returned
536
     * null or throwed an exception.
537
     *
538
     * @exception IllegalArgumentException if key is larger then 80 characters
539
     * @exception IllegalStateException if this node has been removed
540
     * @exception NullPointerException if key is null
541
     */
542
    public String get(String key, String defaultVal) {
543
        if (key.length() > MAX_KEY_LENGTH)
544
            throw new IllegalArgumentException(key);
545
 
546
        synchronized(lock) {
547
            if (isRemoved())
548
                throw new IllegalStateException("Node removed");
549
 
550
            String value;
551
            try {
552
                value = getSpi(key);
553
            } catch (ThreadDeath death) {
554
                throw death;
555
            } catch (Throwable t) {
556
                value = null;
557
            }
558
 
559
            if (value != null) {
560
                return value;
561
            } else {
562
                return defaultVal;
563
            }
564
        }
565
    }
566
 
567
    /**
568
     * Convenience method for getting the given entry as a boolean.
569
     * When the string representation of the requested entry is either
570
     * "true" or "false" (ignoring case) then that value is returned,
571
     * otherwise the given default boolean value is returned.
572
     *
573
     * @exception IllegalArgumentException if key is larger then 80 characters
574
     * @exception IllegalStateException if this node has been removed
575
     * @exception NullPointerException if key is null
576
     */
577
    public boolean getBoolean(String key, boolean defaultVal) {
578
        String value = get(key, null);
579
 
580
        if ("true".equalsIgnoreCase(value))
581
            return true;
582
 
583
        if ("false".equalsIgnoreCase(value))
584
            return false;
585
 
586
        return defaultVal;
587
    }
588
 
589
    /**
590
     * Convenience method for getting the given entry as a byte array.
591
     * When the string representation of the requested entry is a valid
592
     * Base64 encoded string (without any other characters, such as newlines)
593
     * then the decoded Base64 string is returned as byte array,
594
     * otherwise the given default byte array value is returned.
595
     *
596
     * @exception IllegalArgumentException if key is larger then 80 characters
597
     * @exception IllegalStateException if this node has been removed
598
     * @exception NullPointerException if key is null
599
     */
600
    public byte[] getByteArray(String key, byte[] defaultVal) {
601
        String value = get(key, null);
602
 
603
        byte[] b = null;
604
        if (value != null) {
605
            b = decode64(value);
606
        }
607
 
608
        if (b != null)
609
            return b;
610
        else
611
            return defaultVal;
612
    }
613
 
614
    /**
615
     * Helper method for decoding a Base64 string as an byte array.
616
     * Returns null on encoding error. This method does not allow any other
617
     * characters present in the string then the 65 special base64 chars.
618
     */
619
    private static byte[] decode64(String s) {
620
        ByteArrayOutputStream bs = new ByteArrayOutputStream((s.length()/4)*3);
621
        char[] c = new char[s.length()];
622
        s.getChars(0, s.length(), c, 0);
623
 
624
        // Convert from base64 chars
625
        int endchar = -1;
626
        for(int j = 0; j < c.length && endchar == -1; j++) {
627
            if (c[j] >= 'A' && c[j] <= 'Z') {
628
                c[j] -= 'A';
629
            } else if (c[j] >= 'a' && c[j] <= 'z') {
630
                c[j] = (char) (c[j] + 26 - 'a');
631
            } else if (c[j] >= '0' && c[j] <= '9') {
632
                c[j] = (char) (c[j] + 52 - '0');
633
            } else if (c[j] == '+') {
634
                c[j] = 62;
635
            } else if (c[j] == '/') {
636
                c[j] = 63;
637
            } else if (c[j] == '=') {
638
                endchar = j;
639
            } else {
640
                return null; // encoding exception
641
            }
642
        }
643
 
644
        int remaining = endchar == -1 ? c.length : endchar;
645
        int i = 0;
646
        while (remaining > 0) {
647
            // Four input chars (6 bits) are decoded as three bytes as
648
            // 000000 001111 111122 222222
649
 
650
            byte b0 = (byte) (c[i] << 2);
651
            if (remaining >= 2) {
652
                b0 += (c[i+1] & 0x30) >> 4;
653
            }
654
            bs.write(b0);
655
 
656
            if (remaining >= 3) {
657
                byte b1 = (byte) ((c[i+1] & 0x0F) << 4);
658
                b1 += (byte) ((c[i+2] & 0x3C) >> 2);
659
                bs.write(b1);
660
            }
661
 
662
            if (remaining >= 4) {
663
                byte b2 = (byte) ((c[i+2] & 0x03) << 6);
664
                b2 += c[i+3];
665
                bs.write(b2);
666
            }
667
 
668
            i += 4;
669
            remaining -= 4;
670
        }
671
 
672
        return bs.toByteArray();
673
    }
674
 
675
    /**
676
     * Convenience method for getting the given entry as a double.
677
     * When the string representation of the requested entry can be decoded
678
     * with <code>Double.parseDouble()</code> then that double is returned,
679
     * otherwise the given default double value is returned.
680
     *
681
     * @exception IllegalArgumentException if key is larger then 80 characters
682
     * @exception IllegalStateException if this node has been removed
683
     * @exception NullPointerException if key is null
684
     */
685
    public double getDouble(String key, double defaultVal) {
686
        String value = get(key, null);
687
 
688
        if (value != null) {
689
            try {
690
                return Double.parseDouble(value);
691
            } catch (NumberFormatException nfe) { /* ignore */ }
692
        }
693
 
694
        return defaultVal;
695
    }
696
 
697
    /**
698
     * Convenience method for getting the given entry as a float.
699
     * When the string representation of the requested entry can be decoded
700
     * with <code>Float.parseFloat()</code> then that float is returned,
701
     * otherwise the given default float value is returned.
702
     *
703
     * @exception IllegalArgumentException if key is larger then 80 characters
704
     * @exception IllegalStateException if this node has been removed
705
     * @exception NullPointerException if key is null
706
     */
707
    public float getFloat(String key, float defaultVal) {
708
        String value = get(key, null);
709
 
710
        if (value != null) {
711
            try {
712
                return Float.parseFloat(value);
713
            } catch (NumberFormatException nfe) { /* ignore */ }
714
        }
715
 
716
        return defaultVal;
717
    }
718
 
719
    /**
720
     * Convenience method for getting the given entry as an integer.
721
     * When the string representation of the requested entry can be decoded
722
     * with <code>Integer.parseInt()</code> then that integer is returned,
723
     * otherwise the given default integer value is returned.
724
     *
725
     * @exception IllegalArgumentException if key is larger then 80 characters
726
     * @exception IllegalStateException if this node has been removed
727
     * @exception NullPointerException if key is null
728
     */
729
    public int getInt(String key, int defaultVal) {
730
        String value = get(key, null);
731
 
732
        if (value != null) {
733
            try {
734
                return Integer.parseInt(value);
735
            } catch (NumberFormatException nfe) { /* ignore */ }
736
        }
737
 
738
        return defaultVal;
739
    }
740
 
741
    /**
742
     * Convenience method for getting the given entry as a long.
743
     * When the string representation of the requested entry can be decoded
744
     * with <code>Long.parseLong()</code> then that long is returned,
745
     * otherwise the given default long value is returned.
746
     *
747
     * @exception IllegalArgumentException if key is larger then 80 characters
748
     * @exception IllegalStateException if this node has been removed
749
     * @exception NullPointerException if key is null
750
     */
751
    public long getLong(String key, long defaultVal) {
752
        String value = get(key, null);
753
 
754
        if (value != null) {
755
            try {
756
                return Long.parseLong(value);
757
            } catch (NumberFormatException nfe) { /* ignore */ }
758
        }
759
 
760
        return defaultVal;
761
    }
762
 
763
    /**
764
     * Sets the value of the given preferences entry for this node.
765
     * Key and value cannot be null, the key cannot exceed 80 characters
766
     * and the value cannot exceed 8192 characters.
767
     * <p>
768
     * The result will be immediatly visible in this VM, but may not be
769
     * immediatly written to the backing store.
770
     * <p>
771
     * Checks that key and value are valid, locks this node, and checks that
772
     * the node has not been removed. Then it calls <code>putSpi()</code>.
773
     *
774
     * @exception NullPointerException if either key or value are null
775
     * @exception IllegalArgumentException if either key or value are to large
776
     * @exception IllegalStateException when this node has been removed
777
     */
778
    public void put(String key, String value) {
779
        if (key.length() > MAX_KEY_LENGTH
780
            || value.length() > MAX_VALUE_LENGTH)
781
            throw new IllegalArgumentException("key ("
782
                                               + key.length() + ")"
783
                                               + " or value ("
784
                                               + value.length() + ")"
785
                                               + " to large");
786
        synchronized(lock) {
787
            if (isRemoved())
788
                throw new IllegalStateException("Node removed");
789
 
790
            putSpi(key, value);
791
 
792
            // XXX - fire events
793
        }
794
 
795
    }
796
 
797
    /**
798
     * Convenience method for setting the given entry as a boolean.
799
     * The boolean is converted with <code>Boolean.toString(value)</code>
800
     * and then stored in the preference entry as that string.
801
     *
802
     * @exception NullPointerException if key is null
803
     * @exception IllegalArgumentException if the key length is to large
804
     * @exception IllegalStateException when this node has been removed
805
     */
806
    public void putBoolean(String key, boolean value) {
807
        put(key, String.valueOf(value));
808
        // XXX - Use when using 1.4 compatible Boolean
809
        // put(key, Boolean.toString(value));
810
    }
811
 
812
    /**
813
     * Convenience method for setting the given entry as an array of bytes.
814
     * The byte array is converted to a Base64 encoded string
815
     * and then stored in the preference entry as that string.
816
     * <p>
817
     * Note that a byte array encoded as a Base64 string will be about 1.3
818
     * times larger then the original length of the byte array, which means
819
     * that the byte array may not be larger about 6 KB.
820
     *
821
     * @exception NullPointerException if either key or value are null
822
     * @exception IllegalArgumentException if either key or value are to large
823
     * @exception IllegalStateException when this node has been removed
824
     */
825
    public void putByteArray(String key, byte[] value) {
826
        put(key, encode64(value));
827
    }
828
 
829
    /**
830
     * Helper method for encoding an array of bytes as a Base64 String.
831
     */
832
    private static String encode64(byte[] b) {
833
        StringBuffer sb = new StringBuffer((b.length/3)*4);
834
 
835
        int i = 0;
836
        int remaining = b.length;
837
        char c[] = new char[4];
838
        while (remaining > 0) {
839
            // Three input bytes are encoded as four chars (6 bits) as
840
            // 00000011 11112222 22333333
841
 
842
            c[0] = (char) ((b[i] & 0xFC) >> 2);
843
            c[1] = (char) ((b[i] & 0x03) << 4);
844
            if (remaining >= 2) {
845
                c[1] += (char) ((b[i+1] & 0xF0) >> 4);
846
                c[2] = (char) ((b[i+1] & 0x0F) << 2);
847
                if (remaining >= 3) {
848
                    c[2] += (char) ((b[i+2] & 0xC0) >> 6);
849
                    c[3] = (char) (b[i+2] & 0x3F);
850
                } else {
851
                    c[3] = 64;
852
                }
853
            } else {
854
                c[2] = 64;
855
                c[3] = 64;
856
            }
857
 
858
            // Convert to base64 chars
859
            for(int j = 0; j < 4; j++) {
860
                if (c[j] < 26) {
861
                    c[j] += 'A';
862
                } else if (c[j] < 52) {
863
                    c[j] = (char) (c[j] - 26 + 'a');
864
                } else if (c[j] < 62) {
865
                    c[j] = (char) (c[j] - 52 + '0');
866
                } else if (c[j] == 62) {
867
                    c[j] = '+';
868
                } else if (c[j] == 63) {
869
                    c[j] = '/';
870
                } else {
871
                    c[j] = '=';
872
                }
873
            }
874
 
875
            sb.append(c);
876
            i += 3;
877
            remaining -= 3;
878
        }
879
 
880
        return sb.toString();
881
    }
882
 
883
    /**
884
     * Convenience method for setting the given entry as a double.
885
     * The double is converted with <code>Double.toString(double)</code>
886
     * and then stored in the preference entry as that string.
887
     *
888
     * @exception NullPointerException if the key is null
889
     * @exception IllegalArgumentException if the key length is to large
890
     * @exception IllegalStateException when this node has been removed
891
     */
892
    public void putDouble(String key, double value) {
893
        put(key, Double.toString(value));
894
    }
895
 
896
    /**
897
     * Convenience method for setting the given entry as a float.
898
     * The float is converted with <code>Float.toString(float)</code>
899
     * and then stored in the preference entry as that string.
900
     *
901
     * @exception NullPointerException if the key is null
902
     * @exception IllegalArgumentException if the key length is to large
903
     * @exception IllegalStateException when this node has been removed
904
     */
905
    public void putFloat(String key, float value) {
906
        put(key, Float.toString(value));
907
    }
908
 
909
    /**
910
     * Convenience method for setting the given entry as an integer.
911
     * The integer is converted with <code>Integer.toString(int)</code>
912
     * and then stored in the preference entry as that string.
913
     *
914
     * @exception NullPointerException if the key is null
915
     * @exception IllegalArgumentException if the key length is to large
916
     * @exception IllegalStateException when this node has been removed
917
     */
918
    public void putInt(String key, int value) {
919
        put(key, Integer.toString(value));
920
    }
921
 
922
    /**
923
     * Convenience method for setting the given entry as a long.
924
     * The long is converted with <code>Long.toString(long)</code>
925
     * and then stored in the preference entry as that string.
926
     *
927
     * @exception NullPointerException if the key is null
928
     * @exception IllegalArgumentException if the key length is to large
929
     * @exception IllegalStateException when this node has been removed
930
     */
931
    public void putLong(String key, long value) {
932
        put(key, Long.toString(value));
933
    }
934
 
935
    /**
936
     * Removes the preferences entry from this preferences node.
937
     * <p>
938
     * The result will be immediatly visible in this VM, but may not be
939
     * immediatly written to the backing store.
940
     * <p>
941
     * This implementation checks that the key is not larger then 80
942
     * characters, gets the lock of this node, checks that the node has
943
     * not been removed and calls <code>removeSpi</code> with the given key.
944
     *
945
     * @exception NullPointerException if the key is null
946
     * @exception IllegalArgumentException if the key length is to large
947
     * @exception IllegalStateException when this node has been removed
948
     */
949
    public void remove(String key) {
950
        if (key.length() > MAX_KEY_LENGTH)
951
            throw new IllegalArgumentException(key);
952
 
953
        synchronized(lock) {
954
            if (isRemoved())
955
                throw new IllegalStateException("Node removed");
956
 
957
            removeSpi(key);
958
        }
959
    }
960
 
961
    /**
962
     * Removes all entries from this preferences node. May need access to the
963
     * backing store to get and clear all entries.
964
     * <p>
965
     * The result will be immediatly visible in this VM, but may not be
966
     * immediatly written to the backing store.
967
     * <p>
968
     * This implementation locks this node, checks that the node has not been
969
     * removed and calls <code>keys()</code> to get a complete array of keys
970
     * for this node. For every key found <code>removeSpi()</code> is called.
971
     *
972
     * @exception BackingStoreException when the backing store cannot be
973
     *            reached
974
     * @exception IllegalStateException if this node has been removed
975
     */
976
    public void clear() throws BackingStoreException {
977
        synchronized(lock) {
978
            if (isRemoved())
979
                throw new IllegalStateException("Node Removed");
980
 
981
            String[] keys = keys();
982
            for (int i = 0; i < keys.length; i++) {
983
                removeSpi(keys[i]);
984
            }
985
        }
986
    }
987
 
988
    /**
989
     * Writes all preference changes on this and any subnode that have not
990
     * yet been written to the backing store. This has no effect on the
991
     * preference entries in this VM, but it makes sure that all changes
992
     * are visible to other programs (other VMs might need to call the
993
     * <code>sync()</code> method to actually see the changes to the backing
994
     * store.
995
     * <p>
996
     * Locks this node, calls the <code>flushSpi()</code> method, gets all
997
     * the (cached - already existing in this VM) subnodes and then calls
998
     * <code>flushSpi()</code> on every subnode with this node unlocked and
999
     * only that particular subnode locked.
1000
     *
1001
     * @exception BackingStoreException when the backing store cannot be
1002
     *            reached
1003
     */
1004
    public void flush() throws BackingStoreException {
1005
        flushNode(false);
1006
    }
1007
 
1008
    /**
1009
     * Writes and reads all preference changes to and from this and any
1010
     * subnodes. This makes sure that all local changes are written to the
1011
     * backing store and that all changes to the backing store are visible
1012
     * in this preference node (and all subnodes).
1013
     * <p>
1014
     * Checks that this node is not removed, locks this node, calls the
1015
     * <code>syncSpi()</code> method, gets all the subnodes and then calls
1016
     * <code>syncSpi()</code> on every subnode with this node unlocked and
1017
     * only that particular subnode locked.
1018
     *
1019
     * @exception BackingStoreException when the backing store cannot be
1020
     *            reached
1021
     * @exception IllegalStateException if this node has been removed
1022
     */
1023
    public void sync() throws BackingStoreException {
1024
        flushNode(true);
1025
    }
1026
 
1027
 
1028
    /**
1029
     * Private helper method that locks this node and calls either
1030
     * <code>flushSpi()</code> if <code>sync</code> is false, or
1031
     * <code>flushSpi()</code> if <code>sync</code> is true. Then it gets all
1032
     * the currently cached subnodes. For every subnode it calls this method
1033
     * recursively with this node no longer locked.
1034
     * <p>
1035
     * Called by either <code>flush()</code> or <code>sync()</code>
1036
     */
1037
    private void flushNode(boolean sync) throws BackingStoreException {
1038
        String[] keys = null;
1039
        synchronized(lock) {
1040
            if (sync) {
1041
                syncSpi();
1042
            } else {
1043
                flushSpi();
1044
            }
1045
            keys = (String[]) childCache.keySet().toArray(new String[]{});
1046
        }
1047
 
1048
        if (keys != null) {
1049
            for (int i = 0; i < keys.length; i++) {
1050
                // Have to lock this node again to access the childCache
1051
                AbstractPreferences subNode;
1052
                synchronized(this) {
1053
                    subNode = (AbstractPreferences) childCache.get(keys[i]);
1054
                }
1055
 
1056
                // The child could already have been removed from the cache
1057
                if (subNode != null) {
1058
                    subNode.flushNode(sync);
1059
                }
1060
            }
1061
        }
1062
    }
1063
 
1064
    /**
1065
     * Removes this and all subnodes from the backing store and clears all
1066
     * entries. After removal this instance will not be useable (except for
1067
     * a few methods that don't throw a <code>InvalidStateException</code>),
1068
     * even when a new node with the same path name is created this instance
1069
     * will not be usable again.
1070
     * <p>
1071
     * Checks that this is not a root node. If not it locks the parent node,
1072
     * then locks this node and checks that the node has not yet been removed.
1073
     * Then it makes sure that all subnodes of this node are in the child cache,
1074
     * by calling <code>childSpi()</code> on any children not yet in the cache.
1075
     * Then for all children it locks the subnode and removes it. After all
1076
     * subnodes have been purged the child cache is cleared, this nodes removed
1077
     * flag is set and any listeners are called. Finally this node is removed
1078
     * from the child cache of the parent node.
1079
     *
1080
     * @exception BackingStoreException when the backing store cannot be
1081
     *            reached
1082
     * @exception IllegalStateException if this node has already been removed
1083
     * @exception UnsupportedOperationException if this is a root node
1084
     */
1085
    public void removeNode() throws BackingStoreException {
1086
        // Check if it is a root node
1087
        if (parent == null)
1088
            throw new UnsupportedOperationException("Cannot remove root node");
1089
 
1090
        synchronized(parent) {
1091
            synchronized(this) {
1092
                if (isRemoved())
1093
                    throw new IllegalStateException("Node Removed");
1094
 
1095
                purge();
1096
            }
1097
            parent.childCache.remove(name);
1098
        }
1099
    }
1100
 
1101
    /**
1102
     * Private helper method used to completely remove this node.
1103
     * Called by <code>removeNode</code> with the parent node and this node
1104
     * locked.
1105
     * <p>
1106
     * Makes sure that all subnodes of this node are in the child cache,
1107
     * by calling <code>childSpi()</code> on any children not yet in the
1108
     * cache. Then for all children it locks the subnode and calls this method
1109
     * on that node. After all subnodes have been purged the child cache is
1110
     * cleared, this nodes removed flag is set and any listeners are called.
1111
     */
1112
    private void purge() throws BackingStoreException
1113
    {
1114
        // Make sure all children have an AbstractPreferences node in cache
1115
        String children[] = childrenNamesSpi();
1116
        for (int i = 0; i < children.length; i++) {
1117
            if (childCache.get(children[i]) == null)
1118
                childCache.put(children[i], childSpi(children[i]));
1119
        }
1120
 
1121
        // purge all children
1122
        Iterator i = childCache.values().iterator();
1123
        while (i.hasNext()) {
1124
            AbstractPreferences node = (AbstractPreferences) i.next();
1125
            synchronized(node) {
1126
                node.purge();
1127
            }
1128
        }
1129
 
1130
        // Cache is empty now
1131
        childCache.clear();
1132
 
1133
        // remove this node
1134
        removeNodeSpi();
1135
        removed = true;
1136
 
1137
        // XXX - check for listeners
1138
    }
1139
 
1140
    // listener methods
1141
 
1142
    /**
1143
     * XXX
1144
     */
1145
    public void addNodeChangeListener(NodeChangeListener listener) {
1146
        // XXX
1147
    }
1148
 
1149
    public void addPreferenceChangeListener(PreferenceChangeListener listener) {
1150
        // XXX
1151
    }
1152
 
1153
    public void removeNodeChangeListener(NodeChangeListener listener) {
1154
        // XXX
1155
    }
1156
 
1157
    public void removePreferenceChangeListener
1158
                            (PreferenceChangeListener listener)
1159
    {
1160
        // XXX
1161
    }
1162
 
1163
    // abstract spi methods
1164
 
1165
    /**
1166
     * Returns the names of the sub nodes of this preference node.
1167
     * This method only has to return any not yet cached child names,
1168
     * but may return all names if that is easier. It must not return
1169
     * null when there are no children, it has to return an empty array
1170
     * in that case. Since this method must consult the backing store to
1171
     * get all the sub node names it may throw a BackingStoreException.
1172
     * <p>
1173
     * Called by <code>childrenNames()</code> with this node locked.
1174
     */
1175
    protected abstract String[] childrenNamesSpi() throws BackingStoreException;
1176
 
1177
    /**
1178
     * Returns a child note with the given name.
1179
     * This method is called by the <code>node()</code> method (indirectly
1180
     * through the <code>getNode()</code> helper method) with this node locked
1181
     * if a sub node with this name does not already exist in the child cache.
1182
     * If the child node did not aleady exist in the backing store the boolean
1183
     * field <code>newNode</code> of the returned node should be set.
1184
     * <p>
1185
     * Note that this method should even return a non-null child node if the
1186
     * backing store is not available since it may not throw a
1187
     * <code>BackingStoreException</code>.
1188
     */
1189
    protected abstract AbstractPreferences childSpi(String name);
1190
 
1191
    /**
1192
     * Returns an (possibly empty) array with all the keys of the preference
1193
     * entries of this node.
1194
     * <p>
1195
     * Called by <code>keys()</code> with this node locked if this node has
1196
     * not been removed. May throw an exception when the backing store cannot
1197
     * be accessed.
1198
     *
1199
     * @exception BackingStoreException when the backing store cannot be
1200
     *            reached
1201
     */
1202
    protected abstract String[] keysSpi() throws BackingStoreException;
1203
 
1204
    /**
1205
     * Returns the value associated with the key in this preferences node or
1206
     * null when the key does not exist in this preferences node.
1207
     * <p>
1208
     * Called by <code>key()</code> with this node locked after checking that
1209
     * key is valid, not null and that the node has not been removed.
1210
     * <code>key()</code> will catch any exceptions that this method throws.
1211
     */
1212
    protected abstract String getSpi(String key);
1213
 
1214
    /**
1215
     * Sets the value of the given preferences entry for this node.
1216
     * The implementation is not required to propagate the change to the
1217
     * backing store immediatly. It may not throw an exception when it tries
1218
     * to write to the backing store and that operation fails, the failure
1219
     * should be registered so a later invocation of <code>flush()</code>
1220
     * or <code>sync()</code> can signal the failure.
1221
     * <p>
1222
     * Called by <code>put()</code> with this node locked after checking that
1223
     * key and value are valid and non-null.
1224
     */
1225
    protected abstract void putSpi(String key, String value);
1226
 
1227
    /**
1228
     * Removes the given key entry from this preferences node.
1229
     * The implementation is not required to propagate the change to the
1230
     * backing store immediatly.  It may not throw an exception when it tries
1231
     * to write to the backing store and that operation fails, the failure
1232
     * should be registered so a later invocation of <code>flush()</code>
1233
     * or <code>sync()</code> can signal the failure.
1234
     * <p>
1235
     * Called by <code>remove()</code> with this node locked after checking
1236
     * that the key is valid and non-null.
1237
     */
1238
    protected abstract void removeSpi(String key);
1239
 
1240
    /**
1241
     * Writes all entries of this preferences node that have not yet been
1242
     * written to the backing store and possibly creates this node in the
1243
     * backing store, if it does not yet exist. Should only write changes to
1244
     * this node and not write changes to any subnodes.
1245
     * Note that the node can be already removed in this VM. To check if
1246
     * that is the case the implementation can call <code>isRemoved()</code>.
1247
     * <p>
1248
     * Called (indirectly) by <code>flush()</code> with this node locked.
1249
     */
1250
    protected abstract void flushSpi() throws BackingStoreException;
1251
 
1252
    /**
1253
     * Writes all entries of this preferences node that have not yet been
1254
     * written to the backing store and reads any entries that have changed
1255
     * in the backing store but that are not yet visible in this VM.
1256
     * Should only sync this node and not change any of the subnodes.
1257
     * Note that the node can be already removed in this VM. To check if
1258
     * that is the case the implementation can call <code>isRemoved()</code>.
1259
     * <p>
1260
     * Called (indirectly) by <code>sync()</code> with this node locked.
1261
     */
1262
    protected abstract void syncSpi() throws BackingStoreException;
1263
 
1264
    /**
1265
     * Clears this node from this VM and removes it from the backing store.
1266
     * After this method has been called the node is marked as removed.
1267
     * <p>
1268
     * Called (indirectly) by <code>removeNode()</code> with this node locked
1269
     * after all the sub nodes of this node have already been removed.
1270
     */
1271
    protected abstract void removeNodeSpi() throws BackingStoreException;
1272
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.