From adc854b798c1cfe3bfd4c27d68d5cee38ca617da Mon Sep 17 00:00:00 2001
From: The Android Open Source Project
Date: Tue, 3 Mar 2009 19:28:47 -0800
Subject: auto import from //depot/cupcake/@135843
---
.../java/java/util/prefs/AbstractPreferences.java | 974 ++++++++++
.../java/util/prefs/BackingStoreException.java | 56 +
.../util/prefs/FilePreferencesFactoryImpl.java | 44 +
.../java/java/util/prefs/FilePreferencesImpl.java | 236 +++
.../prefs/InvalidPreferencesFormatException.java | 71 +
.../main/java/java/util/prefs/NodeChangeEvent.java | 99 +
.../java/java/util/prefs/NodeChangeListener.java | 54 +
prefs/src/main/java/java/util/prefs/NodeSet.java | 38 +
.../java/util/prefs/PreferenceChangeEvent.java | 116 ++
.../java/util/prefs/PreferenceChangeListener.java | 45 +
.../src/main/java/java/util/prefs/Preferences.java | 1043 +++++++++++
.../java/java/util/prefs/PreferencesFactory.java | 49 +
prefs/src/main/java/java/util/prefs/XMLParser.java | 621 ++++++
prefs/src/main/java/java/util/prefs/package.html | 14 +
.../harmony/prefs/internal/nls/Messages.java | 140 ++
.../harmony/prefs/internal/nls/messages.properties | 33 +
.../services/java.util.prefs.PreferencesFactory | 1 +
.../java/util/prefs/AbstractPreferencesTest.java | 1468 +++++++++++++++
.../prefs/tests/java/util/prefs/AllTests.java | 48 +
.../java/util/prefs/BackingStoreExceptionTest.java | 96 +
.../java/util/prefs/FilePreferencesImplTest.java | 286 +++
.../InvalidPreferencesFormatExceptionTest.java | 119 ++
.../java/util/prefs/MockAbstractPreferences.java | 245 +++
.../java/util/prefs/MockNodeChangeListener.java | 132 ++
.../util/prefs/MockPreferenceChangeListener.java | 118 ++
.../tests/java/util/prefs/MockSecurityManager.java | 60 +
.../tests/java/util/prefs/NodeChangeEventTest.java | 155 ++
.../java/util/prefs/NodeChangeListenerTest.java | 85 +
.../java/util/prefs/PreferenceChangeEventTest.java | 199 ++
.../util/prefs/PreferenceChangeListenerTest.java | 64 +
.../java/util/prefs/PreferencesFactoryTest.java | 77 +
.../tests/java/util/prefs/PreferencesTest.java | 1980 ++++++++++++++++++++
prefs/src/test/java/tests/prefs/AllTests.java | 38 +
.../prefs/java/util/prefs/preferences.dtd | 56 +
.../prefs/java/util/prefs/userprefs-ascii.xml | 63 +
.../java/util/prefs/userprefs-badencoding.xml | 64 +
.../prefs/java/util/prefs/userprefs-badform.xml | 54 +
.../prefs/java/util/prefs/userprefs-badtype.xml | 64 +
.../java/util/prefs/userprefs-higherversion.xml | 64 +
.../resources/prefs/java/util/prefs/userprefs.xml | 76 +
.../prefs/BackingStoreExceptionTest.golden.ser | Bin 0 -> 1547 bytes
...nvalidPreferencesFormatExceptionTest.golden.ser | Bin 0 -> 1583 bytes
42 files changed, 9245 insertions(+)
create mode 100644 prefs/src/main/java/java/util/prefs/AbstractPreferences.java
create mode 100644 prefs/src/main/java/java/util/prefs/BackingStoreException.java
create mode 100644 prefs/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java
create mode 100644 prefs/src/main/java/java/util/prefs/FilePreferencesImpl.java
create mode 100644 prefs/src/main/java/java/util/prefs/InvalidPreferencesFormatException.java
create mode 100644 prefs/src/main/java/java/util/prefs/NodeChangeEvent.java
create mode 100644 prefs/src/main/java/java/util/prefs/NodeChangeListener.java
create mode 100644 prefs/src/main/java/java/util/prefs/NodeSet.java
create mode 100644 prefs/src/main/java/java/util/prefs/PreferenceChangeEvent.java
create mode 100644 prefs/src/main/java/java/util/prefs/PreferenceChangeListener.java
create mode 100644 prefs/src/main/java/java/util/prefs/Preferences.java
create mode 100644 prefs/src/main/java/java/util/prefs/PreferencesFactory.java
create mode 100644 prefs/src/main/java/java/util/prefs/XMLParser.java
create mode 100644 prefs/src/main/java/java/util/prefs/package.html
create mode 100644 prefs/src/main/java/org/apache/harmony/prefs/internal/nls/Messages.java
create mode 100644 prefs/src/main/java/org/apache/harmony/prefs/internal/nls/messages.properties
create mode 100644 prefs/src/main/resources/META-INF/services/java.util.prefs.PreferencesFactory
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/AbstractPreferencesTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/AllTests.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/BackingStoreExceptionTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/FilePreferencesImplTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/InvalidPreferencesFormatExceptionTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/MockAbstractPreferences.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/MockNodeChangeListener.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/MockPreferenceChangeListener.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/MockSecurityManager.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/NodeChangeEventTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/NodeChangeListenerTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/PreferenceChangeEventTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/PreferenceChangeListenerTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/PreferencesFactoryTest.java
create mode 100644 prefs/src/test/java/org/apache/harmony/prefs/tests/java/util/prefs/PreferencesTest.java
create mode 100644 prefs/src/test/java/tests/prefs/AllTests.java
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/preferences.dtd
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/userprefs-ascii.xml
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/userprefs-badencoding.xml
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/userprefs-badform.xml
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/userprefs-badtype.xml
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/userprefs-higherversion.xml
create mode 100644 prefs/src/test/resources/prefs/java/util/prefs/userprefs.xml
create mode 100644 prefs/src/test/resources/serialization/org/apache/harmony/prefs/tests/java/util/prefs/BackingStoreExceptionTest.golden.ser
create mode 100644 prefs/src/test/resources/serialization/org/apache/harmony/prefs/tests/java/util/prefs/InvalidPreferencesFormatExceptionTest.golden.ser
(limited to 'prefs/src')
diff --git a/prefs/src/main/java/java/util/prefs/AbstractPreferences.java b/prefs/src/main/java/java/util/prefs/AbstractPreferences.java
new file mode 100644
index 0000000..711cc01
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/AbstractPreferences.java
@@ -0,0 +1,974 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util.prefs;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.EventListener;
+import java.util.EventObject;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.TreeSet;
+
+import org.apache.harmony.luni.util.Base64;
+import org.apache.harmony.prefs.internal.nls.Messages;
+
+/**
+ * This abstract class is a partial implementation of the abstract class
+ * Preferences, which can be used to simplify {@code Preferences} provider's
+ * implementation. This class defines nine abstract SPI methods, which must be
+ * implemented by a preference provider.
+ *
+ * @since Android 1.0
+ */
+public abstract class AbstractPreferences extends Preferences {
+ /*
+ * -----------------------------------------------------------
+ * Class fields
+ * -----------------------------------------------------------
+ */
+ /**
+ * The unhandled events collection.
+ */
+ private static final List events = new LinkedList();
+
+ /**
+ * The event dispatcher thread.
+ */
+ private static final EventDispatcher dispatcher = new EventDispatcher("Preference Event Dispatcher"); //$NON-NLS-1$
+
+ /*
+ * -----------------------------------------------------------
+ * Class initializer
+ * -----------------------------------------------------------
+ */
+ static {
+ dispatcher.setDaemon(true);
+ dispatcher.start();
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ Preferences uroot = Preferences.userRoot();
+ Preferences sroot = Preferences.systemRoot();
+ try {
+ uroot.flush();
+ } catch (BackingStoreException e) {//ignore
+ }
+ try {
+ sroot.flush();
+ } catch (BackingStoreException e) {//ignore
+ }
+ }
+ });
+ }
+
+ /*
+ * -----------------------------------------------------------
+ * Instance fields (package-private)
+ * -----------------------------------------------------------
+ */
+ /**
+ * True, if this node is in user preference hierarchy.
+ */
+ boolean userNode;
+
+ /*
+ * -----------------------------------------------------------
+ * Instance fields (private)
+ * -----------------------------------------------------------
+ */
+ /**
+ * Marker class for 'lock' field.
+ */
+ private static class Lock {
+ }
+
+ /**
+ * The object used to lock this node.
+ *
+ * @since Android 1.0
+ */
+ protected final Object lock;
+
+ /**
+ * This field is true if this node is created while it doesn't exist in the
+ * backing store. This field's default value is false, and it is checked
+ * when the node creation is completed, and if it is true, the node change
+ * event will be fired for this node's parent.
+ *
+ * @since Android 1.0
+ */
+ protected boolean newNode;
+
+ /**
+ * Cached child nodes
+ */
+ private Map cachedNode;
+
+ //the collections of listeners
+ private List nodeChangeListeners;
+ private List preferenceChangeListeners;
+
+ //this node's name
+ private String nodeName;
+
+ //handler to this node's parent
+ private AbstractPreferences parentPref;
+
+ //true if this node has been removed
+ private boolean isRemoved;
+
+ //handler to this node's root node
+ private AbstractPreferences root;
+
+ /*
+ * -----------------------------------------------------------
+ * Constructors
+ * -----------------------------------------------------------
+ */
+ /**
+ * Constructs a new {@code AbstractPreferences} instance using the given parent node
+ * and node name.
+ *
+ * @param parent
+ * the parent node of the new node or {@code null} to indicate
+ * that the new node is a root node.
+ * @param name
+ * the name of the new node or an empty string to indicate that
+ * this node is called "root".
+ * @throws IllegalArgumentException
+ * if the name contains a slash character or is empty if {@code
+ * parent} is not {@code null}.
+ * @since Android 1.0
+ */
+ protected AbstractPreferences(AbstractPreferences parent, String name) {
+ if ((null == parent ^ name.length() == 0) || name.indexOf("/") >= 0) { //$NON-NLS-1$
+ throw new IllegalArgumentException();
+ }
+ root = null == parent ? this : parent.root;
+ nodeChangeListeners = new LinkedList();
+ preferenceChangeListeners = new LinkedList();
+ isRemoved = false;
+ cachedNode = new HashMap();
+ nodeName = name;
+ parentPref = parent;
+ lock = new Lock();
+ userNode = root.userNode;
+ }
+
+ /*
+ * -----------------------------------------------------------
+ * Methods
+ * -----------------------------------------------------------
+ */
+ /**
+ * Returns an array of all cached child nodes.
+ *
+ * @return the array of cached child nodes.
+ * @since Android 1.0
+ */
+ protected final AbstractPreferences[] cachedChildren() {
+ return cachedNode.values().toArray(new AbstractPreferences[cachedNode.size()]);
+ }
+
+ /**
+ * Returns the child node with the specified name or {@code null} if it
+ * doesn't exist. Implementers can assume that the name supplied to this method
+ * will be a valid node name string (conforming to the node naming format) and
+ * will not correspond to a node that has been cached or removed.
+ *
+ * @param name
+ * the name of the desired child node.
+ * @return the child node with the given name or {@code null} if it doesn't
+ * exist.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ protected AbstractPreferences getChild(String name)
+ throws BackingStoreException {
+ synchronized (lock) {
+ checkState();
+ AbstractPreferences result = null;
+ String[] childrenNames = childrenNames();
+ for (int i = 0; i < childrenNames.length; i++) {
+ if (childrenNames[i].equals(name)) {
+ result = childSpi(name);
+ break;
+ }
+ }
+ return result;
+ }
+
+ }
+
+ /**
+ * Returns whether this node has been removed by invoking the method {@code
+ * removeNode()}.
+ *
+ * @return {@code true}, if this node has been removed, {@code false}
+ * otherwise.
+ * @since Android 1.0
+ */
+ protected boolean isRemoved() {
+ synchronized (lock) {
+ return isRemoved;
+ }
+ }
+
+ /**
+ * Flushes changes of this node to the backing store. This method should
+ * only flush this node and should not include the descendant nodes. Any
+ * implementation that wants to provide functionality to flush all nodes
+ * at once should override the method {@link #flush() flush()}.
+ *
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ protected abstract void flushSpi() throws BackingStoreException;
+
+ /**
+ * Returns the names of all of the child nodes of this node or an empty array if
+ * this node has no children. The names of cached children are not required to be
+ * returned.
+ *
+ * @return the names of this node's children.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ protected abstract String[] childrenNamesSpi() throws BackingStoreException;
+
+ /**
+ * Returns the child preference node with the given name, creating it
+ * if it does not exist. The caller of this method should ensure that the
+ * given name is valid and that this node has not been removed or cached.
+ * If the named node has just been removed, the implementation
+ * of this method must create a new one instead of reactivating the removed
+ * one.
+ *
+ * The new creation is not required to be persisted immediately until the
+ * flush method will be invoked.
+ *
+ *
+ * @param name
+ * the name of the child preference to be returned.
+ * @return the child preference node.
+ * @since Android 1.0
+ */
+ protected abstract AbstractPreferences childSpi(String name);
+
+
+ /**
+ * Puts the given key-value pair into this node. Caller of this method
+ * should ensure that both of the given values are valid and that this
+ * node has not been removed.
+ *
+ * @param name
+ * the given preference key.
+ * @param value
+ * the given preference value.
+ * @since Android 1.0
+ */
+ protected abstract void putSpi(String name, String value);
+
+ /**
+ * Gets the preference value mapped to the given key. The caller of this method
+ * should ensure that the given key is valid and that this node has not been
+ * removed. This method should not throw any exceptions but if it does, the
+ * caller will ignore the exception, regarding it as a {@code null} return value.
+ *
+ * @param key
+ * the given key to be searched for.
+ * @return the preference value mapped to the given key.
+ * @since Android 1.0
+ */
+ protected abstract String getSpi(String key);
+
+
+ /**
+ * Returns an array of all preference keys of this node or an empty array if
+ * no preferences have been found. The caller of this method should ensure that
+ * this node has not been removed.
+ *
+ * @return the array of all preference keys.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ protected abstract String[] keysSpi() throws BackingStoreException;
+
+ /**
+ * Removes this node from the preference hierarchy tree. The caller of this
+ * method should ensure that this node has no child nodes, which means the
+ * method {@link Preferences#removeNode() Preferences.removeNode()} should
+ * invoke this method multiple-times in bottom-up pattern. The removal is
+ * not required to be persisted until after it is flushed.
+ *
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ protected abstract void removeNodeSpi() throws BackingStoreException;
+
+ /**
+ * Removes the preference with the specified key. The caller of this method
+ * should ensure that the given key is valid and that this node has not been
+ * removed.
+ *
+ * @param key
+ * the key of the preference that is to be removed.
+ * @since Android 1.0
+ */
+ protected abstract void removeSpi(String key);
+
+ /**
+ * Synchronizes this node with the backing store. This method should only
+ * synchronize this node and should not include the descendant nodes. An
+ * implementation that wants to provide functionality to synchronize all nodes at once should
+ * override the method {@link #sync() sync()}.
+ *
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ protected abstract void syncSpi() throws BackingStoreException;
+
+ /*
+ * -----------------------------------------------------------
+ * Methods inherited from Preferences
+ * -----------------------------------------------------------
+ */
+ @Override
+ public String absolutePath() {
+ if (parentPref == null) {
+ return "/"; //$NON-NLS-1$
+ } else if (parentPref == root) {
+ return "/" + nodeName; //$NON-NLS-1$
+ }
+ return parentPref.absolutePath() + "/" + nodeName; //$NON-NLS-1$
+ }
+
+ @Override
+ public String[] childrenNames() throws BackingStoreException {
+ synchronized (lock) {
+ checkState();
+ TreeSet result = new TreeSet(cachedNode.keySet());
+ String[] names = childrenNamesSpi();
+ for (int i = 0; i < names.length; i++) {
+ result.add(names[i]);
+ }
+ return result.toArray(new String[0]);
+ }
+ }
+
+ @Override
+ public void clear() throws BackingStoreException {
+ synchronized (lock) {
+ String[] keyList = keys();
+ for (int i = 0; i < keyList.length; i++) {
+ remove(keyList[i]);
+ }
+ }
+ }
+
+ @Override
+ public void exportNode(OutputStream ostream) throws IOException,
+ BackingStoreException {
+ if(ostream == null) {
+ // prefs.5=Stream is null
+ throw new NullPointerException(Messages.getString("prefs.5")); //$NON-NLS-1$
+ }
+ checkState();
+ XMLParser.exportPrefs(this, ostream, false);
+
+ }
+
+ @Override
+ public void exportSubtree(OutputStream ostream) throws IOException,
+ BackingStoreException {
+ if(ostream == null) {
+ // prefs.5=Stream is null
+ throw new NullPointerException(Messages.getString("prefs.5")); //$NON-NLS-1$
+ }
+ checkState();
+ XMLParser.exportPrefs(this, ostream, true);
+ }
+
+ @Override
+ public void flush() throws BackingStoreException {
+ synchronized (lock) {
+ flushSpi();
+ }
+ AbstractPreferences[] cc = cachedChildren();
+ int i;
+ for (i = 0; i < cc.length; i++) {
+ cc[i].flush();
+ }
+ }
+
+ @Override
+ public String get(String key, String deflt) {
+ if (key == null) {
+ throw new NullPointerException();
+ }
+ String result;
+ synchronized (lock) {
+ checkState();
+ try {
+ result = getSpi(key);
+ } catch (Exception e) {
+ result = null;
+ }
+ }
+ return (result == null ? deflt : result);
+ }
+
+ @Override
+ public boolean getBoolean(String key, boolean deflt) {
+ String result = get(key, null);
+ if (result == null) {
+ return deflt;
+ } else if (result.equalsIgnoreCase("true")) { //$NON-NLS-1$
+ return true;
+ } else if (result.equalsIgnoreCase("false")) { //$NON-NLS-1$
+ return false;
+ } else {
+ return deflt;
+ }
+ }
+
+ @Override
+ public byte[] getByteArray(String key, byte[] deflt) {
+ String svalue = get(key, null);
+ if (svalue == null) {
+ return deflt;
+ }
+ if (svalue.length() == 0) {
+ return new byte[0];
+ }
+ byte[] dres;
+ try {
+ byte[] bavalue = svalue.getBytes("US-ASCII"); //$NON-NLS-1$
+ if (bavalue.length % 4 != 0) {
+ return deflt;
+ }
+ dres = Base64.decode(bavalue);
+ } catch (Exception e) {
+ dres = deflt;
+ }
+ return dres;
+ }
+
+ @Override
+ public double getDouble(String key, double deflt) {
+ String result = get(key, null);
+ if (result == null) {
+ return deflt;
+ }
+ double dres;
+ try {
+ dres = Double.parseDouble(result);
+ } catch (NumberFormatException e) {
+ dres = deflt;
+ }
+ return dres;
+ }
+
+ @Override
+ public float getFloat(String key, float deflt) {
+ String result = get(key, null);
+ if (result == null) {
+ return deflt;
+ }
+ float fres;
+ try {
+ fres = Float.parseFloat(result);
+ } catch (NumberFormatException e) {
+ fres = deflt;
+ }
+ return fres;
+ }
+
+ @Override
+ public int getInt(String key, int deflt) {
+ String result = get(key, null);
+ if (result == null) {
+ return deflt;
+ }
+ int ires;
+ try {
+ ires = Integer.parseInt(result);
+ } catch (NumberFormatException e) {
+ ires = deflt;
+ }
+ return ires;
+ }
+
+ @Override
+ public long getLong(String key, long deflt) {
+ String result = get(key, null);
+ if (result == null) {
+ return deflt;
+ }
+ long lres;
+ try {
+ lres = Long.parseLong(result);
+ } catch (NumberFormatException e) {
+ lres = deflt;
+ }
+ return lres;
+ }
+
+ @Override
+ public boolean isUserNode() {
+ return root == Preferences.userRoot();
+ }
+
+ @Override
+ public String[] keys() throws BackingStoreException {
+ synchronized (lock) {
+ checkState();
+ return keysSpi();
+ }
+ }
+
+ @Override
+ public String name() {
+ return nodeName;
+ }
+
+ @Override
+ public Preferences node(String name) {
+ AbstractPreferences startNode = null;
+ synchronized (lock) {
+ checkState();
+ validateName(name);
+ if ("".equals(name)) { //$NON-NLS-1$
+ return this;
+ } else if ("/".equals(name)) { //$NON-NLS-1$
+ return root;
+ }
+ if (name.startsWith("/")) { //$NON-NLS-1$
+ startNode = root;
+ name = name.substring(1);
+ } else {
+ startNode = this;
+ }
+ }
+ Preferences result = null;
+ try {
+ result = startNode.nodeImpl(name, true);
+ } catch (BackingStoreException e) {
+ //should not happen
+ }
+ return result;
+ }
+
+ private void validateName(String name) {
+ if (name.endsWith("/") && name.length() > 1) { //$NON-NLS-1$
+ // prefs.6=Name cannot end with '/'\!
+ throw new IllegalArgumentException(Messages.getString("prefs.6")); //$NON-NLS-1$
+ }
+ if (name.indexOf("//") >= 0) { //$NON-NLS-1$
+ // prefs.7=Name cannot contains consecutive '/'\!
+ throw new IllegalArgumentException(
+ Messages.getString("prefs.7")); //$NON-NLS-1$
+ }
+ }
+
+ private AbstractPreferences nodeImpl(String path, boolean createNew)
+ throws BackingStoreException {
+ StringTokenizer st = new StringTokenizer(path, "/"); //$NON-NLS-1$
+ AbstractPreferences currentNode = this;
+ AbstractPreferences temp = null;
+ while (st.hasMoreTokens() && null != currentNode) {
+ String name = st.nextToken();
+ synchronized (currentNode.lock) {
+ temp = currentNode.cachedNode.get(name);
+ if (temp == null) {
+ temp = getNodeFromBackend(createNew, currentNode, name);
+ }
+ }
+
+ currentNode = temp;
+ }
+ return currentNode;
+ }
+
+ private AbstractPreferences getNodeFromBackend(boolean createNew,
+ AbstractPreferences currentNode, String name)
+ throws BackingStoreException {
+ AbstractPreferences temp;
+ if (name.length() > MAX_NAME_LENGTH) {
+ // prefs.8=Name length is too long: {0}
+ throw new IllegalArgumentException(Messages.getString("prefs.8", //$NON-NLS-1$
+ name));
+ }
+ if (createNew) {
+ temp = currentNode.childSpi(name);
+ currentNode.cachedNode.put(name, temp);
+ if (temp.newNode && currentNode.nodeChangeListeners.size() > 0) {
+ currentNode.notifyChildAdded(temp);
+ }
+ } else {
+ temp = currentNode.getChild(name);
+ }
+ return temp;
+ }
+
+ @Override
+ public boolean nodeExists(String name) throws BackingStoreException {
+ AbstractPreferences startNode = null;
+ synchronized (lock) {
+ if (isRemoved()) {
+ if ("".equals(name)) { //$NON-NLS-1$
+ return false;
+ }
+ // prefs.9=This node has been removed\!
+ throw new IllegalStateException(Messages.getString("prefs.9")); //$NON-NLS-1$
+ }
+ validateName(name);
+ if ("".equals(name) || "/".equals(name)) { //$NON-NLS-1$ //$NON-NLS-2$
+ return true;
+ }
+ if (name.startsWith("/")) { //$NON-NLS-1$
+ startNode = root;
+ name = name.substring(1);
+ } else {
+ startNode = this;
+ }
+ }
+ try {
+ Preferences result = startNode.nodeImpl(name, false);
+ return null == result ? false : true;
+ } catch(IllegalArgumentException e) {
+ return false;
+ }
+ }
+
+ @Override
+ public Preferences parent() {
+ checkState();
+ return parentPref;
+ }
+
+ private void checkState() {
+ if (isRemoved()) {
+ // prefs.9=This node has been removed\!
+ throw new IllegalStateException(Messages.getString("prefs.9")); //$NON-NLS-1$
+ }
+ }
+
+ @Override
+ public void put(String key, String value) {
+ if (null == key || null == value) {
+ throw new NullPointerException();
+ }
+ if (key.length() > MAX_KEY_LENGTH || value.length() > MAX_VALUE_LENGTH) {
+ throw new IllegalArgumentException();
+ }
+ synchronized (lock) {
+ checkState();
+ putSpi(key, value);
+ }
+ notifyPreferenceChange(key, value);
+ }
+
+ @Override
+ public void putBoolean(String key, boolean value) {
+ String sval = String.valueOf(value);
+ put(key, sval);
+ }
+
+ @Override
+ public void putByteArray(String key, byte[] value) {
+ try {
+ put(key, Base64.encode(value, "US-ASCII")); //$NON-NLS-1$
+ } catch (UnsupportedEncodingException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public void putDouble(String key, double value) {
+ String sval = Double.toString(value);
+ put(key, sval);
+ }
+
+ @Override
+ public void putFloat(String key, float value) {
+ String sval = Float.toString(value);
+ put(key, sval);
+ }
+
+ @Override
+ public void putInt(String key, int value) {
+ String sval = Integer.toString(value);
+ put(key, sval);
+ }
+
+ @Override
+ public void putLong(String key, long value) {
+ String sval = Long.toString(value);
+ put(key, sval);
+ }
+
+ @Override
+ public void remove(String key) {
+ synchronized (lock) {
+ checkState();
+ removeSpi(key);
+ }
+ notifyPreferenceChange(key, null);
+ }
+
+ @Override
+ public void removeNode() throws BackingStoreException {
+ if (root == this) {
+ // prefs.A=Cannot remove root node\!
+ throw new UnsupportedOperationException(Messages.getString("prefs.A")); //$NON-NLS-1$
+ }
+ synchronized (parentPref.lock) {
+ removeNodeImpl();
+ }
+ }
+
+ private void removeNodeImpl() throws BackingStoreException {
+ synchronized (lock) {
+ checkState();
+ String[] childrenNames = childrenNamesSpi();
+ for (int i = 0; i < childrenNames.length; i++) {
+ if (null == cachedNode.get(childrenNames[i])) {
+ AbstractPreferences child = childSpi(childrenNames[i]);
+ cachedNode.put(childrenNames[i], child);
+ }
+ }
+ AbstractPreferences[] children = cachedNode
+ .values().toArray(new AbstractPreferences[0]);
+ for (int i = 0; i < children.length; i++) {
+ children[i].removeNodeImpl();
+ }
+ removeNodeSpi();
+ isRemoved = true;
+ parentPref.cachedNode.remove(nodeName);
+ }
+ if (parentPref.nodeChangeListeners.size() > 0) {
+ parentPref.notifyChildRemoved(this);
+ }
+ }
+
+ @Override
+ public void addNodeChangeListener(NodeChangeListener ncl) {
+ if (null == ncl) {
+ throw new NullPointerException();
+ }
+ checkState();
+ synchronized (nodeChangeListeners) {
+ nodeChangeListeners.add(ncl);
+ }
+ }
+
+ @Override
+ public void addPreferenceChangeListener(PreferenceChangeListener pcl) {
+ if (null == pcl) {
+ throw new NullPointerException();
+ }
+ checkState();
+ synchronized (preferenceChangeListeners) {
+ preferenceChangeListeners.add(pcl);
+ }
+ }
+
+ @Override
+ public void removeNodeChangeListener(NodeChangeListener ncl) {
+ checkState();
+ synchronized (nodeChangeListeners) {
+ int pos;
+ if ((pos = nodeChangeListeners.indexOf(ncl)) == -1) {
+ throw new IllegalArgumentException();
+ }
+ nodeChangeListeners.remove(pos);
+ }
+ }
+
+ @Override
+ public void removePreferenceChangeListener(PreferenceChangeListener pcl) {
+ checkState();
+ synchronized (preferenceChangeListeners) {
+ int pos;
+ if ((pos = preferenceChangeListeners.indexOf(pcl)) == -1) {
+ throw new IllegalArgumentException();
+ }
+ preferenceChangeListeners.remove(pos);
+ }
+ }
+
+ @Override
+ public void sync() throws BackingStoreException {
+ synchronized (lock) {
+ checkState();
+ syncSpi();
+ }
+ AbstractPreferences[] cc = cachedChildren();
+ int i;
+ for (i = 0; i < cc.length; i++) {
+ cc[i].sync();
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuffer sb = new StringBuffer();
+ sb.append(isUserNode() ? "User" : "System"); //$NON-NLS-1$ //$NON-NLS-2$
+ sb.append(" Preference Node: "); //$NON-NLS-1$
+ sb.append(absolutePath());
+ return sb.toString();
+ }
+
+ private void notifyChildAdded(Preferences child) {
+ NodeChangeEvent nce = new NodeAddEvent(this, child);
+ synchronized (events) {
+ events.add(nce);
+ events.notifyAll();
+ }
+ }
+
+ private void notifyChildRemoved(Preferences child) {
+ NodeChangeEvent nce = new NodeRemoveEvent(this, child);
+ synchronized (events) {
+ events.add(nce);
+ events.notifyAll();
+ }
+ }
+
+ private void notifyPreferenceChange(String key, String newValue) {
+ PreferenceChangeEvent pce = new PreferenceChangeEvent(this, key,
+ newValue);
+ synchronized (events) {
+ events.add(pce);
+ events.notifyAll();
+ }
+ }
+
+ private static class EventDispatcher extends Thread {
+ EventDispatcher(String name){
+ super(name);
+ }
+
+ @Override
+ public void run() {
+ while (true) {
+ EventObject event = null;
+ try {
+ event = getEventObject();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ continue;
+ }
+ AbstractPreferences pref = (AbstractPreferences) event
+ .getSource();
+ if (event instanceof NodeAddEvent) {
+ dispatchNodeAdd((NodeChangeEvent) event,
+ pref.nodeChangeListeners);
+ } else if (event instanceof NodeRemoveEvent) {
+ dispatchNodeRemove((NodeChangeEvent) event,
+ pref.nodeChangeListeners);
+ } else if (event instanceof PreferenceChangeEvent) {
+ dispatchPrefChange((PreferenceChangeEvent) event,
+ pref.preferenceChangeListeners);
+ }
+ }
+ }
+
+ private EventObject getEventObject() throws InterruptedException {
+ synchronized (events) {
+ if (events.isEmpty()) {
+ events.wait();
+ }
+ EventObject event = events.get(0);
+ events.remove(0);
+ return event;
+ }
+ }
+
+ private void dispatchPrefChange(PreferenceChangeEvent event,
+ List preferenceChangeListeners) {
+ synchronized (preferenceChangeListeners) {
+ Iterator i = preferenceChangeListeners.iterator();
+ while (i.hasNext()) {
+ PreferenceChangeListener pcl = (PreferenceChangeListener) i
+ .next();
+ pcl.preferenceChange(event);
+ }
+ }
+ }
+
+ private void dispatchNodeRemove(NodeChangeEvent event,
+ List nodeChangeListeners) {
+ synchronized (nodeChangeListeners) {
+ Iterator i = nodeChangeListeners.iterator();
+ while (i.hasNext()) {
+ NodeChangeListener ncl = (NodeChangeListener) i.next();
+ ncl.childRemoved(event);
+ }
+ }
+ }
+
+ private void dispatchNodeAdd(NodeChangeEvent event,
+ List nodeChangeListeners) {
+ synchronized (nodeChangeListeners) {
+ Iterator i = nodeChangeListeners.iterator();
+ while (i.hasNext()) {
+ NodeChangeListener ncl = (NodeChangeListener) i.next();
+ ncl.childAdded(event);
+ }
+ }
+ }
+ }
+
+ private static class NodeAddEvent extends NodeChangeEvent {
+ //The base class is NOT serializable, so this class isn't either.
+ private static final long serialVersionUID = 1L;
+
+ public NodeAddEvent(Preferences p, Preferences c) {
+ super(p, c);
+ }
+ }
+
+ private static class NodeRemoveEvent extends NodeChangeEvent {
+ //The base class is NOT serializable, so this class isn't either.
+ private static final long serialVersionUID = 1L;
+
+ public NodeRemoveEvent(Preferences p, Preferences c) {
+ super(p, c);
+ }
+ }
+}
diff --git a/prefs/src/main/java/java/util/prefs/BackingStoreException.java b/prefs/src/main/java/java/util/prefs/BackingStoreException.java
new file mode 100644
index 0000000..e8a805c
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/BackingStoreException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util.prefs;
+
+
+/**
+ * An exception to indicate that an error was encountered while accessing the
+ * backing store.
+ *
+ * @since Android 1.0
+ */
+public class BackingStoreException extends Exception {
+
+ private static final long serialVersionUID = 859796500401108469L;
+
+ /**
+ * Constructs a new {@code BackingStoreException} instance with a detailed exception
+ * message.
+ *
+ * @param s
+ * the detailed exception message.
+ * @since Android 1.0
+ */
+ public BackingStoreException (String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs a new {@code BackingStoreException} instance with a nested {@code Throwable}.
+ *
+ * @param t
+ * the nested {@code Throwable}.
+ * @since Android 1.0
+ */
+ public BackingStoreException (Throwable t) {
+ super(t);
+ }
+}
+
+
+
diff --git a/prefs/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java b/prefs/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java
new file mode 100644
index 0000000..cc68e62
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/FilePreferencesFactoryImpl.java
@@ -0,0 +1,44 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util.prefs;
+
+/**
+ * The default implementation of PreferencesFactory for the Linux
+ * platform, using the file system as its back end.
+ *
+ * @since Android 1.0
+ */
+class FilePreferencesFactoryImpl implements PreferencesFactory {
+ // user root preferences
+ private static final Preferences USER_ROOT = new FilePreferencesImpl(true);
+
+ // system root preferences
+ private static final Preferences SYSTEM_ROOT = new FilePreferencesImpl(false);
+
+ public FilePreferencesFactoryImpl() {
+ super();
+ }
+
+ public Preferences userRoot() {
+ return USER_ROOT;
+ }
+
+ public Preferences systemRoot() {
+ return SYSTEM_ROOT;
+ }
+
+}
diff --git a/prefs/src/main/java/java/util/prefs/FilePreferencesImpl.java b/prefs/src/main/java/java/util/prefs/FilePreferencesImpl.java
new file mode 100644
index 0000000..cf85fa0
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/FilePreferencesImpl.java
@@ -0,0 +1,236 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util.prefs;
+
+import java.io.File;
+import java.io.FilenameFilter;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Properties;
+import java.util.Set;
+
+import org.apache.harmony.prefs.internal.nls.Messages;
+
+/**
+ * The default implementation of AbstractPreferences for the Linux platform,
+ * using the file system as its back end.
+ *
+ * TODO some sync mechanism with backend, Performance - check file edit date
+ *
+ * @since Android 1.0
+ */
+class FilePreferencesImpl extends AbstractPreferences {
+
+ /*
+ * --------------------------------------------------------------
+ * Class fields
+ * --------------------------------------------------------------
+ */
+
+ //prefs file name
+ private static final String PREFS_FILE_NAME = "prefs.xml"; //$NON-NLS-1$
+
+ //home directory for user prefs
+ private static String USER_HOME;
+
+ //home directory for system prefs
+ private static String SYSTEM_HOME;
+
+ /*
+ * --------------------------------------------------------------
+ * Class initializer
+ * --------------------------------------------------------------
+ */
+ static {
+ AccessController.doPrivileged(new PrivilegedAction() {
+ public Void run() {
+ USER_HOME = System.getProperty("user.home") + "/.java/.userPrefs";//$NON-NLS-1$ //$NON-NLS-2$
+ SYSTEM_HOME = System.getProperty("java.home") + "/.systemPrefs";//$NON-NLS-1$//$NON-NLS-2$
+ return null;
+ }
+
+ });
+ }
+
+ /*
+ * --------------------------------------------------------------
+ * Instance fields
+ * --------------------------------------------------------------
+ */
+
+ //file path for this preferences node
+ private String path;
+
+ //internal cache for prefs key-value pair
+ private Properties prefs;
+
+ //file represents this preferences node
+ private File prefsFile;
+
+ //parent dir for this preferences node
+ private File dir;
+
+ //cache for removed prefs key-value pair
+ private Set removed = new HashSet();
+
+ //cache for updated prefs key-value pair
+ private Set updated = new HashSet();
+
+ /*
+ * --------------------------------------------------------------
+ * Constructors
+ * --------------------------------------------------------------
+ */
+
+ /**
+ * Construct root FilePreferencesImpl instance, construct
+ * user root if userNode is true, system root otherwise
+ */
+ FilePreferencesImpl(boolean userNode) {
+ super(null, ""); //$NON-NLS-1$
+ this.userNode = userNode;
+ path = userNode ? USER_HOME : SYSTEM_HOME;
+ initPrefs();
+ }
+
+ /**
+ * Construct a prefs using given parent and given name
+ */
+ private FilePreferencesImpl(AbstractPreferences parent, String name) {
+ super(parent, name);
+ path = ((FilePreferencesImpl) parent).path + File.separator + name;
+ initPrefs();
+ }
+
+ private void initPrefs() {
+ dir = new File(path);
+ newNode = (AccessController.doPrivileged(new PrivilegedAction() {
+ public Boolean run() {
+ return Boolean.valueOf(!dir.exists());
+ }
+ })).booleanValue();
+ prefsFile = new File(path + File.separator + PREFS_FILE_NAME);
+ prefs = XMLParser.loadFilePrefs(prefsFile);
+ }
+
+ @Override
+ protected String[] childrenNamesSpi() throws BackingStoreException {
+ String[] names = AccessController
+ .doPrivileged(new PrivilegedAction() {
+ public String[] run() {
+ return dir.list(new FilenameFilter() {
+ public boolean accept(File parent, String name) {
+ return new File(path + File.separator + name).isDirectory();
+ }
+ });
+
+ }
+ });
+ if (null == names) {// file is not a directory, exception case
+ // prefs.3=Cannot get children names for {0}!
+ throw new BackingStoreException(
+ Messages.getString("prefs.3", toString())); //$NON-NLS-1$
+ }
+ return names;
+ }
+
+ @Override
+ protected AbstractPreferences childSpi(String name) {
+ FilePreferencesImpl child = new FilePreferencesImpl(this, name);
+ return child;
+ }
+
+ @Override
+ protected void flushSpi() throws BackingStoreException {
+ try {
+ //if removed, return
+ if(isRemoved()){
+ return;
+ }
+ // reload
+ Properties currentPrefs = XMLParser.loadFilePrefs(prefsFile);
+ // merge
+ Iterator it = removed.iterator();
+ while (it.hasNext()) {
+ currentPrefs.remove(it.next());
+ }
+ removed.clear();
+ it = updated.iterator();
+ while (it.hasNext()) {
+ Object key = it.next();
+ currentPrefs.put(key, prefs.get(key));
+ }
+ updated.clear();
+ // flush
+ prefs = currentPrefs;
+ XMLParser.flushFilePrefs(prefsFile, prefs);
+ } catch (Exception e) {
+ throw new BackingStoreException(e);
+ }
+ }
+
+ @Override
+ protected String getSpi(String key) {
+ try {
+ if (null == prefs) {
+ prefs = XMLParser.loadFilePrefs(prefsFile);
+ }
+ return prefs.getProperty(key);
+ } catch (Exception e) {// if Exception happened, return null
+ return null;
+ }
+ }
+
+ @Override
+ protected String[] keysSpi() throws BackingStoreException {
+ return prefs.keySet().toArray(new String[0]);
+ }
+
+ @Override
+ protected void putSpi(String name, String value) {
+ prefs.setProperty(name, value);
+ updated.add(name);
+ }
+
+ @Override
+ protected void removeNodeSpi() throws BackingStoreException {
+ boolean removeSucceed = (AccessController.doPrivileged(new PrivilegedAction() {
+ public Boolean run() {
+ prefsFile.delete();
+ return Boolean.valueOf(dir.delete());
+ }
+ })).booleanValue();
+ if (!removeSucceed) {
+ // prefs.4=Cannot remove {0}!
+ throw new BackingStoreException(Messages.getString("prefs.4", toString())); //$NON-NLS-1$
+ }
+ }
+
+ @Override
+ protected void removeSpi(String key) {
+ prefs.remove(key);
+ updated.remove(key);
+ removed.add(key);
+ }
+
+ @Override
+ protected void syncSpi() throws BackingStoreException {
+ flushSpi();
+ }
+}
diff --git a/prefs/src/main/java/java/util/prefs/InvalidPreferencesFormatException.java b/prefs/src/main/java/java/util/prefs/InvalidPreferencesFormatException.java
new file mode 100644
index 0000000..b31b3a1
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/InvalidPreferencesFormatException.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util.prefs;
+
+/**
+ * An exception to indicate that the input XML file is not well-formed or could
+ * not be validated against the appropriate document type (specified by
+ * in the {@code Preferences}).
+ *
+ * @since Android 1.0
+ */
+public class InvalidPreferencesFormatException extends Exception {
+
+ private static final long serialVersionUID = -791715184232119669L;
+
+ /**
+ * Constructs a new {@code InvalidPreferencesFormatException} instance with a
+ * detailed exception message.
+ *
+ * @param s
+ * the detailed exception message.
+ * @since Android 1.0
+ */
+ public InvalidPreferencesFormatException (String s) {
+ super(s);
+ }
+
+ /**
+ * Constructs a new {@code InvalidPreferencesFormatException} instance with a
+ * detailed exception message and a nested {@code Throwable}.
+ *
+ * @param s
+ * the detailed exception message.
+ * @param t
+ * the nested {@code Throwable}.
+ * @since Android 1.0
+ */
+ public InvalidPreferencesFormatException (String s, Throwable t) {
+ super(s,t);
+ }
+
+ /**
+ * Constructs a new {@code InvalidPreferencesFormatException} instance with a nested
+ * {@code Throwable}.
+ *
+ * @param t
+ * the nested {@code Throwable}.
+ * @since Android 1.0
+ */
+ public InvalidPreferencesFormatException (Throwable t) {
+ super(t);
+ }
+}
+
+
+
diff --git a/prefs/src/main/java/java/util/prefs/NodeChangeEvent.java b/prefs/src/main/java/java/util/prefs/NodeChangeEvent.java
new file mode 100644
index 0000000..e9824bc
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/NodeChangeEvent.java
@@ -0,0 +1,99 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.prefs;
+
+import java.io.Serializable;
+import java.util.EventObject;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.io.NotSerializableException;
+import java.io.IOException;
+
+/**
+ * This is the event class to indicate that one child of the preference node has
+ * been added or deleted.
+ *
+ * Please note that the serialization functionality has not yet been implemented, so
+ * the serialization methods do nothing but throw a {@code NotSerializableException}.
+ *
+ *
+ * @since Android 1.0
+ */
+public class NodeChangeEvent extends EventObject implements Serializable {
+
+ private static final long serialVersionUID = 8068949086596572957L;
+
+ private final Preferences parent;
+ private final Preferences child;
+
+ /**
+ * Constructs a new {@code NodeChangeEvent} instance.
+ *
+ * @param p
+ * the {@code Preferences} instance that fired this event; this object is
+ * considered as the event source.
+ * @param c
+ * the child {@code Preferences} instance that was added or deleted.
+ * @since Android 1.0
+ */
+ public NodeChangeEvent (Preferences p, Preferences c) {
+ super(p);
+ parent = p;
+ child = c;
+ }
+
+ /**
+ * Gets the {@code Preferences} instance that fired this event.
+ *
+ * @return the {@code Preferences} instance that fired this event.
+ * @since Android 1.0
+ */
+ public Preferences getParent() {
+ return parent;
+ }
+
+ /**
+ * Gets the child {@code Preferences} node that was added or removed.
+ *
+ * @return the added or removed child {@code Preferences} node.
+ * @since Android 1.0
+ */
+ public Preferences getChild() {
+ return child;
+ }
+
+ /*
+ * This method always throws a NotSerializableException,
+ * because this object cannot be serialized,
+ */
+ private void writeObject (ObjectOutputStream out) throws IOException {
+ throw new NotSerializableException();
+ }
+
+ /*
+ * This method always throws a NotSerializableException,
+ * because this object cannot be serialized,
+ */
+ private void readObject (ObjectInputStream in) throws IOException, ClassNotFoundException {
+ throw new NotSerializableException();
+ }
+}
+
+
+
+
diff --git a/prefs/src/main/java/java/util/prefs/NodeChangeListener.java b/prefs/src/main/java/java/util/prefs/NodeChangeListener.java
new file mode 100644
index 0000000..f16b206
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/NodeChangeListener.java
@@ -0,0 +1,54 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.prefs;
+
+import java.util.EventListener;
+import java.util.prefs.NodeChangeEvent;
+
+/**
+ * This interface is used to handle preference node change events.
+ * The implementation of this interface can be installed by the {@code Preferences} instance.
+ *
+ * @see NodeChangeEvent
+ *
+ * @since Android 1.0
+ */
+public interface NodeChangeListener extends EventListener {
+
+ /**
+ * This method gets called whenever a child node is added to another node.
+ *
+ * @param e
+ * the node change event.
+ * @since Android 1.0
+ */
+ public void childAdded (NodeChangeEvent e);
+
+ /**
+ * This method gets called whenever a child node is removed from another
+ * node.
+ *
+ * @param e
+ * the node change event.
+ * @since Android 1.0
+ */
+ public void childRemoved (NodeChangeEvent e);
+}
+
+
+
diff --git a/prefs/src/main/java/java/util/prefs/NodeSet.java b/prefs/src/main/java/java/util/prefs/NodeSet.java
new file mode 100644
index 0000000..7edd030
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/NodeSet.java
@@ -0,0 +1,38 @@
+package java.util.prefs;
+
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ *
+ * @since Android 1.0
+ */
+class NodeSet implements NodeList {
+
+ ArrayList list = new ArrayList();
+
+ public NodeSet(Iterator nodes) {
+ while(nodes.hasNext()) {
+ list.add(nodes.next());
+ }
+ }
+
+ public int getLength() {
+ return list.size();
+ }
+
+ public Node item(int index) {
+ Node result = null;
+ try {
+ result = list.get(index);
+ } catch(IndexOutOfBoundsException ioobe) {
+ // TODO log this event?
+ return null;
+ }
+
+ return result;
+ }
+}
diff --git a/prefs/src/main/java/java/util/prefs/PreferenceChangeEvent.java b/prefs/src/main/java/java/util/prefs/PreferenceChangeEvent.java
new file mode 100644
index 0000000..f0f0787
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/PreferenceChangeEvent.java
@@ -0,0 +1,116 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.prefs;
+
+import java.io.IOException;
+import java.io.NotSerializableException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import java.util.EventObject;
+
+/**
+ * This is the event class to indicate that a preference has been added, deleted
+ * or updated.
+ *
+ * Please note that the serialization functionality has not yet been implemented, so
+ * the serialization methods do nothing but throw a {@code NotSerializableException}.
+ *
+ *
+ * @since Android 1.0
+ */
+public class PreferenceChangeEvent extends EventObject implements Serializable {
+
+ private static final long serialVersionUID = 793724513368024975L;
+
+ private final Preferences node;
+
+ private final String key;
+
+ private final String value;
+
+ /**
+ * Construct a new {@code PreferenceChangeEvent} instance.
+ *
+ * @param p
+ * the {@code Preferences} instance that fired this event; this object is
+ * considered as the event's source.
+ * @param k
+ * the changed preference key.
+ * @param v
+ * the new value of the changed preference, this value can be
+ * {@code null}, which means the preference has been removed.
+ * @since Android 1.0
+ */
+ public PreferenceChangeEvent(Preferences p, String k, String v) {
+ super(p);
+ node = p;
+ key = k;
+ value = v;
+ }
+
+ /**
+ * Gets the key of the changed preference.
+ *
+ * @return the changed preference's key.
+ * @since Android 1.0
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Gets the new value of the changed preference or {@code null} if the
+ * preference has been removed.
+ *
+ * @return the new value of the changed preference or null if the preference
+ * has been removed.
+ * @since Android 1.0
+ */
+ public String getNewValue() {
+ return value;
+ }
+
+ /**
+ * Gets the {@code Preferences} instance that fired this event.
+ *
+ * @return the {@code Preferences} instance that fired this event.
+ * @since Android 1.0
+ */
+ public Preferences getNode() {
+ return node;
+ }
+
+ /*
+ * This method always throws a NotSerializableException,
+ * because this object cannot be serialized,
+ */
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ throw new NotSerializableException();
+ }
+
+ /*
+ * This method always throws a NotSerializableException,
+ * because this object cannot be serialized,
+ */
+ private void readObject(ObjectInputStream in) throws IOException{
+ throw new NotSerializableException();
+ }
+}
+
+
diff --git a/prefs/src/main/java/java/util/prefs/PreferenceChangeListener.java b/prefs/src/main/java/java/util/prefs/PreferenceChangeListener.java
new file mode 100644
index 0000000..28bb763
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/PreferenceChangeListener.java
@@ -0,0 +1,45 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.prefs;
+
+import java.util.EventListener;
+
+/**
+ * This interface is used to handle preferences change events. The implementation of
+ * this interface can be installed by the {@code Preferences} instance.
+ *
+ * @see PreferenceChangeEvent
+ *
+ * @since Android 1.0
+ */
+public interface PreferenceChangeListener extends EventListener {
+
+ /**
+ * This method gets invoked whenever a preference is added, deleted or
+ * updated.
+ *
+ * @param pce
+ * the event instance which describes the changed {@code Preferences}
+ * instance and the preference value.
+ * @since Android 1.0
+ */
+ void preferenceChange (PreferenceChangeEvent pce);
+}
+
+
+
diff --git a/prefs/src/main/java/java/util/prefs/Preferences.java b/prefs/src/main/java/java/util/prefs/Preferences.java
new file mode 100644
index 0000000..b7a0c70
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/Preferences.java
@@ -0,0 +1,1043 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package java.util.prefs;
+
+// BEGIN android-added
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.Enumeration;
+// END android-added
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import org.apache.harmony.prefs.internal.nls.Messages;
+
+/**
+ * An instance of the class {@code Preferences} represents one node in a preference tree,
+ * which provides a mechanism to store and access configuration data in a
+ * hierarchical way. Two hierarchy trees are maintained, one for system
+ * preferences shared by all users and the other for user preferences
+ * specific to the user. {@code Preferences} hierarchy trees and data are stored
+ * in an implementation-dependent back-end.
+ *
+ * Every node has one name and one unique absolute path following the same
+ * notational conventions as directories in a file system. The root node's
+ * name is "", and other node name strings cannot contain the slash character
+ * and cannot be empty. The root node's absolute path is "/", and all other
+ * nodes' absolute paths are constructed in the standard way: <parent's absolute
+ * path> + "/" + <node's name>. Since the set of nodes forms a tree with
+ * the root node at its base, all absolute paths start with the slash character.
+ * Every node has one relative path to each of its ancestors. The relative path
+ * doesn't start with slash: it equals the node's absolute path with leading
+ * substring removed corresponding to the ancestor's absolute path and a slash.
+ *
+ *
+ * Modification to preferences data may be asynchronous, which means that
+ * preference update method calls may return immediately instead of blocking.
+ * The {@code flush()} and {@code sync()} methods force the back-end to
+ * synchronously perform all pending updates, but the implementation is
+ * permitted to perform the modifications on the underlying back-end data
+ * at any time between the moment the request is made and the moment the
+ * {@code flush()} or {@code sync()} method returns.
+ * Please note that if JVM exit normally, the implementation must assure all
+ * modifications are persisted implicitly.
+ *
+ *
+ * When invoking a method that retrieves preferences, the user must provide
+ * a default value. The default value is returned when the preferences cannot
+ * be found or the back-end is unavailable. Some other methods will throw
+ * {@code BackingStoreException} when the back-end is unavailable.
+ *
+ *
+ * Preferences can be exported to and imported from an XML files.
+ *
+ * There must be a concrete {@code PreferencesFactory} type for every concrete
+ * {@code Preferences} type developed. Every J2SE implementation must provide a default
+ * implementation for every supported platform, and must also provide a means of
+ * replacing the default implementation. This implementation uses the system property
+ * {@code java.util.prefs.PreferencesFactory} to detemine which preferences
+ * implementation to use.
+ *
+ *
+ * The methods of this class are thread-safe. If multiple JVMs are using the same
+ * back-end concurrently, the back-end won't be corrupted, but no other
+ * behavior guarantees are made.
+ *
+ *
+ * @since Android 1.0
+ */
+public abstract class Preferences {
+
+ /*
+ * ---------------------------------------------------------
+ * Class fields
+ * ---------------------------------------------------------
+ */
+
+ /**
+ * Maximum size in characters allowed for a preferences key.
+ *
+ * @since Android 1.0
+ */
+ public static final int MAX_KEY_LENGTH = 80;
+
+ /**
+ * Maximum size in characters allowed for a preferences name.
+ *
+ * @since Android 1.0
+ */
+ public static final int MAX_NAME_LENGTH = 80;
+
+ /**
+ * Maximum size in characters allowed for a preferences value.
+ *
+ * @since Android 1.0
+ */
+ public static final int MAX_VALUE_LENGTH = 8192;
+
+ // BEGIN android-added
+ /**
+ * The name of the configuration file where preferences factory class names
+ * can be specified.
+ */
+ private static final String FACTORY_CONFIGURATION_FILE_NAME = "META-INF/services/java.util.prefs.PreferencesFactory"; //$NON-NLS-1$
+
+ /**
+ * The encoding of configuration files
+ */
+ private static final String CONFIGURATION_FILE_ENCODING = "UTF-8"; //$NON-NLS-1$
+
+ /**
+ * The comment string used in configuration files
+ */
+ private static final String CONFIGURATION_FILE_COMMENT = "#"; //$NON-NLS-1$
+
+ // END android-added
+
+ //permission
+ private static final RuntimePermission PREFS_PERM = new RuntimePermission("preferences"); //$NON-NLS-1$
+
+ //factory used to get user/system prefs root
+ private static final PreferencesFactory factory;
+
+ /**
+ * ---------------------------------------------------------
+ * Class initializer
+ * ---------------------------------------------------------
+ */
+ static{
+ String factoryClassName = AccessController.doPrivileged(new PrivilegedAction() {
+ public String run() {
+ return System.getProperty("java.util.prefs.PreferencesFactory"); //$NON-NLS-1$
+ }
+ });
+ // BEGIN android-removed
+ // if(factoryClassName != null) {
+ // try {
+ // ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ // if(loader == null){
+ // loader = ClassLoader.getSystemClassLoader();
+ // }
+ // Class> factoryClass = loader.loadClass(factoryClassName);
+ // factory = (PreferencesFactory) factoryClass.newInstance();
+ // } catch (Exception e) {
+ // // prefs.10=Cannot initiate PreferencesFactory: {0}. Caused by {1}
+ // throw new InternalError(Messages.getString("prefs.10", factoryClassName, e)); //$NON-NLS-1$
+ // }
+ // }
+ // END android-removed
+ // BEGIN android-added
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ if (loader == null) {
+ loader = ClassLoader.getSystemClassLoader();
+ }
+ if (factoryClassName == null) {
+ Enumeration en = null;
+ try {
+ en = loader.getResources(FACTORY_CONFIGURATION_FILE_NAME);
+ BufferedReader reader = null;
+ int commentIndex = 0;
+ while (en.hasMoreElements()) {
+ try {
+ InputStream is = en.nextElement().openStream();
+ // Read each line for charset provider class names
+ // BEGIN android-modified
+ reader = new BufferedReader(new InputStreamReader(is,
+ CONFIGURATION_FILE_ENCODING), 8192);
+ // END android-modified
+ factoryClassName = reader.readLine();
+ commentIndex = factoryClassName.indexOf(CONFIGURATION_FILE_COMMENT);
+ if (commentIndex > 0) {
+ factoryClassName = factoryClassName.substring(0, commentIndex).trim();
+ }
+ if (factoryClassName.length() > 0) {
+ break;
+ }
+ } catch (IOException ex) {
+ // ignore if a resource couldn't be read
+ }
+ }
+ } catch (Exception e) {
+ // prefs.10=Cannot initiate PreferencesFactory: {0}. Caused by
+ // {1}
+ throw new InternalError(Messages.getString("prefs.10",
+ FACTORY_CONFIGURATION_FILE_NAME, e)); //$NON-NLS-1$
+ }
+ }
+
+ if (factoryClassName == null) {
+ factoryClassName = "java.util.prefs.FilePreferencesFactoryImpl";
+ }
+
+ try {
+ Class> c = loader.loadClass(factoryClassName);
+ factory = (PreferencesFactory)c.newInstance();
+ } catch (Exception e) {
+ // prefs.10=Cannot initiate PreferencesFactory: {0}. Caused by {1}
+ throw new InternalError(Messages.getString("prefs.10", factoryClassName, e)); //$NON-NLS-1$
+ }
+ // END android-added
+ }
+
+ /*
+ * ---------------------------------------------------------
+ * Constructors
+ * ---------------------------------------------------------
+ */
+
+ /**
+ * Default constructor, for use by subclasses only.
+ *
+ * @since Android 1.0
+ */
+ protected Preferences() {
+ super();
+ }
+
+ /*
+ * ---------------------------------------------------------
+ * Methods
+ * ---------------------------------------------------------
+ */
+
+ /**
+ * Gets the absolute path string of this preference node.
+ *
+ * @return the preference node's absolute path string.
+ * @since Android 1.0
+ */
+ public abstract String absolutePath();
+
+ /**
+ * Returns the names of all children of this node or an empty string if this
+ * node has no children.
+ *
+ * @return the names of all children of this node.
+ * @throws BackingStoreException
+ * if backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract String[] childrenNames() throws BackingStoreException;
+
+ /**
+ * Removes all preferences of this node.
+ *
+ * @throws BackingStoreException
+ * if backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void clear() throws BackingStoreException;
+
+ /**
+ * Exports all of the preferences of this node to a XML document using the given
+ * output stream.
+ *
+ * This XML document uses the UTF-8 encoding and is written according to the
+ * DTD in its DOCTYPE declaration, which is the following:
+ *
+ *
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ *
+ *
+ * Please note that (unlike the methods of this class that don't concern serialization), this call is not thread-safe.
+ *
+ *
+ * @param ostream
+ * the output stream to write the XML-formatted data to.
+ * @throws IOException
+ * if an error occurs while exporting.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void exportNode (OutputStream ostream) throws IOException, BackingStoreException;
+
+ /**
+ * Exports all of the preferences of this node and all its descendants to a XML
+ * document using the given output stream.
+ *
+ * This XML document uses the UTF-8 encoding and is written according to the
+ * DTD in its DOCTYPE declaration, which is the following:
+ *
+ *
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ *
+ *
+ * Please note that (unlike the methods of this class that don't concern serialization), this call is not thread-safe.
+ *
+ *
+ * @param ostream
+ * the output stream to write the XML-formatted data to.
+ * @throws IOException
+ * if an error occurs while exporting.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void exportSubtree (OutputStream ostream) throws IOException, BackingStoreException;
+
+ /**
+ * Forces all pending updates to this node and its descendants to be
+ * persisted in the backing store.
+ *
+ * If this node has been removed, the invocation of this method only flushes
+ * this node, not its descendants.
+ *
+ *
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ public abstract void flush() throws BackingStoreException;
+
+ /**
+ * Gets the {@code String} value mapped to the given key or its default value if no
+ * value is mapped or no backing store is available.
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key or no backing store is available.
+ * @return the preference value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract String get (String key, String deflt);
+
+ /**
+ * Gets the {@code boolean} value mapped to the given key or its default value if no
+ * value is mapped, if the backing store is unavailable, or if the value is invalid.
+ *
+ * The only valid values are the {@code String} "true", which represents {@code true} and
+ * "false", which represents {@code false}, ignoring case.
+ *
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key, if the backing store is unavailable, or if the
+ * value is invalid.
+ * @return the boolean value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract boolean getBoolean (String key, boolean deflt);
+
+ /**
+ * Gets the {@code byte} array value mapped to the given key or its default value if
+ * no value is mapped, if the backing store is unavailable, or if the value is an
+ * invalid string.
+ *
+ * To be valid, the value string must be Base64-encoded binary data. The Base64 encoding
+ * is as defined in RFC
+ * 2045, section 6.8.
+ *
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key, if the backing store is unavailable, or if the
+ * value is invalid.
+ * @return the byte array value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract byte[] getByteArray (String key, byte[] deflt);
+
+ /**
+ * Gets the {@code double} value mapped to the given key or its default value if no
+ * value is mapped, if the backing store is unavailable, or if the value is an invalid
+ * string.
+ *
+ * To be valid, the value string must be a string that can be converted to a {@code double} by
+ * {@link Double#parseDouble(String) Double.parseDouble(String)}.
+ *
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key, if the backing store is unavailable, or if the
+ * value is invalid.
+ * @return the double value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract double getDouble (String key, double deflt);
+
+ /**
+ * Gets the {@code float} value mapped to the given key or its default value if no
+ * value is mapped, if the backing store is unavailable, or if the value is an invalid
+ * string.
+ *
+ * To be valid, the value string must be a string that can be converted to a {@code float} by
+ * {@link Float#parseFloat(String) Float.parseFloat(String)}.
+ *
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key, if the backing store is unavailable, or if the
+ * value is invalid.
+ * @return the float value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract float getFloat (String key, float deflt);
+
+ /**
+ * Gets the {@code int} value mapped to the given key or its default value if no
+ * value is mapped, if the backing store is unavailable, or if the value is an invalid
+ * string.
+ *
+ * To be valid, the value string must be a string that can be converted to an {@code int} by
+ * {@link Integer#parseInt(String) Integer.parseInt(String)}.
+ *
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key, if the backing store is unavailable, or if the
+ * value is invalid.
+ * @return the integer value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract int getInt (String key, int deflt);
+
+ /**
+ * Gets the {@code long} value mapped to the given key or its default value if no
+ * value is mapped, if the backing store is unavailable, or if the value is an invalid
+ * string.
+ *
+ * To be valid, the value string must be a string that can be converted to a {@code long} by
+ * {@link Long#parseLong(String) Long.parseLong(String)}.
+ *
+ *
+ * Some implementations may store default values in backing stores. In this
+ * case, if there is no value mapped to the given key, the stored default
+ * value is returned.
+ *
+ *
+ * @param key
+ * the preference key.
+ * @param deflt
+ * the default value, which will be returned if no value is
+ * mapped to the given key, if the backing store is unavailable, or if the
+ * value is invalid.
+ * @return the long value mapped to the given key.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws NullPointerException
+ * if the parameter {@code key} is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract long getLong (String key, long deflt);
+
+ /**
+ * Imports all the preferences from an XML document using the given input
+ * stream.
+ *
+ * This XML document uses the UTF-8 encoding and must be written according to the
+ * DTD in its DOCTYPE declaration, which must be the following:
+ *
+ *
+ * <!DOCTYPE preferences SYSTEM "http://java.sun.com/dtd/preferences.dtd">
+ *
+ *
+ * Please note that (unlike the methods of this class that don't concern serialization), this call is not thread-safe.
+ *
+ *
+ * @param istream
+ * the input stream to read the data from.
+ * @throws InvalidPreferencesFormatException
+ * if the data read from the given input stream is not from a
+ * valid XML document.
+ * @throws IOException
+ * if an error occurs while importing.
+ * @throws SecurityException
+ * if {@code RuntimePermission("preferences")} is denied by a
+ * SecurityManager.
+ * @since Android 1.0
+ */
+ public static void importPreferences (InputStream istream) throws InvalidPreferencesFormatException, IOException {
+ checkSecurity();
+ if(null == istream){
+ // prefs.0=Inputstream cannot be null\!
+ throw new MalformedURLException(Messages.getString("prefs.0")); //$NON-NLS-1$
+ }
+ XMLParser.importPrefs(istream);
+ }
+
+ /**
+ * Returns whether this is a user preference node.
+ *
+ * @return {@code true}, if this is a user preference node, {@code false} if
+ * this is a system preference node.
+ * @since Android 1.0
+ */
+ public abstract boolean isUserNode();
+
+ /**
+ * Returns all preference keys stored in this node or an empty array if no
+ * key was found.
+ *
+ * @return the list of all preference keys of this node.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract String[] keys() throws BackingStoreException;
+
+ /**
+ * Returns the name of this node.
+ *
+ * @return the name of this node.
+ * @since Android 1.0
+ */
+ public abstract String name();
+
+ /**
+ * Returns the preference node with the given path name. The path name can
+ * be relative or absolute. The requested node and its ancestors will
+ * be created if they do not exist.
+ *
+ * The path is treated as relative to this node if it doesn't start with a
+ * slash, otherwise it will be treated as an absolute path.
+ *
+ *
+ * @param path
+ * the path name of the requested preference node.
+ * @return the requested preference node.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws IllegalArgumentException
+ * if the path name is invalid.
+ * @throws NullPointerException
+ * if the given path is {@code null}.
+ * @since Android 1.0
+ */
+ public abstract Preferences node (String path);
+
+ /**
+ * Returns whether the preference node with the given path name exists. The
+ * path is treated as relative to this node if it doesn't start with a slash,
+ * otherwise it is treated as an absolute path.
+ *
+ * Please note that if this node has been removed, an invocation of this
+ * node will throw an {@code IllegalStateException} unless the given path is
+ * an empty string, which will return {@code false}.
+ *
+ *
+ * @param path
+ * the path name of the preference node to query.
+ * @return {@code true}, if the queried preference node exists, {@code false}
+ * otherwise.
+ * @throws IllegalStateException
+ * if this node has been removed and the path is not an empty
+ * string.
+ * @throws IllegalArgumentException
+ * if the path name is invalid.
+ * @throws NullPointerException
+ * if the given path is {@code null}.
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @since Android 1.0
+ */
+ public abstract boolean nodeExists (String path) throws BackingStoreException;
+
+ /**
+ * Returns the parent preference node of this node or {@code null} if this
+ * node is the root node.
+ *
+ * @return the parent preference node of this node.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract Preferences parent();
+
+ /**
+ * Adds a new preference to this node using the given key and value or
+ * updates the value if a preference with the given key already exists.
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference value for the given key.
+ * @throws NullPointerException
+ * if the given key or value is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH} or the value's length is bigger than {@code
+ * MAX_VALUE_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void put (String key, String value);
+
+ /**
+ * Adds a new preference with a {@code boolean} value to this node using the given
+ * key and value or updates the value if a preference with the given key
+ * already exists.
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference {@code boolean} value for the given key.
+ * @throws NullPointerException
+ * if the given key is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void putBoolean (String key, boolean value);
+
+ /**
+ * Adds a new preference to this node using the given key and the string
+ * form of the given value or updates the value if a preference with the
+ * given key already exists.
+ *
+ * The string form of the value is the Base64-encoded binary data of the
+ * given byte array. The Base64 encoding is as defined in RFC 2045, section 6.8.
+ *
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference value for the given key.
+ * @throws NullPointerException
+ * if the given key or value is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH} or value's length is bigger than three
+ * quarters of {@code MAX_KEY_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void putByteArray (String key, byte[] value);
+
+ /**
+ * Adds a new preference to this node using the given key and {@code double}
+ * value or updates the value if a preference with the
+ * given key already exists.
+ *
+ * The value is stored in its string form, which is the result of invoking
+ * {@link Double#toString(double) Double.toString(double)}.
+ *
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference value for the given key.
+ * @throws NullPointerException
+ * if the given key is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void putDouble (String key, double value);
+
+ /**
+ * Adds a new preference to this node using the given key and {@code float}
+ * value or updates the value if a preference with the
+ * given key already exists.
+ *
+ * The value is stored in its string form, which is the result of invoking
+ * {@link Float#toString(float) Float.toString(float)}.
+ *
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference value for the given key.
+ * @throws NullPointerException
+ * if the given key is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void putFloat (String key, float value);
+
+ /**
+ * Adds a new preference to this node using the given key and {@code int}
+ * value or updates the value if a preference with the
+ * given key already exists.
+ *
+ * The value is stored in its string form, which is the result of invoking
+ * {@link Integer#toString(int) Integer.toString(int)}.
+ *
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference value for the given key.
+ * @throws NullPointerException
+ * if the given key is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void putInt (String key, int value);
+
+ /**
+ * Adds a new preference to this node using the given key and {@code long}
+ * value or updates the value if a preference with the
+ * given key already exists.
+ *
+ * The value is stored in its string form, which is the result of invoking
+ * {@link Long#toString(long) Long.toString(long)}.
+ *
+ *
+ * @param key
+ * the preference key to be added or updated.
+ * @param value
+ * the preference value for the given key.
+ * @throws NullPointerException
+ * if the given key is {@code null}.
+ * @throws IllegalArgumentException
+ * if the given key's length is bigger than {@code
+ * MAX_KEY_LENGTH}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void putLong (String key, long value);
+
+ /**
+ * Removes the preference mapped to the given key from this node.
+ *
+ * @param key
+ * the key of the preference to be removed.
+ * @throws NullPointerException
+ * if the given key is {@code null}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void remove (String key);
+
+ /**
+ * Removes this preference node with all its descendants. The removal
+ * won't necessarily be persisted until the method {@code flush()} is invoked.
+ *
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @throws UnsupportedOperationException
+ * if this is a root node.
+ * @since Android 1.0
+ */
+ public abstract void removeNode() throws BackingStoreException;
+
+ /**
+ * Registers a {@code NodeChangeListener} instance for this node, which will handle
+ * {@code NodeChangeEvent}s. {@code NodeChangeEvent}s will be fired when a child node has
+ * been added to or removed from this node.
+ *
+ * @param ncl
+ * the listener to be registered.
+ * @throws NullPointerException
+ * if the given listener is {@code null}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void addNodeChangeListener (NodeChangeListener ncl);
+
+ /**
+ * Registers a {@code PreferenceChangeListener} instance for this node, which will
+ * handle {@code PreferenceChangeEvent}s. {@code PreferenceChangeEvent}s will be fired when
+ * a preference has been added to, removed from, or updated for this node.
+ *
+ * @param pcl
+ * the listener to be registered.
+ * @throws NullPointerException
+ * if the given listener is {@code null}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void addPreferenceChangeListener (PreferenceChangeListener pcl);
+
+ /**
+ * Removes the given {@code NodeChangeListener} instance from this node.
+ *
+ * @param ncl
+ * the listener to be removed.
+ * @throws IllegalArgumentException
+ * if the given listener is {@code null}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void removeNodeChangeListener (NodeChangeListener ncl);
+
+ /**
+ * Removes the given {@code PreferenceChangeListener} instance from this node.
+ *
+ * @param pcl
+ * the listener to be removed.
+ * @throws IllegalArgumentException
+ * if the given listener is {@code null}.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void removePreferenceChangeListener (PreferenceChangeListener pcl);
+
+ /**
+ * Synchronizes the data of this preference node and its descendants with
+ * the back-end preference store. Any changes found in the back-end data should be reflected
+ * in this node and its descendants, and at the same time any local changes to this node and
+ * descendants should be persisted.
+ *
+ * @throws BackingStoreException
+ * if the backing store is unavailable or causes an operation
+ * failure.
+ * @throws IllegalStateException
+ * if this node has been removed.
+ * @since Android 1.0
+ */
+ public abstract void sync() throws BackingStoreException;
+
+ /**
+ * Returns the system preference node for the package of the given class.
+ * The absolute path of the returned node is one slash followed by the given
+ * class's full package name, replacing each period character ('.') with
+ * a slash. For example, the absolute path of the preference associated with
+ * the class Object would be "/java/lang". As a special case, the unnamed
+ * package is associated with a preference node "/<unnamed>". This
+ * method will create the node and its ancestors as needed. Any nodes created
+ * by this method won't necessarily be persisted until the method {@code flush()} is
+ * invoked.
+ *
+ * @param c
+ * the given class.
+ * @return the system preference node for the package of the given class.
+ * @throws NullPointerException
+ * if the given class is {@code null}.
+ * @throws SecurityException
+ * if the {@code RuntimePermission("preferences")} is denied by
+ * a SecurityManager.
+ * @since Android 1.0
+ */
+ public static Preferences systemNodeForPackage (Class> c) {
+ checkSecurity();
+ return factory.systemRoot().node(getNodeName(c));
+ }
+
+ /**
+ * Returns the root node of the system preference hierarchy.
+ *
+ * @return the system preference hierarchy root node.
+ * @throws SecurityException
+ * if the {@code RuntimePermission("preferences")} is denied by
+ * a SecurityManager.
+ * @since Android 1.0
+ */
+ public static Preferences systemRoot() {
+ checkSecurity();
+ return factory.systemRoot();
+ }
+
+ //check the RuntimePermission("preferences")
+ private static void checkSecurity() {
+ SecurityManager manager = System.getSecurityManager();
+ if(null != manager){
+ manager.checkPermission(PREFS_PERM);
+ }
+
+ }
+
+ /**
+ * Returns the user preference node for the package of the given class.
+ * The absolute path of the returned node is one slash followed by the given
+ * class's full package name, replacing each period character ('.') with
+ * a slash. For example, the absolute path of the preference associated with
+ * the class Object would be "/java/lang". As a special case, the unnamed
+ * package is associated with a preference node "/<unnamed>". This
+ * method will create the node and its ancestors as needed. Any nodes created
+ * by this method won't necessarily be persisted until the method {@code flush()} is
+ * invoked.
+ *
+ * @param c
+ * the given class.
+ * @return the user preference node for the package of the given class.
+ * @throws NullPointerException
+ * if the given class is {@code null}.
+ * @throws SecurityException
+ * if the {@code RuntimePermission("preferences")} is denied by
+ * a SecurityManager.
+ * @since Android 1.0
+ */
+ public static Preferences userNodeForPackage (Class> c) {
+ checkSecurity();
+ return factory.userRoot().node(getNodeName(c));
+ }
+
+ //parse node's absolute path from class instance
+ private static String getNodeName(Class> c){
+ // ??? PREFS TODO change back to harmony code once getPackage
+ // delivers the correct results
+ // Package p = c.getPackage();
+ // if(null == p){
+ // return "/"; //$NON-NLS-1$
+ // }
+ // return "/"+p.getName().replace('.', '/'); //$NON-NLS-1$
+ int dotIndex = c.getName().lastIndexOf(".");
+ return "/" + c.getName().substring(0, dotIndex).replace(".", "/");
+ }
+
+ /**
+ * Returns the root node of the user preference hierarchy.
+ *
+ * @return the user preference hierarchy root node.
+ * @throws SecurityException
+ * if the {@code RuntimePermission("preferences")} is denied by
+ * a SecurityManager.
+ * @since Android 1.0
+ */
+ public static Preferences userRoot() {
+ checkSecurity();
+ return factory.userRoot();
+ }
+
+ /**
+ * Returns a string representation of this node. The format is "User/System
+ * Preference Node: " followed by this node's absolute path.
+ *
+ * @return the string representation of this node.
+ * @since Android 1.0
+ */
+ @Override
+ public abstract String toString();
+}
diff --git a/prefs/src/main/java/java/util/prefs/PreferencesFactory.java b/prefs/src/main/java/java/util/prefs/PreferencesFactory.java
new file mode 100644
index 0000000..e56dd95
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/PreferencesFactory.java
@@ -0,0 +1,49 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.prefs;
+
+/**
+ * This interface is used by the {@link Preferences} class as factory class to
+ * create {@code Preferences} instances. This interface can be implemented and installed
+ * to replace the default preferences implementation.
+ *
+ * @since Android 1.0
+ */
+public interface PreferencesFactory {
+
+ /**
+ * Returns the root node of the preferences hierarchy for the calling user
+ * context.
+ *
+ * @return the user preferences hierarchy root node.
+ * @since Android 1.0
+ */
+ Preferences userRoot();
+
+ /**
+ * Returns the root node of the system preferences hierarchy.
+ *
+ * @return the system preferences hierarchy root node.
+ * @since Android 1.0
+ */
+ Preferences systemRoot();
+}
+
+
+
+
diff --git a/prefs/src/main/java/java/util/prefs/XMLParser.java b/prefs/src/main/java/java/util/prefs/XMLParser.java
new file mode 100644
index 0000000..2edfc71
--- /dev/null
+++ b/prefs/src/main/java/java/util/prefs/XMLParser.java
@@ -0,0 +1,621 @@
+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package java.util.prefs;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.StringReader;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import javax.xml.parsers.ParserConfigurationException;
+// BEGIN android-removed
+// import javax.xml.transform.TransformerException;
+// END android-removed
+
+import org.apache.harmony.prefs.internal.nls.Messages;
+// BEGIN android-removed
+// import org.apache.xpath.XPathAPI;
+// END android-removed
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+// BEGIN android-added
+import java.util.ArrayList;
+import org.w3c.dom.DocumentType;
+import org.w3c.dom.Node;
+// END android-added
+
+/**
+ * Utility class for importing and exporting {@code Preferences} data from an XML file.
+ *
+ * @since Android 1.0
+ */
+class XMLParser {
+
+ /*
+ * Constant - the specified DTD URL
+ */
+ static final String PREFS_DTD_NAME = "http://java.sun.com/dtd/preferences.dtd"; //$NON-NLS-1$
+
+ /*
+ * Constant - the DTD string
+ */
+ static final String PREFS_DTD = "" //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " " //$NON-NLS-1$
+ + " "; //$NON-NLS-1$
+
+ /*
+ * Constant - the specified header
+ */
+ static final String HEADER = ""; //$NON-NLS-1$
+
+ /*
+ * Constant - the specified DOCTYPE
+ */
+ static final String DOCTYPE = ""; //$NON-NLS-1$
+
+ /*
+ * Constant - specify the DTD version
+ */
+ private static final float XML_VERSION = 1.0f;
+
+ /*
+ * DOM builder
+ */
+ private static final DocumentBuilder builder;
+
+ /*
+ * specify the indent level
+ */
+ private static int indent = -1;
+
+ /*
+ * init DOM builder
+ */
+ static {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ // BEGIN android-changed
+ factory.setValidating(false);
+ // END android-changed
+ try {
+ builder = factory.newDocumentBuilder();
+ } catch (ParserConfigurationException e) {
+ throw new Error(e);
+ }
+ builder.setEntityResolver(new EntityResolver() {
+ public InputSource resolveEntity(String publicId, String systemId)
+ throws SAXException, IOException {
+ if (systemId.equals(PREFS_DTD_NAME)) {
+ InputSource result = new InputSource(new StringReader(
+ PREFS_DTD));
+ result.setSystemId(PREFS_DTD_NAME);
+ return result;
+ }
+ // prefs.1=Invalid DOCTYPE declaration: {0}
+ throw new SAXException(
+ Messages.getString("prefs.1", systemId)); //$NON-NLS-1$
+ }
+ });
+ builder.setErrorHandler(new ErrorHandler() {
+ public void warning(SAXParseException e) throws SAXException {
+ throw e;
+ }
+
+ public void error(SAXParseException e) throws SAXException {
+ throw e;
+ }
+
+ public void fatalError(SAXParseException e) throws SAXException {
+ throw e;
+ }
+ });
+ }
+
+ private XMLParser() {// empty constructor
+ }
+
+ /***************************************************************************
+ * utilities for Preferences export
+ **************************************************************************/
+ static void exportPrefs(Preferences prefs, OutputStream stream,
+ boolean withSubTree) throws IOException, BackingStoreException {
+ indent = -1;
+ // BEGIN android-modified
+ BufferedWriter out = new BufferedWriter(new OutputStreamWriter(stream, "UTF-8"), 8192); //$NON-NLS-1$
+ // END android-modified
+ out.write(HEADER);
+ out.newLine();
+ out.newLine();
+
+ out.write(DOCTYPE);
+ out.write(" '"); //$NON-NLS-1$
+ out.write(PREFS_DTD_NAME);
+ out.write("'>"); //$NON-NLS-1$
+ out.newLine();
+ out.newLine();
+
+ flushStartTag(
+ "preferences", new String[] { "EXTERNAL_XML_VERSION" }, new String[] { String.valueOf(XML_VERSION) }, out); //$NON-NLS-1$ //$NON-NLS-2$
+ flushStartTag(
+ "root", new String[] { "type" }, new String[] { prefs.isUserNode() ? "user" : "system" }, out); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ flushEmptyElement("map", out); //$NON-NLS-1$
+
+ StringTokenizer ancestors = new StringTokenizer(prefs.absolutePath(),
+ "/"); //$NON-NLS-1$
+ exportNode(ancestors, prefs, withSubTree, out);
+
+ flushEndTag("root", out); //$NON-NLS-1$
+ flushEndTag("preferences", out); //$NON-NLS-1$
+ out.flush();
+ out = null;
+ }
+
+ private static void exportNode(StringTokenizer ancestors,
+ Preferences prefs, boolean withSubTree, BufferedWriter out)
+ throws IOException, BackingStoreException {
+ if (ancestors.hasMoreTokens()) {
+ String name = ancestors.nextToken();
+ flushStartTag(
+ "node", new String[] { "name" }, new String[] { name }, out); //$NON-NLS-1$ //$NON-NLS-2$
+ if (ancestors.hasMoreTokens()) {
+ flushEmptyElement("map", out); //$NON-NLS-1$
+ exportNode(ancestors, prefs, withSubTree, out);
+ } else {
+ exportEntries(prefs, out);
+ if (withSubTree) {
+ exportSubTree(prefs, out);
+ }
+ }
+ flushEndTag("node", out); //$NON-NLS-1$
+ }
+ }
+
+ private static void exportSubTree(Preferences prefs, BufferedWriter out)
+ throws BackingStoreException, IOException {
+ String[] names = prefs.childrenNames();
+ if (names.length > 0) {
+ for (int i = 0; i < names.length; i++) {
+ Preferences child = prefs.node(names[i]);
+ flushStartTag(
+ "node", new String[] { "name" }, new String[] { names[i] }, out); //$NON-NLS-1$ //$NON-NLS-2$
+ exportEntries(child, out);
+ exportSubTree(child, out);
+ flushEndTag("node", out); //$NON-NLS-1$
+ }
+ }
+ }
+
+ private static void exportEntries(Preferences prefs, BufferedWriter out)
+ throws BackingStoreException, IOException {
+ String[] keys = prefs.keys();
+ String[] values = new String[keys.length];
+ for (int i = 0; i < keys.length; i++) {
+ values[i] = prefs.get(keys[i], null);
+ }
+ exportEntries(keys, values, out);
+ }
+
+ private static void exportEntries(String[] keys, String[] values,
+ BufferedWriter out) throws IOException {
+ if (keys.length == 0) {
+ flushEmptyElement("map", out); //$NON-NLS-1$
+ return;
+ }
+ flushStartTag("map", out); //$NON-NLS-1$
+ for (int i = 0; i < keys.length; i++) {
+ if (values[i] != null) {
+ flushEmptyElement(
+ "entry", new String[] { "key", "value" }, new String[] { keys[i], values[i] }, out); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+ flushEndTag("map", out); //$NON-NLS-1$
+ }
+
+ private static void flushEndTag(String tagName, BufferedWriter out)
+ throws IOException {
+ flushIndent(indent--, out);
+ out.write(""); //$NON-NLS-1$
+ out.write(tagName);
+ out.write(">"); //$NON-NLS-1$
+ out.newLine();
+ }
+
+ private static void flushEmptyElement(String tagName, BufferedWriter out)
+ throws IOException {
+ flushIndent(++indent, out);
+ out.write("<"); //$NON-NLS-1$
+ out.write(tagName);
+ out.write(" />"); //$NON-NLS-1$
+ out.newLine();
+ indent--;
+ }
+
+ private static void flushEmptyElement(String tagName, String[] attrKeys,
+ String[] attrValues, BufferedWriter out) throws IOException {
+ flushIndent(++indent, out);
+ out.write("<"); //$NON-NLS-1$
+ out.write(tagName);
+ flushPairs(attrKeys, attrValues, out);
+ out.write(" />"); //$NON-NLS-1$
+ out.newLine();
+ indent--;
+ }
+
+ private static void flushPairs(String[] attrKeys, String[] attrValues,
+ BufferedWriter out) throws IOException {
+ for (int i = 0; i < attrKeys.length; i++) {
+ out.write(" "); //$NON-NLS-1$
+ out.write(attrKeys[i]);
+ out.write("=\""); //$NON-NLS-1$
+ out.write(htmlEncode(attrValues[i]));
+ out.write("\""); //$NON-NLS-1$
+ }
+ }
+
+ private static void flushIndent(int ind, BufferedWriter out)
+ throws IOException {
+ for (int i = 0; i < ind; i++) {
+ out.write(" "); //$NON-NLS-1$
+ }
+ }
+
+ private static void flushStartTag(String tagName, String[] attrKeys,
+ String[] attrValues, BufferedWriter out) throws IOException {
+ flushIndent(++indent, out);
+ out.write("<"); //$NON-NLS-1$
+ out.write(tagName);
+ flushPairs(attrKeys, attrValues, out);
+ out.write(">"); //$NON-NLS-1$
+ out.newLine();
+ }
+
+ private static void flushStartTag(String tagName, BufferedWriter out)
+ throws IOException {
+ flushIndent(++indent, out);
+ out.write("<"); //$NON-NLS-1$
+ out.write(tagName);
+ out.write(">"); //$NON-NLS-1$
+ out.newLine();
+ }
+
+ private static String htmlEncode(String s) {
+ StringBuffer sb = new StringBuffer();
+ char c;
+ for (int i = 0; i < s.length(); i++) {
+ c = s.charAt(i);
+ switch (c) {
+ case '<':
+ sb.append("<"); //$NON-NLS-1$
+ break;
+ case '>':
+ sb.append(">"); //$NON-NLS-1$
+ break;
+ case '&':
+ sb.append("&"); //$NON-NLS-1$
+ break;
+ case '\\':
+ sb.append("'"); //$NON-NLS-1$
+ break;
+ case '"':
+ sb.append("""); //$NON-NLS-1$
+ break;
+ default:
+ sb.append(c);
+ }
+ }
+ return sb.toString();
+ }
+
+ /***************************************************************************
+ * utilities for Preferences import
+ **************************************************************************/
+ static void importPrefs(InputStream in) throws IOException,
+ InvalidPreferencesFormatException {
+ try {
+ // load XML document
+ Document doc = builder.parse(new InputSource(in));
+
+ // check preferences' export version
+ Element preferences;
+ preferences = doc.getDocumentElement();
+ String version = preferences.getAttribute("EXTERNAL_XML_VERSION"); //$NON-NLS-1$
+ if (version != null && Float.parseFloat(version) > XML_VERSION) {
+ // prefs.2=This preferences exported version is not supported:{0}
+ throw new InvalidPreferencesFormatException(
+ Messages.getString("prefs.2", version)); //$NON-NLS-1$
+ }
+
+ // check preferences root's type
+ Element root = (Element) preferences
+ .getElementsByTagName("root").item(0); //$NON-NLS-1$
+ Preferences prefsRoot = null;
+ String type = root.getAttribute("type"); //$NON-NLS-1$
+ if (type.equals("user")) { //$NON-NLS-1$
+ prefsRoot = Preferences.userRoot();
+ } else {
+ prefsRoot = Preferences.systemRoot();
+ }
+
+ // load node
+ loadNode(prefsRoot, root);
+ } catch (FactoryConfigurationError e) {
+ throw new InvalidPreferencesFormatException(e);
+ } catch (SAXException e) {
+ throw new InvalidPreferencesFormatException(e);
+ }
+ // BEGIN android-removed
+ // catch (TransformerException e) {
+ // throw new InvalidPreferencesFormatException(e);
+ // }
+ // END android-removed
+ }
+
+ private static void loadNode(Preferences prefs, Element node) {
+ // BEGIN android-note
+ // removed throw clause for TransformerException
+ // END android-note
+ // load preferences
+ // BEGIN android-changed
+ NodeList children = selectNodeList(node, "node"); //$NON-NLS-1$
+ NodeList entries = selectNodeList(node, "map/entry"); //$NON-NLS-1$
+ // END android-changed
+ int childNumber = children.getLength();
+ Preferences[] prefChildren = new Preferences[childNumber];
+ int entryNumber = entries.getLength();
+ synchronized (((AbstractPreferences) prefs).lock) {
+ if (((AbstractPreferences) prefs).isRemoved()) {
+ return;
+ }
+ for (int i = 0; i < entryNumber; i++) {
+ Element entry = (Element) entries.item(i);
+ String key = entry.getAttribute("key"); //$NON-NLS-1$
+ String value = entry.getAttribute("value"); //$NON-NLS-1$
+ prefs.put(key, value);
+ }
+ // get children preferences node
+ for (int i = 0; i < childNumber; i++) {
+ Element child = (Element) children.item(i);
+ String name = child.getAttribute("name"); //$NON-NLS-1$
+ prefChildren[i] = prefs.node(name);
+ }
+ }
+
+ // load children nodes after unlock
+ for (int i = 0; i < childNumber; i++) {
+ loadNode(prefChildren[i], (Element) children.item(i));
+ }
+ }
+
+ // BEGIN android-added
+ // TODO dirty implementation of a method from javax.xml.xpath
+ // should be replaced with a call to a good impl of this method
+ private static NodeList selectNodeList(Element documentElement, String string) {
+
+ NodeList result = null;
+
+ ArrayList input = new ArrayList();
+
+ String[] path = string.split("/");
+
+ NodeList childNodes = documentElement.getChildNodes();
+
+ if(path[0].equals("entry") || path[0].equals("node")) {
+ for(int i = 0; i < childNodes.getLength(); i++) {
+ Object next = childNodes.item(i);
+ if(next instanceof Element) {
+ if(((Element) next).getNodeName().equals(path[0])
+ && next instanceof Node) {
+ input.add((Node)next);
+ }
+ }
+ }
+ } else if(path[0].equals("map") && path[1].equals("entry")) {
+ for(int i = 0; i < childNodes.getLength(); i++) {
+ Object next = childNodes.item(i);
+ if(next instanceof Element) {
+ if(((Element) next).getNodeName().equals(path[0])
+ && next instanceof Node) {
+ NodeList nextChildNodes = ((Node)next).getChildNodes();
+ for(int j = 0; j < nextChildNodes.getLength(); j++) {
+ Object subnext = nextChildNodes.item(j);
+ if(subnext instanceof Element) {
+ if(((Element)subnext).getNodeName().equals(path[1])) {
+ input.add((Node)subnext);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ result = new NodeSet(input.iterator());
+
+ return result;
+ }
+ // END android-added
+
+ /***************************************************************************
+ * utilities for FilePreferencesImpl, which is default implementation of Linux platform
+ **************************************************************************/
+ /**
+ * load preferences from file, if cannot load, create a new one FIXME: need
+ * lock or not?
+ *
+ * @param file the XML file to be read
+ * @return Properties instance which indicates the preferences key-value pairs
+ */
+ static Properties loadFilePrefs(final File file) {
+ return AccessController.doPrivileged(new PrivilegedAction() {
+ public Properties run() {
+ return loadFilePrefsImpl(file);
+ }
+ });
+
+ // try {
+ // //FIXME: lines below can be deleted, because it is not required to
+ // persistent at the very beginning
+ // flushFilePrefs(file, result);
+ // } catch (IOException e) {
+ // e.printStackTrace();
+ // }
+ }
+
+ static Properties loadFilePrefsImpl(final File file) {
+ Properties result = new Properties();
+ if (!file.exists()) {
+ file.getParentFile().mkdirs();
+ } else if (file.canRead()) {
+ InputStream in = null;
+ FileLock lock = null;
+ try {
+
+ FileInputStream istream = new FileInputStream(file);
+ // BEGIN android-modified
+ in = new BufferedInputStream(istream, 8192);
+ // END android-modified
+ FileChannel channel = istream.getChannel();
+ lock = channel.lock(0L, Long.MAX_VALUE, true);
+ Document doc = builder.parse(in);
+ // BEGIN android-modified
+ NodeList entries = selectNodeList(doc
+ .getDocumentElement(), "entry"); //$NON-NLS-1$
+ // END android-modified
+ int length = entries.getLength();
+ for (int i = 0; i < length; i++) {
+ Element node = (Element) entries.item(i);
+ String key = node.getAttribute("key"); //$NON-NLS-1$
+ String value = node.getAttribute("value"); //$NON-NLS-1$
+ result.setProperty(key, value);
+ }
+ return result;
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ try {
+ lock.release();
+ } catch (Exception e) {//ignore
+ }
+ try {
+ in.close();
+ } catch (Exception e) {//ignore
+ }
+ }
+ } else {
+ file.delete();
+ }
+ return result;
+ }
+
+ /**
+ *
+ * @param file
+ * @param prefs
+ * @throws PrivilegedActionException
+ */
+ static void flushFilePrefs(final File file, final Properties prefs) throws PrivilegedActionException {
+ AccessController.doPrivileged(new PrivilegedExceptionAction