Author: oheger Date: Wed Apr 16 23:40:56 2008 New Revision: 648976 URL: http://svn.apache.org/viewvc?rev=648976&view=rev Log: Initial implementation of PreferencesConfiguration
Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java (with props) commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java (with props) Added: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java?rev=648976&view=auto ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java (added) +++ commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java Wed Apr 16 23:40:56 2008 @@ -0,0 +1,425 @@ +/* + * 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 org.apache.commons.configuration2; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import org.apache.commons.configuration2.expr.AbstractNodeHandler; +import org.apache.commons.configuration2.expr.ExpressionEngine; +import org.apache.commons.configuration2.expr.def.DefaultExpressionEngine; + +/** + * <p> + * A configuration implementation on top of the Java <code>Preferences</code> + * API. + * </p> + * <p> + * This implementation of the <code>Configuration</code> interface is backed + * by a <code>java.util.prefs.Preferences</code> node. Query or update + * operations are directly performed on this node and its descendants. When + * creating this configuration the underlying <code>Preferences</code> node + * can be determined: + * <ul> + * <li>the system root node</li> + * <li>the user root node</li> + * <li>a system node corresponding to a specific package</li> + * <li>a user node corresponding to a specific package</li> + * </ul> + * This corresponds to the static factory methods provided by the + * <code>Preferences</code> class. It is also possible to change this node + * later by calling <code>setAssociatedClass()</code> or + * <code>setSystem()</code>. + * </p> + * <p> + * With this class the power provided by the <code>Configuration</code> + * interface can be used for interacting with <code>Preferences</code> nodes. + * Note however that some features provided by the <code>Configuration</code> + * interface are not supported by the <code>Preferences</code> API. One + * example of such a feature is the support for multiple values for a property. + * </p> + * + * @author Oliver Heger + * @version $Id$ + */ +public class PreferencesConfiguration extends + AbstractHierarchicalConfiguration<Preferences> +{ + /** A lock for protecting the root node. */ + private Lock lockRoot; + + /** Stores the associated preferences node. */ + private Preferences root; + + /** Stores the class to be used when obtaining the root node. */ + private Class<?> associatedClass; + + /** Stores the system flag. */ + private boolean system; + + /** + * Creates a new instance of <code>PreferencesConfiguration</code> that + * accesses the user root node. + */ + public PreferencesConfiguration() + { + this(false, null); + } + + /** + * Creates a new instance of <code>PreferencesConfiguration</code> that + * accesses either the system or the user root node. + * + * @param system <b>true</b> for the system root node, <b>false</b> for + * the user root node + */ + public PreferencesConfiguration(boolean system) + { + this(system, null); + } + + /** + * Creates a new instance of <code>PreferencesConfiguration</code> that + * accesses the user preferences node associated with the package of the + * specified class. + * + * @param c the class defining the desired package + */ + public PreferencesConfiguration(Class<?> c) + { + this(false, c); + } + + /** + * Creates a new instance of <code>PreferencesConfiguration</code> that + * accesses the node associated with the package of the specified class in + * either the user or the system space. + * + * @param system <b>true</b> for the system root node, <b>false</b> for + * the user root node + * @param c the class defining the desired package + */ + public PreferencesConfiguration(boolean system, Class<?> c) + { + super(new PreferencesNodeHandler()); + lockRoot = new ReentrantLock(); + setExpressionEngine(setUpExpressionEngine()); + setAssociatedClass(c); + setSystem(system); + } + + /** + * Returns the class associated with this configuration. + * + * @return the associated class + */ + public Class<?> getAssociatedClass() + { + return associatedClass; + } + + /** + * Sets the associated class. When obtaining the associated + * <code>Preferences</code> node, this class is passed in. + * + * @param associatedClass the associated class + */ + public void setAssociatedClass(Class<?> associatedClass) + { + this.associatedClass = associatedClass; + clearRootNode(); + } + + /** + * Returns the system flag. This flag determines whether system or user + * preferences are used. + * + * @return the system flag + */ + public boolean isSystem() + { + return system; + } + + /** + * Sets the system flag. This flag determines whether system or user + * preferences are used. + * + * @param system the system flag + */ + public void setSystem(boolean system) + { + this.system = system; + clearRootNode(); + } + + /** + * Returns the root node of this configuration. This is a + * <code>Preferences</code> node, which is specified by the properties + * <code>associatedClass</code> and <code>system</code>. + * + * @return the root node + */ + @Override + public Preferences getRootNode() + { + lockRoot.lock(); + try + { + if (root == null) + { + root = createRootNode(); + } + return root; + } + finally + { + lockRoot.unlock(); + } + } + + /** + * Saves all changes made at this configuration. Calls <code>flush()</code> + * on the underlying <code>Preferences</code> node. This causes the + * preferences to be written back to the backing store. + * + * @throws ConfigurationRuntimeException if an error occurs + */ + public void flush() + { + try + { + getRootNode().flush(); + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException( + "Could not flush configuration", bex); + } + } + + /** + * Creates the root node of this configuration. This method has to evaluate + * the system flag and the package to obtain the correct preferences node. + * + * @return the root preferences node of this configuration + */ + protected Preferences createRootNode() + { + if (getAssociatedClass() != null) + { + return isSystem() ? Preferences + .systemNodeForPackage(getAssociatedClass()) : Preferences + .userNodeForPackage(getAssociatedClass()); + } + else + { + return isSystem() ? Preferences.systemRoot() : Preferences + .userRoot(); + } + } + + /** + * Creates the expression engine to be used by this configuration. This + * implementation returns an expression engine that uses dots for both nodes + * and attributes. + * + * @return the expression engine to use + */ + protected ExpressionEngine setUpExpressionEngine() + { + DefaultExpressionEngine ex = new DefaultExpressionEngine(); + ex.setAttributeEnd(null); + ex.setAttributeStart(ex.getPropertyDelimiter()); + return ex; + } + + /** + * Resets the root node. This method is called when the parameters of this + * configuration were been changed. When the root node is accessed next + * time, it is re-created. + */ + private void clearRootNode() + { + lockRoot.lock(); + try + { + root = null; + } + finally + { + lockRoot.unlock(); + } + } + + /** + * The node handler for dealing with <code>Preferences</code> nodes. + */ + private static class PreferencesNodeHandler extends + AbstractNodeHandler<Preferences> + { + + public void addAttributeValue(Preferences node, String name, + Object value) + { + node.put(name, String.valueOf(value)); + } + + public Preferences addChild(Preferences node, String name) + { + return node.node(name); + } + + @Override + public Preferences addChild(Preferences node, String name, Object value) + { + addAttributeValue(node, name, value); + return null; + } + + public Object getAttributeValue(Preferences node, String name) + { + return node.get(name, null); + } + + public List<String> getAttributes(Preferences node) + { + try + { + return Arrays.asList(node.keys()); + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + public Preferences getChild(Preferences node, int index) + { + try + { + String[] childrenNames = node.childrenNames(); + return node.node(childrenNames[index]); + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + public List<Preferences> getChildren(Preferences node) + { + try + { + String[] childrenNames = node.childrenNames(); + List<Preferences> result = new ArrayList<Preferences>( + childrenNames.length); + for (String name : childrenNames) + { + result.add(node.node(name)); + } + return result; + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + public List<Preferences> getChildren(Preferences node, String name) + { + try + { + if (node.nodeExists(name)) + { + return Collections.singletonList(node.node(name)); + } + else + { + return Collections.emptyList(); + } + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + public int getChildrenCount(Preferences node, String name) + { + try + { + String[] childrenNames = node.childrenNames(); + return childrenNames.length; + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + public Preferences getParent(Preferences node) + { + return node.parent(); + } + + public Object getValue(Preferences node) + { + return null; + } + + public String nodeName(Preferences node) + { + return node.name(); + } + + public void removeAttribute(Preferences node, String name) + { + node.remove(name); + } + + public void removeChild(Preferences node, Preferences child) + { + try + { + child.removeNode(); + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + public void setAttributeValue(Preferences node, String name, + Object value) + { + addAttributeValue(node, name, value); + } + + public void setValue(Preferences node, Object value) + { + throw new UnsupportedOperationException( + "Cannot set a value of a Preferences node!"); + } + } +} Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Propchange: commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/PreferencesConfiguration.java ------------------------------------------------------------------------------ svn:mime-type = text/plain Added: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java URL: http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java?rev=648976&view=auto ============================================================================== --- commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java (added) +++ commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java Wed Apr 16 23:40:56 2008 @@ -0,0 +1,314 @@ +/* + * 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 org.apache.commons.configuration2; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; +import java.util.prefs.BackingStoreException; +import java.util.prefs.Preferences; + +import junit.framework.TestCase; + +/** + * Test class for PreferencesConfiguration. + * + * @author Oliver Heger + * @version $Id$ + */ +public class TestPreferencesConfiguration extends TestCase +{ + /** Constant for the node with the test data. */ + private static final String TEST_NODE = "PreferencesConfigurationTest"; + + /** A preferences node with the test data. */ + private Preferences node; + + /** + * Clears the test environment. If the test node was created, it is now + * removed. + */ + @Override + protected void tearDown() throws Exception + { + if (node != null && node.nodeExists(TEST_NODE)) + { + Preferences testNode = node.node(TEST_NODE); + testNode.removeNode(); + } + + super.tearDown(); + } + + /** + * Helper method for creating the final key for the given key. Adds the name + * of the test node. + * + * @param k the key + * @return the final key + */ + private static String key(String k) + { + StringBuilder buf = new StringBuilder(TEST_NODE); + buf.append('.').append(k); + return buf.toString(); + } + + /** + * Adds some test data to the test preferences node. + */ + private void setUpTestData() + { + Preferences testNode = node.node(TEST_NODE); + testNode.putBoolean("test", true); + Preferences guiNode = testNode.node("gui"); + guiNode.put("background", "black"); + guiNode.put("foreground", "blue"); + Preferences dbNode = testNode.node("db"); + dbNode.put("user", "scott"); + dbNode.put("pwd", "tiger"); + Preferences tabNode = dbNode.node("tables"); + tabNode.put("tab1", "users"); + tabNode.put("tab2", "documents"); + try + { + testNode.flush(); + } + catch (BackingStoreException bex) + { + throw new ConfigurationRuntimeException(bex); + } + } + + /** + * Tests whether the configuration contains the expected properties. + * + * @param config the test configuration + */ + private void checkProperties(PreferencesConfiguration config) + { + assertTrue("Wrong value for test", config.getBoolean(key("test"))); + assertEquals("Wrong value for background", "black", config + .getString(key("gui.background"))); + assertEquals("Wrong value for foreground", "blue", config + .getString(key("gui.foreground"))); + assertEquals("Wrong value for user", "scott", config + .getString(key("db.user"))); + assertEquals("Wrong value for pwd", "tiger", config + .getString(key("db.pwd"))); + assertEquals("Wrong value for tab1", "users", config + .getString(key("db.tables.tab1"))); + assertEquals("Wrong value for tab2", "documents", config + .getString(key("db.tables.tab2"))); + } + + /** + * Creates some test data and a configuration for accessing it. + * + * @return the test configuration + */ + private PreferencesConfiguration setUpTestConfig() + { + node = Preferences.systemNodeForPackage(getClass()); + setUpTestData(); + return new PreferencesConfiguration(true, getClass()); + } + + /** + * Tests querying properties from the system root node. + */ + public void testGetPropertiesSystem() + { + node = Preferences.systemRoot(); + setUpTestData(); + PreferencesConfiguration config = new PreferencesConfiguration(true); + checkProperties(config); + } + + /** + * Tests querying properties from the user root node. + */ + public void testGetPropertiesUser() + { + node = Preferences.userRoot(); + setUpTestData(); + PreferencesConfiguration config = new PreferencesConfiguration(); + checkProperties(config); + } + + /** + * Tests querying properties from the system node with the given package. + */ + public void testGetPropertiesSystemPackage() + { + node = Preferences.systemNodeForPackage(getClass()); + setUpTestData(); + PreferencesConfiguration config = new PreferencesConfiguration(true, + getClass()); + checkProperties(config); + } + + /** + * Tests querying properties from the user node with the given package. + */ + public void testGetPropertiesUserPackage() + { + PreferencesConfiguration config = setUpTestConfig(); + checkProperties(config); + } + + /** + * Tests whether changing the configuration's parameters causes the root + * node to be re-created. + */ + public void testChangeParameters() + { + PreferencesConfiguration config = new PreferencesConfiguration(); + Preferences root = config.getRootNode(); + config.setAssociatedClass(getClass()); + assertNotSame("Root node not changed", root, config.getRootNode()); + } + + /** + * Tests whether the expected keys are returned. + */ + public void testGetKeys() + { + PreferencesConfiguration config = setUpTestConfig(); + Set<String> keys = new HashSet<String>(); + for (Iterator<String> it = config.getKeys(); it.hasNext();) + { + keys.add(it.next()); + } + assertTrue("Key not found: background", keys + .contains(key("gui.background"))); + assertTrue("Key not found: user", keys.contains(key("db.user"))); + assertTrue("Key not found: tab1", keys.contains(key("db.tables.tab1"))); + } + + /** + * Tests the isEmpty() method. + */ + public void testIsEmpty() + { + PreferencesConfiguration config = setUpTestConfig(); + assertFalse("Configuration is empty", config.isEmpty()); + } + + /** + * Tests adding a new property. + */ + public void testAddProperty() + { + PreferencesConfiguration config = setUpTestConfig(); + config.addProperty(key("anotherTest"), Boolean.TRUE); + config.addProperty(key("db.url"), "testdb"); + config.flush(); + Preferences nd = node.node(TEST_NODE); + assertTrue("test key not set", nd.getBoolean("anotherTest", false)); + nd = nd.node("db"); + assertEquals("Db property not set", "testdb", nd.get("url", null)); + } + + /** + * Tests the addProperty() method when a new node has to be added. + */ + public void testAddPropertyNewNode() + { + PreferencesConfiguration config = setUpTestConfig(); + config.addProperty(key("db.meta.session.mode"), "debug"); + config.flush(); + Preferences nd = node.node(TEST_NODE + "/db/meta/session"); + assertEquals("Property not added", "debug", nd.get("mode", null)); + } + + /** + * Tests overriding a property. + */ + public void testSetProperty() + { + PreferencesConfiguration config = setUpTestConfig(); + config.setProperty(key("db.user"), "harry"); + config.setProperty(key("db.url"), "testdb"); + Preferences nd = node.node(TEST_NODE + "/db"); + assertEquals("Property not added", "testdb", nd.get("url", null)); + assertEquals("Property not set", "harry", nd.get("user", null)); + } + + /** + * Tests whether a property can be removed. + */ + public void testClearProperty() throws BackingStoreException + { + PreferencesConfiguration config = setUpTestConfig(); + config.clearProperty(key("db.tables.tab1")); + Preferences nd = node.node(TEST_NODE + "/db/tables"); + String[] keys = nd.keys(); + assertEquals("Key not removed", 1, keys.length); + assertEquals("Wrong key removed", "tab2", keys[0]); + } + + /** + * Tests removing a whole property tree. + */ + public void testClearTree() throws BackingStoreException + { + PreferencesConfiguration config = setUpTestConfig(); + config.clearTree(key("db")); + assertFalse("Node not removed", node.nodeExists(TEST_NODE + "/db")); + } + + /** + * Tests querying the number of property values. + */ + public void testGetMaxIndex() + { + PreferencesConfiguration config = setUpTestConfig(); + assertEquals("Wrong number of values", 0, config + .getMaxIndex(key("db.user"))); + assertEquals("Wrong number of values for node", 0, config + .getMaxIndex(key("db"))); + assertEquals("Wrong number of values for non ex. property", -1, config + .getMaxIndex("non.ex.key")); + } + + /** + * Tests obtaining a sub configuration. + */ + public void testConfigurationAt() + { + PreferencesConfiguration config = setUpTestConfig(); + SubConfiguration<Preferences> sub = config.configurationAt(TEST_NODE); + assertEquals("Wrong user", "scott", sub.getString("db.user")); + } + + /** + * Tests modifying a sub configuration. + */ + public void testConfigurationAtModify() + { + PreferencesConfiguration config = setUpTestConfig(); + SubConfiguration<Preferences> sub = config.configurationAt(TEST_NODE); + sub.setProperty("db.user", "harry"); + config.setProperty(key("db.pwd"), "dolphin"); + sub.addProperty("db.url", "testdb"); + assertEquals("User not changed", "harry", config + .getString(key("db.user"))); + assertEquals("URL not found", "testdb", config.getString(key("db.url"))); + assertEquals("Pwd not changed", "dolphin", sub.getString("db.pwd")); + } +} Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java ------------------------------------------------------------------------------ svn:eol-style = native Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java ------------------------------------------------------------------------------ svn:keywords = Date Author Id Revision HeadURL Propchange: commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestPreferencesConfiguration.java ------------------------------------------------------------------------------ svn:mime-type = text/plain