Add AbstractInputCheckedMapDecorator

git-svn-id: https://svn.apache.org/repos/asf/jakarta/commons/proper/collections/trunk@131699 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Stephen Colebourne 2004-05-03 21:48:49 +00:00
parent 07a0f5c777
commit 486a169f5e
4 changed files with 334 additions and 217 deletions

View File

@ -19,7 +19,7 @@
<p>
This release focusses on bug fixes and minor enhancements.
No interface changes, or deprecations have occurred.
No deprecations have occurred.
<hr />
@ -32,6 +32,7 @@ No interface changes, or deprecations have occurred.
<li>MapBackedSet - Set created by decorating a map</li>
<li>ReferenceIdentityMap - Similar to ReferenceMap, but matching keys and values by identity [26503]</li>
<li>AbstractReferenceMap - New base class for reference maps [26503]</li>
<li>AbstractInputCheckedMapDecorator - New base class for map decorators that validate or alter input</li>
</ul>
<center><h3>ENHANCEMENTS</h3></center>

View File

@ -0,0 +1,270 @@
/*
* Copyright 2004 The Apache Software Foundation
*
* Licensed 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.map;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
import org.apache.commons.collections.set.AbstractSetDecorator;
/**
* An abstract base class that simplifies the task of creating map decorators.
* <p>
* The Map API is very difficult to decorate correctly, and involves implementing
* lots of different classes. This class exists to provide a simpler API.
* <p>
* Special hook methods are provided that are called when objects are added to
* the map. By overriding these methods, the input can be validated or manipulated.
* In addition to the main map methods, the entrySet is also affected, which is
* the hardest part of writing map implementations.
*
* @since Commons Collections 3.1
* @version $Revision: 1.1 $ $Date: 2004/05/03 21:48:49 $
*
* @author Stephen Colebourne
*/
public class AbstractInputCheckedMapDecorator
extends AbstractMapDecorator {
/**
* Constructor only used in deserialization, do not use otherwise.
*/
protected AbstractInputCheckedMapDecorator() {
super();
}
/**
* Constructor that wraps (not copies).
*
* @param map the map to decorate, must not be null
* @throws IllegalArgumentException if map is null
*/
protected AbstractInputCheckedMapDecorator(Map map) {
super(map);
}
//-----------------------------------------------------------------------
/**
* Hook method called when a key is being added to the map using
* <code>put</code> or <code>putAll</code>.
* <p>
* An implementation may validate the key and throw an exception
* or it may transform the key into another object.
* The key may already exist in the map.
* <p>
* This implementation returns the input key.
*
* @param key the key to check
* @throws UnsupportedOperationException if the map may not be changed by put/putAll
* @throws IllegalArgumentException if the specified key is invalid
* @throws ClassCastException if the class of the specified key is invalid
* @throws NullPointerException if the specified key is null and nulls are invalid
*/
protected Object checkPutKey(Object key) {
return key;
}
/**
* Hook method called when a new value is being added to the map using
* <code>put</code> or <code>putAll</code>.
* <p>
* An implementation may validate the value and throw an exception
* or it may transform the value into another object.
* <p>
* This implementation returns the input value.
*
* @param value the value to check
* @throws UnsupportedOperationException if the map may not be changed by put/putAll
* @throws IllegalArgumentException if the specified value is invalid
* @throws ClassCastException if the class of the specified value is invalid
* @throws NullPointerException if the specified value is null and nulls are invalid
*/
protected Object checkPutValue(Object value) {
return value;
}
/**
* Hook method called when a value is being set using <code>setValue</code>.
* <p>
* An implementation may validate the value and throw an exception
* or it may transform the value into another object.
* <p>
* This implementation returns the input value.
*
* @param value the value to check
* @throws UnsupportedOperationException if the map may not be changed by setValue
* @throws IllegalArgumentException if the specified value is invalid
* @throws ClassCastException if the class of the specified value is invalid
* @throws NullPointerException if the specified value is null and nulls are invalid
*/
protected Object checkSetValue(Object value) {
return value;
}
/**
* Hook method called to determine if <code>checkSetValue</code> has any effect.
* <p>
* An implementation should return false if the <code>checkSetValue</code> method
* has no effect as this optimises the implementation.
* <p>
* This implementation returns <code>true</code>.
*
* @param value the value to check
*/
protected boolean isSetValueChecking() {
return true;
}
/**
* Checks each element in the specified map, creating a new map.
* <p>
* This method is used by <code>putAll</code> to check all the elements
* before adding them to the map.
* <p>
* This implementation builds a <code>LinkedMap</code> to preserve the order
* of the input map.
*
* @param map the map to transform
* @throws the transformed object
*/
protected Map checkMap(Map map) {
Map result = new LinkedMap(map.size());
for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
result.put(checkPutKey(entry.getKey()), checkPutValue(entry.getValue()));
}
return result;
}
//-----------------------------------------------------------------------
public Object put(Object key, Object value) {
key = checkPutKey(key);
value = checkPutValue(value);
return getMap().put(key, value);
}
public void putAll(Map mapToCopy) {
if (mapToCopy.size() == 0) {
return;
} else {
mapToCopy = checkMap(mapToCopy);
getMap().putAll(mapToCopy);
}
}
public Set entrySet() {
if (isSetValueChecking()) {
return new EntrySet(map.entrySet(), this);
} else {
return map.entrySet();
}
}
//-----------------------------------------------------------------------
/**
* Implementation of an entry set that checks additions via setValue.
*/
static class EntrySet extends AbstractSetDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
super(set);
this.parent = parent;
}
public Iterator iterator() {
return new EntrySetIterator(collection.iterator(), parent);
}
public Object[] toArray() {
Object[] array = collection.toArray();
for (int i = 0; i < array.length; i++) {
array[i] = new MapEntry((Map.Entry) array[i], parent);
}
return array;
}
public Object[] toArray(Object array[]) {
Object[] result = array;
if (array.length > 0) {
// we must create a new array to handle multi-threaded situations
// where another thread could access data before we decorate it
result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0);
}
result = collection.toArray(result);
for (int i = 0; i < result.length; i++) {
result[i] = new MapEntry((Map.Entry) result[i], parent);
}
// check to see if result should be returned straight
if (result.length > array.length) {
return result;
}
// copy back into input array to fulfil the method contract
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = null;
}
return array;
}
}
/**
* Implementation of an entry set iterator that checks additions via setValue.
*/
static class EntrySetIterator extends AbstractIteratorDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new MapEntry(entry, parent);
}
}
/**
* Implementation of a map entry that checks additions via setValue.
*/
static class MapEntry extends AbstractMapEntryDecorator {
/** The parent map */
private final AbstractInputCheckedMapDecorator parent;
protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}
public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}
}

