add splitmap package whose original goal is to provide a more versatile TransformedMap implementation
git-svn-id: https://svn.apache.org/repos/asf/commons/proper/collections/branches/collections_jdk5_branch@751894 13f79535-47bb-0310-9956-ffa450edef68 Project: http://git-wip-us.apache.org/repos/asf/commons-collections/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-collections/commit/87ac9390 Tree: http://git-wip-us.apache.org/repos/asf/commons-collections/tree/87ac9390 Diff: http://git-wip-us.apache.org/repos/asf/commons-collections/diff/87ac9390 Branch: refs/heads/collections_jdk5_branch Commit: 87ac93908a02d75dab464f400b9d3887d3c0e04b Parents: 86b30a0 Author: Matthew Jason Benson <mben...@apache.org> Authored: Mon Mar 9 22:48:07 2009 +0000 Committer: Matthew Jason Benson <mben...@apache.org> Committed: Mon Mar 9 22:48:07 2009 +0000 ---------------------------------------------------------------------- .../AbstractIterableGetMapDecorator.java | 161 +++++++++++++ .../collections/splitmap/SplitMapUtils.java | 233 ++++++++++++++++++ .../collections/splitmap/TransformedMap.java | 208 ++++++++++++++++ .../commons/collections/splitmap/package.html | 39 +++ .../commons/collections/TestAllPackages.java | 3 +- .../commons/collections/splitmap/TestAll.java | 42 ++++ .../collections/splitmap/TestSplitMapUtils.java | 241 +++++++++++++++++++ .../splitmap/TestTransformedMap.java | 134 +++++++++++ 8 files changed, 1060 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/java/org/apache/commons/collections/splitmap/AbstractIterableGetMapDecorator.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/commons/collections/splitmap/AbstractIterableGetMapDecorator.java b/src/java/org/apache/commons/collections/splitmap/AbstractIterableGetMapDecorator.java new file mode 100644 index 0000000..d11e4c7 --- /dev/null +++ b/src/java/org/apache/commons/collections/splitmap/AbstractIterableGetMapDecorator.java @@ -0,0 +1,161 @@ +/* + * 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.collections.splitmap; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections.Get; +import org.apache.commons.collections.IterableGet; +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.map.EntrySetToMapIteratorAdapter; + +/** + * {@link IterableGet} that uses a {@link Map}<K, V> for the {@link Get}<K, V> + * implementation. + * + * @since Commons Collections 5 + * @TODO fix version + * @version $Revision$ $Date$ + * + * @author Matt Benson + */ +public class AbstractIterableGetMapDecorator<K, V> implements IterableGet<K, V> { + /** The map to decorate */ + protected transient Map<K, V> map; + + /** + * Create a new AbstractSplitMapDecorator. + * @param decorated the Map to decorate + */ + public AbstractIterableGetMapDecorator(Map<K, V> decorated) { + this.map = decorated; + } + + /** + * Gets the map being decorated. + * + * @return the decorated map + */ + protected Map<K, V> decorated() { + return map; + } + + /** + * {@inheritDoc} + */ + public void clear() { + decorated().clear(); + } + + /** + * {@inheritDoc} + */ + public boolean containsKey(Object key) { + return decorated().containsKey(key); + } + + /** + * {@inheritDoc} + */ + public boolean containsValue(Object value) { + return decorated().containsValue(value); + } + + /** + * {@inheritDoc} + */ + public Set<Map.Entry<K, V>> entrySet() { + return decorated().entrySet(); + } + + /** + * {@inheritDoc} + */ + public V get(Object key) { + return decorated().get(key); + } + + /** + * {@inheritDoc} + */ + public V remove(Object key) { + return decorated().remove(key); + } + + /** + * {@inheritDoc} + */ + public boolean isEmpty() { + return decorated().isEmpty(); + } + + /** + * {@inheritDoc} + */ + public Set<K> keySet() { + return decorated().keySet(); + } + + /** + * {@inheritDoc} + */ + public int size() { + return decorated().size(); + } + + /** + * {@inheritDoc} + */ + public Collection<V> values() { + return decorated().values(); + } + + /** + * Get a MapIterator over this Get. + * @return MapIterator<K, V> + */ + public MapIterator<K, V> mapIterator() { + return new EntrySetToMapIteratorAdapter<K, V>(entrySet()); + } + + /** + * {@inheritDoc} + */ + public boolean equals(Object object) { + if (object == this) { + return true; + } + return decorated().equals(object); + } + + /** + * {@inheritDoc} + */ + public int hashCode() { + return decorated().hashCode(); + } + + /** + * {@inheritDoc} + */ + public String toString() { + return decorated().toString(); + } + +} http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/java/org/apache/commons/collections/splitmap/SplitMapUtils.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/commons/collections/splitmap/SplitMapUtils.java b/src/java/org/apache/commons/collections/splitmap/SplitMapUtils.java new file mode 100644 index 0000000..f018dbf --- /dev/null +++ b/src/java/org/apache/commons/collections/splitmap/SplitMapUtils.java @@ -0,0 +1,233 @@ +package org.apache.commons.collections.splitmap; + +import java.util.Collection; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections.Get; +import org.apache.commons.collections.IterableGet; +import org.apache.commons.collections.IterableMap; +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.collections.Put; +import org.apache.commons.collections.Unmodifiable; +import org.apache.commons.collections.collection.UnmodifiableCollection; +import org.apache.commons.collections.iterators.UnmodifiableMapIterator; +import org.apache.commons.collections.map.EntrySetToMapIteratorAdapter; +import org.apache.commons.collections.map.UnmodifiableEntrySet; +import org.apache.commons.collections.set.UnmodifiableSet; + +/** + * Utilities for working with "split maps:" objects that implement {@link Put} + * and/or {@link Get} but not {@link Map}. + * + * @since Commons Collections 5 + * @TODO fix version + * @version $Revision$ $Date$ + * @see Get + * @see Put + * @author Matt Benson + */ +public class SplitMapUtils { + + /** + * <code>SplitMapUtils</code> should not normally be instantiated. + */ + public SplitMapUtils() { + } + + private static class WrappedGet<K, V> implements IterableMap<K, V>, Unmodifiable { + private Get<K, V> get; + + private WrappedGet(Get<K, V> get) { + this.get = get; + } + + public void clear() { + throw new UnsupportedOperationException(); + } + + public boolean containsKey(Object key) { + return get.containsKey(key); + } + + public boolean containsValue(Object value) { + return get.containsValue(value); + } + + public Set<java.util.Map.Entry<K, V>> entrySet() { + return UnmodifiableEntrySet.decorate(get.entrySet()); + } + + @Override + public boolean equals(Object arg0) { + if (arg0 == this) { + return true; + } + return arg0 instanceof WrappedGet && ((WrappedGet<?, ?>) arg0).get.equals(this.get); + } + + public V get(Object key) { + return get.get(key); + } + + @Override + public int hashCode() { + return ("WrappedGet".hashCode() << 4) | get.hashCode(); + } + + public boolean isEmpty() { + return get.isEmpty(); + } + + public Set<K> keySet() { + return UnmodifiableSet.decorate(get.keySet()); + } + + public V put(K key, V value) { + throw new UnsupportedOperationException(); + }; + + public void putAll(Map<? extends K, ? extends V> t) { + throw new UnsupportedOperationException(); + } + + public V remove(Object key) { + return get.remove(key); + } + + public int size() { + return get.size(); + } + + public Collection<V> values() { + return UnmodifiableCollection.decorate(get.values()); + } + + public MapIterator<K, V> mapIterator() { + MapIterator<K, V> it; + if (get instanceof IterableGet) { + it = ((IterableGet<K, V>) get).mapIterator(); + } else { + it = new EntrySetToMapIteratorAdapter<K, V>(get.entrySet()); + } + return UnmodifiableMapIterator.decorate(it); + } + } + + private static class WrappedPut<K, V> implements Map<K, V>, Put<K, V> { + private Put<K, V> put; + + private WrappedPut(Put<K, V> put) { + this.put = put; + } + + public void clear() { + put.clear(); + } + + public boolean containsKey(Object key) { + throw new UnsupportedOperationException(); + } + + public boolean containsValue(Object value) { + throw new UnsupportedOperationException(); + } + + public Set<java.util.Map.Entry<K, V>> entrySet() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + return obj instanceof WrappedPut && ((WrappedPut<?, ?>) obj).put.equals(this.put); + } + + public V get(Object key) { + throw new UnsupportedOperationException(); + } + + @Override + public int hashCode() { + return ("WrappedPut".hashCode() << 4) | put.hashCode(); + } + + public boolean isEmpty() { + throw new UnsupportedOperationException(); + } + + public Set<K> keySet() { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + public V put(K key, V value) { + return (V) put.put(key, value); + }; + + public void putAll(Map<? extends K, ? extends V> t) { + put.putAll(t); + } + + public V remove(Object key) { + throw new UnsupportedOperationException(); + } + + public int size() { + throw new UnsupportedOperationException(); + } + + public Collection<V> values() { + throw new UnsupportedOperationException(); + } + } + + /** + * Get the specified {@link Get} as an instance of {@link IterableMap}. + * If <code>get</code> implements {@link IterableMap} directly, no conversion will take place. + * If <code>get</code> implements {@link Map} but not {@link IterableMap} it will be decorated. + * Otherwise an {@link Unmodifiable} {@link IterableMap} will be returned. + * @param <K> + * @param <V> + * @param get to wrap, must not be null + * @return {@link IterableMap} + */ + @SuppressWarnings("unchecked") + public static <K, V> IterableMap<K, V> readableMap(Get<K, V> get) { + if (get == null) { + throw new IllegalArgumentException("Get must not be null"); + } + if (get instanceof Map) { + return get instanceof IterableMap ? ((IterableMap<K, V>) get) : MapUtils + .iterableMap((Map<K, V>) get); + } + return new WrappedGet<K, V>(get); + } + + /** + * Get the specified {@link Put} as an instanceof {@link Map}. + * If <code>put</code> implements {@link Map} directly, no conversion will take place. + * Otherwise a <em>write-only</em> {@link Map} will be returned. On such a {@link Map} + * it is recommended that the result of #put(K, V) be discarded as it likely will not + * match <code>V</code> at runtime. + * + * @param <K> + * @param <V> + * @param put to wrap, must not be null + * @return {@link Map} + */ + @SuppressWarnings("unchecked") + public static <K, V> Map<K, V> writableMap(Put<K, V> put) { + if (put == null) { + throw new IllegalArgumentException("Put must not be null"); + } + if (put instanceof Map) { + return (Map<K, V>) put; + } + return new WrappedPut<K, V>(put); + } + +} http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/java/org/apache/commons/collections/splitmap/TransformedMap.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/commons/collections/splitmap/TransformedMap.java b/src/java/org/apache/commons/collections/splitmap/TransformedMap.java new file mode 100644 index 0000000..4ef627f --- /dev/null +++ b/src/java/org/apache/commons/collections/splitmap/TransformedMap.java @@ -0,0 +1,208 @@ +/* + * 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.collections.splitmap; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Map; + +import org.apache.commons.collections.Put; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.map.LinkedMap; + +/** + * Decorates another <code>Map</code> to transform objects that are added. + * <p> + * The Map put methods and Map.Entry setValue method are affected by this class. + * Thus objects must be removed or searched for using their transformed form. + * For example, if the transformation converts Strings to Integers, you must use + * the Integer form to remove objects. + * <p> + * <strong>Note that TransformedMap is not synchronized and is not + * thread-safe.</strong> If you wish to use this map from multiple threads + * concurrently, you must use appropriate synchronization. The simplest approach + * is to wrap this map using {@link java.util.Collections#synchronizedMap(Map)}. + * This class may throw exceptions when accessed by concurrent threads without + * synchronization. + * + * @since Commons Collections 5 + * @TODO fix version + * @version $Revision$ $Date$ + * + * @author Stephen Colebourne + * @author Matt Benson + */ +public class TransformedMap<J, K, U, V> extends AbstractIterableGetMapDecorator<K, V> implements + Put<J, U>, Serializable { + + /** Serialization version */ + private static final long serialVersionUID = 5966875321133456994L; + + /** The decorated map */ + private Map<K, V> decorated; + /** The transformer to use for the key */ + private final Transformer<? super J, ? extends K> keyTransformer; + /** The transformer to use for the value */ + private final Transformer<? super U, ? extends V> valueTransformer; + + /** + * Factory method to create a transforming map. + * <p> + * If there are any elements already in the map being decorated, they are + * NOT transformed. + * + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, null + * means no transformation + * @param valueTransformer the transformer to use for value conversion, null + * means no transformation + * @throws IllegalArgumentException if map is null + */ + public static <J, K, U, V> TransformedMap<J, K, U, V> decorate(Map<K, V> map, + Transformer<? super J, ? extends K> keyTransformer, + Transformer<? super U, ? extends V> valueTransformer) { + return new TransformedMap<J, K, U, V>(map, keyTransformer, valueTransformer); + } + + //----------------------------------------------------------------------- + /** + * Constructor that wraps (not copies). + * <p> + * If there are any elements already in the collection being decorated, they + * are NOT transformed. + * + * @param map the map to decorate, must not be null + * @param keyTransformer the transformer to use for key conversion, null + * means no conversion + * @param valueTransformer the transformer to use for value conversion, null + * means no conversion + * @throws IllegalArgumentException if map is null + */ + protected TransformedMap(Map<K, V> map, Transformer<? super J, ? extends K> keyTransformer, + Transformer<? super U, ? extends V> valueTransformer) { + super(map); + if (keyTransformer == null) { + throw new IllegalArgumentException("keyTransformer cannot be null"); + } + this.keyTransformer = keyTransformer; + if (valueTransformer == null) { + throw new IllegalArgumentException("valueTransformer cannot be null"); + } + this.valueTransformer = valueTransformer; + } + + //----------------------------------------------------------------------- + /** + * Write the map out using a custom routine. + * + * @param out the output stream + * @throws IOException + */ + private void writeObject(ObjectOutputStream out) throws IOException { + out.defaultWriteObject(); + out.writeObject(decorated); + } + + /** + * Read the map in using a custom routine. + * + * @param in the input stream + * @throws IOException + * @throws ClassNotFoundException + * @since Commons Collections 3.1 + */ + @SuppressWarnings("unchecked") + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + in.defaultReadObject(); + decorated = (Map) in.readObject(); + } + + //----------------------------------------------------------------------- + /** + * Transforms a key. + * <p> + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @throws the transformed object + */ + protected K transformKey(J object) { + return keyTransformer.transform(object); + } + + /** + * Transforms a value. + * <p> + * The transformer itself may throw an exception if necessary. + * + * @param object the object to transform + * @throws the transformed object + */ + protected V transformValue(U object) { + return valueTransformer.transform(object); + } + + /** + * Transforms a map. + * <p> + * The transformer itself may throw an exception if necessary. + * + * @param map the map to transform + * @throws the transformed object + */ + @SuppressWarnings("unchecked") + protected Map<K, V> transformMap(Map<? extends J, ? extends U> map) { + if (map.isEmpty()) { + return (Map<K, V>) map; + } + Map<K, V> result = new LinkedMap<K, V>(map.size()); + + for (Map.Entry<? extends J, ? extends U> entry : map.entrySet()) { + result.put((K) transformKey(entry.getKey()), transformValue(entry.getValue())); + } + return result; + } + + /** + * Override to transform the value when using <code>setValue</code>. + * + * @param value the value to transform + * @return the transformed value + */ + protected V checkSetValue(U value) { + return valueTransformer.transform(value); + } + + //----------------------------------------------------------------------- + /** + * {@inheritDoc} + */ + public V put(J key, U value) { + return decorated().put(transformKey(key), transformValue(value)); + } + + /** + * {@inheritDoc} + */ + public void putAll(Map<? extends J, ? extends U> mapToCopy) { + decorated().putAll(transformMap(mapToCopy)); + } + + +} http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/java/org/apache/commons/collections/splitmap/package.html ---------------------------------------------------------------------- diff --git a/src/java/org/apache/commons/collections/splitmap/package.html b/src/java/org/apache/commons/collections/splitmap/package.html new file mode 100644 index 0000000..464ae43 --- /dev/null +++ b/src/java/org/apache/commons/collections/splitmap/package.html @@ -0,0 +1,39 @@ +<!-- $Id$ --> + <!-- + 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. + --> +<BODY> +<p>The "split map" concept is that of an object that implements +the {@link org.apache.commons.collections.Put Put} and +{@link org.apache.commons.collections.Get Get} interfaces, +with <i>differing</i> generic types. This is like a pre-generics +{@link java.util.Map Map} whose input key/value constraints are +different than its output key/value constraints. While it would +be possible to declare a "split map" with matching input/output +key/value constraints, this would be a {@link java.util.Map Map} +and would therefore make little sense (any Commons Collections +{@link java.util.Map Map} implementation will also implement +{@link org.apache.commons.collections.Put Put} and +{@link org.apache.commons.collections.Get Get} with matching +generic parameters). + +<p> +The following decorators are provided: +<ul> +<li>Transformed - transforms each element added +</ul> +</pre> +</BODY> http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/test/org/apache/commons/collections/TestAllPackages.java ---------------------------------------------------------------------- diff --git a/src/test/org/apache/commons/collections/TestAllPackages.java b/src/test/org/apache/commons/collections/TestAllPackages.java index 36a56f5..a67ca5a 100644 --- a/src/test/org/apache/commons/collections/TestAllPackages.java +++ b/src/test/org/apache/commons/collections/TestAllPackages.java @@ -41,7 +41,8 @@ import org.junit.runners.Suite.SuiteClasses; org.apache.commons.collections.keyvalue.TestAll.class, org.apache.commons.collections.list.TestAll.class, org.apache.commons.collections.map.TestAll.class, - org.apache.commons.collections.set.TestAll.class + org.apache.commons.collections.set.TestAll.class, + org.apache.commons.collections.splitmap.TestAll.class }) public class TestAllPackages { } http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/test/org/apache/commons/collections/splitmap/TestAll.java ---------------------------------------------------------------------- diff --git a/src/test/org/apache/commons/collections/splitmap/TestAll.java b/src/test/org/apache/commons/collections/splitmap/TestAll.java new file mode 100644 index 0000000..c3e54a4 --- /dev/null +++ b/src/test/org/apache/commons/collections/splitmap/TestAll.java @@ -0,0 +1,42 @@ +/* + * 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.collections.splitmap; + +import junit.framework.TestCase; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * Entry point for tests. + * + * @since Commons Collections 5 + * @TODO fix version + * @version $Revision$ $Date$ + * + * @author Stephen Colebourne + * @author Stephen Kestle + * @author Matt Benson + */ +@RunWith(Suite.class) +@SuiteClasses({ + TestSplitMapUtils.class, + TestTransformedMap.class +}) +public class TestAll extends TestCase { +} http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/test/org/apache/commons/collections/splitmap/TestSplitMapUtils.java ---------------------------------------------------------------------- diff --git a/src/test/org/apache/commons/collections/splitmap/TestSplitMapUtils.java b/src/test/org/apache/commons/collections/splitmap/TestSplitMapUtils.java new file mode 100644 index 0000000..c09cb5f --- /dev/null +++ b/src/test/org/apache/commons/collections/splitmap/TestSplitMapUtils.java @@ -0,0 +1,241 @@ +/* + * 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.collections.splitmap; + +import java.util.HashMap; +import java.util.Map; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.commons.collections.BulkTest; +import org.apache.commons.collections.IterableMap; +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.Put; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.Unmodifiable; +import org.apache.commons.collections.functors.NOPTransformer; +import org.apache.commons.collections.map.HashedMap; + +/** + * Tests for {@link TransformedMap} + * + * @since Commons Collections 5 + * @TODO fix version + * @version $Revision$ $Date$ + * + * @author Stephen Colebourne + * @author Matt Benson + */ +public class TestSplitMapUtils extends BulkTest { + private Map<String, Integer> backingMap; + private TransformedMap<String, String, String, Integer> transformedMap; + + private Transformer<String, Integer> stringToInt = new Transformer<String, Integer>() { + public Integer transform(String input) { + return Integer.valueOf(input); + } + }; + + public TestSplitMapUtils(String testName) { + super(testName); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + backingMap = new HashMap<String, Integer>(); + transformedMap = TransformedMap.decorate(backingMap, NOPTransformer.<String> getInstance(), + stringToInt); + for (int i = 0; i < 10; i++) { + transformedMap.put(String.valueOf(i), String.valueOf(i)); + } + } + + public static Test suite() { + return new TestSuite(TestSplitMapUtils.class); + } + + public static void main(String args[]) { + String[] testCaseName = { TestSplitMapUtils.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + // ----------------------------------------------------------------------- + + public void testReadableMap() { + final IterableMap<String, Integer> map = SplitMapUtils.readableMap(transformedMap); + + // basic + for (int i = 0; i < 10; i++) { + assertFalse(map.containsValue(String.valueOf(i))); + assertEquals(i, map.get(String.valueOf(i)).intValue()); + } + + // mapIterator + MapIterator<String, Integer> it = map.mapIterator(); + while (it.hasNext()) { + String k = it.next(); + assertEquals(k, it.getKey()); + assertEquals(Integer.valueOf(k), it.getValue()); + } + + // unmodifiable + assertTrue(map instanceof Unmodifiable); + + // check individual operations + int sz = map.size(); + + attemptPutOperation(new Runnable() { + public void run() { + map.clear(); + } + }); + + assertEquals(sz, map.size()); + + attemptPutOperation(new Runnable() { + public void run() { + map.put("foo", 100); + } + }); + + final HashMap<String, Integer> m = new HashMap<String, Integer>(); + m.put("foo", 100); + m.put("bar", 200); + m.put("baz", 300); + attemptPutOperation(new Runnable() { + public void run() { + map.putAll(m); + } + }); + + // equals, hashcode + IterableMap<String, Integer> other = SplitMapUtils.readableMap(transformedMap); + assertEquals(other, map); + assertEquals(other.hashCode(), map.hashCode()); + + // remove + for (int i = 0; i < 10; i++) { + assertEquals(i, map.remove(String.valueOf(i)).intValue()); + assertEquals(--sz, map.size()); + } + assertTrue(map.isEmpty()); + assertSame(map, SplitMapUtils.readableMap(map)); + } + + public void testAlreadyReadableMap() { + HashedMap<String, Integer> hashedMap = new HashedMap<String, Integer>(); + assertSame(hashedMap, SplitMapUtils.readableMap(hashedMap)); + } + + @SuppressWarnings("unchecked") + public void testWritableMap() { + final Map<String, String> map = SplitMapUtils.writableMap(transformedMap); + attemptGetOperation(new Runnable() { + public void run() { + map.get(null); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.entrySet(); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.keySet(); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.values(); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.size(); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.isEmpty(); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.containsKey(null); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.containsValue(null); + } + }); + attemptGetOperation(new Runnable() { + public void run() { + map.remove(null); + } + }); + + // equals, hashcode + Map<String, String> other = SplitMapUtils.writableMap(transformedMap); + assertEquals(other, map); + assertEquals(other.hashCode(), map.hashCode()); + + // put + int sz = backingMap.size(); + assertFalse(backingMap.containsKey("foo")); + map.put("new", "66"); + assertEquals(++sz, backingMap.size()); + + // putall + Map<String, String> more = new HashMap<String, String>(); + more.put("foo", "77"); + more.put("bar", "88"); + more.put("baz", "99"); + map.putAll(more); + assertEquals(sz + more.size(), backingMap.size()); + + // clear + map.clear(); + assertTrue(backingMap.isEmpty()); + assertSame(map, SplitMapUtils.writableMap((Put<String, String>) map)); + } + + public void testAlreadyWritableMap() { + HashedMap<String, String> hashedMap = new HashedMap<String, String>(); + assertSame(hashedMap, SplitMapUtils.writableMap(hashedMap)); + } + + private void attemptGetOperation(Runnable r) { + attemptMapOperation("Put exposed as writable Map must not allow Get operations", r); + } + + private void attemptPutOperation(Runnable r) { + attemptMapOperation("Get exposed as writable Map must not allow Put operations", r); + } + + private void attemptMapOperation(String s, Runnable r) { + try { + r.run(); + fail(s); + } catch (UnsupportedOperationException e) { + } + } + +} http://git-wip-us.apache.org/repos/asf/commons-collections/blob/87ac9390/src/test/org/apache/commons/collections/splitmap/TestTransformedMap.java ---------------------------------------------------------------------- diff --git a/src/test/org/apache/commons/collections/splitmap/TestTransformedMap.java b/src/test/org/apache/commons/collections/splitmap/TestTransformedMap.java new file mode 100644 index 0000000..b77fa4c --- /dev/null +++ b/src/test/org/apache/commons/collections/splitmap/TestTransformedMap.java @@ -0,0 +1,134 @@ +/* + * 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.collections.splitmap; + +import java.math.BigInteger; +import java.util.HashMap; + +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.commons.collections.BulkTest; +import org.apache.commons.collections.MapIterator; +import org.apache.commons.collections.Transformer; +import org.apache.commons.collections.functors.NOPTransformer; + +/** + * Tests for {@link TransformedMap} + * + * @since Commons Collections 5 + * @TODO fix version, add Serialization tests + * @version $Revision$ $Date$ + * + * @author Stephen Colebourne + * @author Matt Benson + */ +public class TestTransformedMap extends BulkTest { + + private Transformer<Integer, String> intToString = new Transformer<Integer, String>() { + public String transform(Integer input) { + return String.valueOf(input); + }; + }; + + private Transformer<Object, Class<?>> objectToClass = new Transformer<Object, Class<?>>() { + public java.lang.Class<?> transform(Object input) { + return input == null ? null : input.getClass(); + } + }; + + private Transformer<String, Integer> stringToInt = new Transformer<String, Integer>() { + public Integer transform(String input) { + return Integer.valueOf(input); + } + }; + + public TestTransformedMap(String testName) { + super(testName); + } + + public static Test suite() { + return new TestSuite(TestTransformedMap.class); + } + + public static void main(String args[]) { + String[] testCaseName = { TestTransformedMap.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + // ----------------------------------------------------------------------- + @SuppressWarnings("unchecked") + public void testTransformedMap() { + TransformedMap<Integer, String, Object, Class<?>> map = TransformedMap.decorate( + new HashMap<String, Class<?>>(), intToString, objectToClass); + + Integer[] k = new Integer[] { 0, 1, 2, 3, 4, 5, 6 }; + Object[] v = new Object[] { "", new Object(), new HashMap(), 0, BigInteger.TEN, null, + new Object[0] }; + + assertEquals(0, map.size()); + for (int i = 0; i < k.length; i++) { + map.put(k[i], v[i]); + assertEquals(i + 1, map.size()); + assertTrue(map.containsKey(intToString.transform(k[i]))); + assertFalse(map.containsKey(k[i])); + assertTrue(map.containsValue(objectToClass.transform(v[i]))); + assertTrue(objectToClass.transform(v[i]) != v[i] ^ map.containsValue(v[i])); + assertEquals(objectToClass.transform(v[i]), map.get(intToString.transform(k[i]))); + } + + int sz = map.size(); + assertEquals(null, map.remove(k[0])); + assertEquals(sz, map.size()); + assertEquals(objectToClass.transform(v[0]), map.remove(intToString.transform(k[0]))); + assertEquals(--sz, map.size()); + + TransformedMap<String, String, String, Integer> map2 = TransformedMap.decorate( + new HashMap<String, Integer>(), NOPTransformer.<String> getInstance(), stringToInt); + assertEquals(0, map2.size()); + for (int i = 0; i < 6; i++) { + map2.put(String.valueOf(i), String.valueOf(i)); + assertEquals(i + 1, map2.size()); + assertTrue(map2.containsValue(i)); + assertFalse(map2.containsValue(String.valueOf(i))); + assertTrue(map2.containsKey(String.valueOf(i))); + assertEquals(i, map2.get(String.valueOf(i)).intValue()); + } + + int sz2 = map2.size(); + assertEquals(Integer.valueOf(0), map2.remove("0")); + assertEquals(--sz2, map2.size()); + } + + // ----------------------------------------------------------------------- + + public void testMapIterator() { + TransformedMap<String, String, String, Integer> map = TransformedMap.decorate( + new HashMap<String, Integer>(), NOPTransformer.<String> getInstance(), stringToInt); + assertEquals(0, map.size()); + for (int i = 0; i < 6; i++) { + map.put(String.valueOf(i), String.valueOf(i)); + } + + for (MapIterator<String, Integer> it = map.mapIterator(); it.hasNext();) { + String k = it.next(); + assertEquals(k, it.getKey()); + assertEquals(map.get(k), it.getValue()); + } + } + +}