Author: oheger
Date: Mon Nov 5 17:30:20 2012
New Revision: 1405890
URL: http://svn.apache.org/viewvc?rev=1405890&view=rev
Log:
Added a utility method to ConfigurationUtils to create immutable configurations.
Added:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
(with props)
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
(with props)
Modified:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
Modified:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java?rev=1405890&r1=1405889&r2=1405890&view=diff
==============================================================================
---
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
(original)
+++
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ConfigurationUtils.java
Mon Nov 5 17:30:20 2012
@@ -23,6 +23,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
@@ -62,6 +63,14 @@ public final class ConfigurationUtils
/** Constant for parsing numbers in hex format. */
private static final int HEX = 16;
+ /**
+ * An array with interfaces to be implemented by a proxy for an immutable
+ * configuration.
+ */
+ private static final Class<?>[] IMMUTABLE_CONFIG_IFCS = {
+ ImmutableConfiguration.class
+ };
+
/** The logger.*/
private static final Log LOG = LogFactory.getLog(ConfigurationUtils.class);
@@ -755,4 +764,30 @@ public final class ConfigurationUtils
}
});
}
+
+ /**
+ * Creates an {@code ImmutableConfiguration} from the given
+ * {@code Configuration} object. This method creates a proxy object
wrapping
+ * the original configuration and making it available under the
+ * {@code ImmutableConfiguration} interface. Through this interface the
+ * configuration cannot be manipulated. It is also not possible to cast the
+ * returned object back to a {@code Configuration} instance to circumvent
+ * this protection.
+ *
+ * @param c the {@code Configuration} to be wrapped (must not be
+ * <b>null</b>)
+ * @return an {@code ImmutableConfiguration} view on the specified
+ * {@code Configuration} object
+ * @throws NullPointerException if the passed in {@code Configuration} is
+ * <b>null</b>
+ * @since 2.0
+ */
+ public static ImmutableConfiguration unmodifiableConfiguration(
+ Configuration c)
+ {
+ return (ImmutableConfiguration) Proxy.newProxyInstance(
+ ConfigurationUtils.class.getClassLoader(),
+ IMMUTABLE_CONFIG_IFCS,
+ new ImmutableConfigurationInvocationHandler(c));
+ }
}
Added:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java?rev=1405890&view=auto
==============================================================================
---
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
(added)
+++
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
Mon Nov 5 17:30:20 2012
@@ -0,0 +1,146 @@
+/*
+ * 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.configuration;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.util.Iterator;
+
+/**
+ * <p>
+ * A specialized {@code InvocationHandler} implementation for supporting
+ * immutable configurations.
+ * </p>
+ * <p>
+ * An instance of this class is constructed with a reference to a
+ * {@code Configuration} object. All method invocations (which stem from the
+ * {@code ImmutableConfiguration} interface) are delegated to this object. That
+ * way all functionality is actually backed by the underlying
+ * {@code Configuration} implementation, but because the associated proxy only
+ * implements the {@code ImmutableConfiguration} interface manipulations are
not
+ * possible.
+ * </p>
+ * <p>
+ * There is one caveat however: Some methods of the
+ * {@code ImmutableConfiguration} interface return an {@code Iterator} object.
+ * Using the iterator's {@code remove()} method it may be possible to remove
+ * keys from the underlying {@code Configuration} object. Therefore, in these
+ * cases a specialized {@code Iterator} is returned which does not support the
+ * remove operation.
+ * </p>
+ *
+ * @version $Id$
+ * @since 2.0
+ */
+class ImmutableConfigurationInvocationHandler implements InvocationHandler
+{
+ /** The underlying configuration object. */
+ private final Configuration wrappedConfiguration;
+
+ /**
+ * Creates a new instance of {@code
ImmutableConfigurationInvocationHandler}
+ * and initializes it with the wrapped configuration object.
+ *
+ * @param conf the wrapped {@code Configuration} (must not be <b>null</b>)
+ * @throws NullPointerException if the {@code Configuration} is <b>null</b>
+ */
+ public ImmutableConfigurationInvocationHandler(Configuration conf)
+ {
+ if (conf == null)
+ {
+ throw new NullPointerException(
+ "Wrapped configuration must not be null!");
+ }
+ wrappedConfiguration = conf;
+ }
+
+ /**
+ * {@inheritDoc} This implementation delegates to the wrapped configuration
+ * object. Result objects are wrapped if necessary.
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable
+ {
+ return handleResult(method.invoke(wrappedConfiguration, args));
+ }
+
+ /**
+ * Handles the result from the method invocation on the wrapped
+ * configuration. This implementation wraps result objects if necessary so
+ * that the underlying configuration cannot be manipulated.
+ *
+ * @param result the result object
+ * @return the processed result object
+ */
+ private static Object handleResult(Object result)
+ {
+ if (result instanceof Iterator)
+ {
+ return new ImmutableIterator((Iterator<?>) result);
+ }
+ return result;
+ }
+
+ /**
+ * A specialized {@code Iterator} implementation which delegates to an
+ * underlying iterator, but does not support the {@code remove()} method.
+ */
+ private static class ImmutableIterator implements Iterator<Object>
+ {
+ /** The underlying iterator. */
+ private final Iterator<?> wrappedIterator;
+
+ /**
+ * Creates a new instance of {@code ImmutableIterator} and sets the
+ * underlying iterator.
+ *
+ * @param it the underlying iterator
+ */
+ public ImmutableIterator(Iterator<?> it)
+ {
+ wrappedIterator = it;
+ }
+
+ /**
+ * {@inheritDoc} This implementation just delegates to the underlying
+ * iterator.
+ */
+ public boolean hasNext()
+ {
+ return wrappedIterator.hasNext();
+ }
+
+ /**
+ * {@inheritDoc} This implementation just delegates to the underlying
+ * iterator.
+ */
+ public Object next()
+ {
+ return wrappedIterator.next();
+ }
+
+ /**
+ * {@inheritDoc} This implementation just throws an exception: removing
+ * objects is not supported.
+ */
+ public void remove()
+ {
+ throw new UnsupportedOperationException(
+ "remove() operation not supported!");
+ }
+ }
+}
Propchange:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/ImmutableConfigurationInvocationHandler.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java?rev=1405890&view=auto
==============================================================================
---
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
(added)
+++
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
Mon Nov 5 17:30:20 2012
@@ -0,0 +1,179 @@
+/*
+ * 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.configuration;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.commons.configuration.builder.FileBasedBuilderParameters;
+import org.apache.commons.configuration.builder.FileBasedConfigurationBuilder;
+import org.junit.Test;
+
+/**
+ * A test class which tests functionality related to immutable configurations.
+ *
+ * @version $Id$
+ */
+public class TestImmutableConfiguration
+{
+ /** Constant for the name of a test properties file. */
+ private static final String TEST_FILE = "test.properties";
+
+ /**
+ * Tries to create an immutable configuration from a null object.
+ */
+ @Test(expected = NullPointerException.class)
+ public void testUnmodifiableConfigurationNull()
+ {
+ ConfigurationUtils.unmodifiableConfiguration(null);
+ }
+
+ /**
+ * Creates a test configuration object filled with properties.
+ *
+ * @return the test configuration
+ * @throws ConfigurationException if an error occurs
+ */
+ private static PropertiesConfiguration createTestConfig()
+ throws ConfigurationException
+ {
+ return new FileBasedConfigurationBuilder<PropertiesConfiguration>(
+ PropertiesConfiguration.class).configure(
+ new FileBasedBuilderParameters().setFile(ConfigurationAssert
+ .getTestFile(TEST_FILE))).getConfiguration();
+ }
+
+ /**
+ * Tests whether data can be accessed from an unmodifiable configuration.
+ */
+ @Test
+ public void testUnmodifiableConfigurationAccess()
+ throws ConfigurationException
+ {
+ Configuration confOrg = createTestConfig();
+ ImmutableConfiguration conf =
+ ConfigurationUtils.unmodifiableConfiguration(confOrg);
+ assertFalse("Empty", conf.isEmpty());
+ for (Iterator<String> it = confOrg.getKeys(); it.hasNext();)
+ {
+ String key = it.next();
+ assertTrue("Key not contained: " + key, conf.containsKey(key));
+ assertEquals("Wrong value for " + key, confOrg.getProperty(key),
+ conf.getProperty(key));
+ }
+ }
+
+ /**
+ * Tests different access methods for properties.
+ */
+ @Test
+ public void testUnmodifiableConfigurationOtherTypes()
+ throws ConfigurationException
+ {
+ ImmutableConfiguration conf =
+ ConfigurationUtils
+ .unmodifiableConfiguration(createTestConfig());
+ assertEquals("Wrong byte", (byte) 10, conf.getByte("test.byte"));
+ assertEquals("Wrong boolean", true, conf.getBoolean("test.boolean"));
+ assertEquals("Wrong double", 10.25, conf.getDouble("test.double"),
.05);
+ assertEquals("Wrong float", 20.25f, conf.getFloat("test.float"), .05);
+ assertEquals("Wrong int", 10, conf.getInt("test.integer"));
+ assertEquals("Wrong long", 1000000L, conf.getLong("test.long"));
+ assertEquals("Wrong short", (short) 1, conf.getShort("test.short"));
+ }
+
+ /**
+ * Obtains all keys from the given iteration.
+ *
+ * @param it the iterator
+ * @return a set with all keys
+ */
+ private static Set<String> fetchKeys(Iterator<String> it)
+ {
+ Set<String> keys = new HashSet<String>();
+ while (it.hasNext())
+ {
+ keys.add(it.next());
+ }
+ return keys;
+ }
+
+ /**
+ * Tests an iteration over the keys of the immutable configuration.
+ */
+ @Test
+ public void testUnmodifiableConfigurationIterate()
+ throws ConfigurationException
+ {
+ Configuration confOrg = createTestConfig();
+ ImmutableConfiguration conf =
+ ConfigurationUtils.unmodifiableConfiguration(confOrg);
+ assertEquals("Different keys", fetchKeys(confOrg.getKeys()),
+ fetchKeys(conf.getKeys()));
+ }
+
+ /**
+ * Tests that it is not possible to remove keys using the iterator.
+ */
+ @Test(expected = UnsupportedOperationException.class)
+ public void testUnmodifiableConfigurationIteratorRemove()
+ throws ConfigurationException
+ {
+ ImmutableConfiguration conf =
+ ConfigurationUtils
+ .unmodifiableConfiguration(createTestConfig());
+ Iterator<String> it = conf.getKeys();
+ it.next();
+ it.remove();
+ }
+
+ /**
+ * Tests whether an update of the original configuration is visible for the
+ * immutable view.
+ */
+ @Test
+ public void testUnmodifiableConfigurationLiveUpdate()
+ throws ConfigurationException
+ {
+ Configuration confOrg = createTestConfig();
+ ImmutableConfiguration conf =
+ ConfigurationUtils.unmodifiableConfiguration(confOrg);
+ final String key = "new.property";
+ String value = "new value";
+ confOrg.addProperty(key, value);
+ assertEquals("Value not set", value, conf.getString(key));
+ }
+
+ /**
+ * Tests that a cast to a mutable configuration is not possible.
+ */
+ @Test(expected = ClassCastException.class)
+ public void testUnmodifiableConfigurationCast()
+ throws ConfigurationException
+ {
+ ImmutableConfiguration conf =
+ ConfigurationUtils
+ .unmodifiableConfiguration(createTestConfig());
+ Configuration mutableConf = (Configuration) conf;
+ mutableConf.clear();
+ }
+}
Propchange:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
------------------------------------------------------------------------------
svn:keywords = Date Author Id Revision HeadURL
Propchange:
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/TestImmutableConfiguration.java
------------------------------------------------------------------------------
svn:mime-type = text/plain