View File

@ -19,15 +19,10 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.collection.AbstractCollectionDecorator;
import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
/**
* Decorates another <code>Map</code> to validate that additions
@ -39,13 +34,13 @@ import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
* This class is Serializable from Commons Collections 3.1.
*
* @since Commons Collections 3.0
* @version $Revision: 1.9 $ $Date: 2004/04/09 10:36:01 $
* @version $Revision: 1.10 $ $Date: 2004/05/03 21:48:49 $
*
* @author Stephen Colebourne
* @author Paul Jack
*/
public class PredicatedMap
extends AbstractMapDecorator
extends AbstractInputCheckedMapDecorator
implements Serializable {
/** Serialization version */
@ -94,6 +89,13 @@ public class PredicatedMap
}
}
/**
* Validates a key value pair.
*
* @param key the key to validate
* @param value the value to validate
* @throws IllegalArgumentException if invalid
*/
protected void validate(Object key, Object value) {
if (keyPredicate != null && keyPredicate.evaluate(key) == false) {
throw new IllegalArgumentException("Cannot add key - Predicate rejected it");
@ -129,6 +131,33 @@ public class PredicatedMap
map = (Map) in.readObject();
}
//-----------------------------------------------------------------------
// The validate method exists for backwards compatability - in an ideal
// world, it wouldn't and the superclass methods checkPutKey/checkPutValue
// would be overridden instead
/**
* Override to validate an object set into the map via <code>setValue</code>.
*
* @param value the value to validate
* @throws IllegalArgumentException if invalid
*/
protected Object checkSetValue(Object value) {
if (valuePredicate.evaluate(value) == false) {
throw new IllegalArgumentException("Cannot set value - Predicate rejected it");
}
return value;
}
/**
* Override to only return true when there is a value transformer.
*
* @return true if a value predicate is in use
*/
protected boolean isSetValueChecking() {
return (valuePredicate != null);
}
//-----------------------------------------------------------------------
public Object put(Object key, Object value) {
validate(key, value);
@ -146,104 +175,4 @@ public class PredicatedMap
map.putAll(mapToCopy);
}
public Set entrySet() {
if (valuePredicate == null) {
return map.entrySet();
}
return new PredicatedMapEntrySet(map.entrySet(), valuePredicate);
}
//-----------------------------------------------------------------------
/**
* Implementation of an entry set that checks (predicates) additions.
*/
static class PredicatedMapEntrySet extends AbstractCollectionDecorator implements Set {
/** The predicate to use */
private final Predicate valuePredicate;
protected PredicatedMapEntrySet(Set set, Predicate valuePred) {
super(set);
this.valuePredicate = valuePred;
}
public Iterator iterator() {
return new PredicatedMapEntrySetIterator(collection.iterator(), valuePredicate);
}
public Object[] toArray() {
Object[] array = collection.toArray();
for (int i = 0; i < array.length; i++) {
array[i] = new PredicatedMapEntry((Map.Entry) array[i], valuePredicate);
}
return array;
}
public Object[] toArray(Object array[]) {
Object[] result = array;
if (array.length > 0) {
// we must create a new array to handle multi-threaded situations
// where another thread could access data before we decorate it
result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0);
}
result = collection.toArray(result);
for (int i = 0; i < result.length; i++) {
result[i] = new PredicatedMapEntry((Map.Entry) result[i], valuePredicate);
}
// check to see if result should be returned straight
if (result.length > array.length) {
return result;
}
// copy back into input array to fulfil the method contract
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = null;
}
return array;
}
}
/**
* Implementation of an entry set iterator.
*/
static class PredicatedMapEntrySetIterator extends AbstractIteratorDecorator {
/** The predicate to use */
private final Predicate valuePredicate;
protected PredicatedMapEntrySetIterator(Iterator iterator, Predicate valuePredicate) {
super(iterator);
this.valuePredicate = valuePredicate;
}
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new PredicatedMapEntry(entry, valuePredicate);
}
}
/**
* Implementation of a map entry that checks (predicates) additions.
*/
static class PredicatedMapEntry extends AbstractMapEntryDecorator {
/** The predicate to use */
private final Predicate predicate;
protected PredicatedMapEntry(Map.Entry entry, Predicate valuePredicate) {
super(entry);
this.predicate = valuePredicate;
}
public Object setValue(Object obj) {
if (predicate != null && predicate.evaluate(obj) == false) {
throw new IllegalArgumentException("Cannot set value - Predicate rejected it");
}
return entry.setValue(obj);
}
}
}

View File

@ -19,16 +19,10 @@ import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.collection.AbstractCollectionDecorator;
import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
/**
* Decorates another <code>Map</code> to transform objects that are added.
@ -41,12 +35,12 @@ import org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator;
* This class is Serializable from Commons Collections 3.1.
*
* @since Commons Collections 3.0
* @version $Revision: 1.8 $ $Date: 2004/04/09 10:36:01 $
* @version $Revision: 1.9 $ $Date: 2004/05/03 21:48:49 $
*
* @author Stephen Colebourne
*/
public class TransformedMap
extends AbstractMapDecorator
extends AbstractInputCheckedMapDecorator
implements Serializable {
/** Serialization version */
@ -117,6 +111,10 @@ public class TransformedMap
}
//-----------------------------------------------------------------------
// The transformKey/transformValue/transformMap methods exist for backwards
// compatability - in an ideal world, they wouldn't and the superclass
// methods checkPutKey/checkPutValue would be overridden instead
/**
* Transforms a key.
* <p>
@ -156,7 +154,7 @@ public class TransformedMap
* @throws the transformed object
*/
protected Map transformMap(Map map) {
Map result = new HashMap(map.size());
Map result = new LinkedMap(map.size());
for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
Map.Entry entry = (Map.Entry) it.next();
result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
@ -164,6 +162,25 @@ public class TransformedMap
return result;
}
/**
* Override to transform the value when using <code>setValue</code>.
*
* @param value the value to transform
* @return the transformed value
*/
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
/**
* Override to only return true when there is a value transformer.
*
* @return true if a value transformer is in use
*/
protected boolean isSetValueChecking() {
return (valueTransformer != null);
}
//-----------------------------------------------------------------------
public Object put(Object key, Object value) {
key = transformKey(key);
@ -176,104 +193,4 @@ public class TransformedMap
getMap().putAll(mapToCopy);
}
public Set entrySet() {
if (valueTransformer == null) {
return map.entrySet();
}
return new TransformedMapEntrySet(map.entrySet(), valueTransformer);
}
//-----------------------------------------------------------------------
/**
* Implementation of an entry set that uses a transforming map entry.
*/
static class TransformedMapEntrySet extends AbstractCollectionDecorator implements Set {
/** The transformer to use */
private final Transformer valueTransformer;
protected TransformedMapEntrySet(Set set, Transformer valueTransformer) {
super(set);
this.valueTransformer = valueTransformer;
}
public Iterator iterator() {
return new TransformedMapEntrySetIterator(collection.iterator(), valueTransformer);
}
public Object[] toArray() {
Object[] array = collection.toArray();
for (int i = 0; i < array.length; i++) {
array[i] = new TransformedMapEntry((Map.Entry) array[i], valueTransformer);
}
return array;
}
public Object[] toArray(Object array[]) {
Object[] result = array;
if (array.length > 0) {
// we must create a new array to handle multi-threaded situations
// where another thread could access data before we decorate it
result = (Object[]) Array.newInstance(array.getClass().getComponentType(), 0);
}
result = collection.toArray(result);
for (int i = 0; i < result.length; i++) {
result[i] = new TransformedMapEntry((Map.Entry) result[i], valueTransformer);
}
// check to see if result should be returned straight
if (result.length > array.length) {
return result;
}
// copy back into input array to fulfil the method contract
System.arraycopy(result, 0, array, 0, result.length);
if (array.length > result.length) {
array[result.length] = null;
}
return array;
}
}
/**
* Implementation of an entry set iterator.
*/
static class TransformedMapEntrySetIterator extends AbstractIteratorDecorator {
/** The transformer to use */
private final Transformer valueTransformer;
protected TransformedMapEntrySetIterator(Iterator iterator, Transformer valueTransformer) {
super(iterator);
this.valueTransformer = valueTransformer;
}
public Object next() {
Map.Entry entry = (Map.Entry) iterator.next();
return new TransformedMapEntry(entry, valueTransformer);
}
}
/**
* Implementation of a map entry that transforms additions.
*/
static class TransformedMapEntry extends AbstractMapEntryDecorator {
/** The transformer to use */
private final Transformer valueTransformer;
protected TransformedMapEntry(Map.Entry entry, Transformer valueTransformer) {
super(entry);
this.valueTransformer = valueTransformer;
}
public Object setValue(Object object) {
if (valueTransformer != null) {
object = valueTransformer.transform(object);
}
return entry.setValue(object);
}
}
}