commons-user mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Chris Lambrou <m...@chrislambrou.com>
Subject Re: "safe" maps, sets and lists
Date Sun, 10 Oct 2004 22:47:22 GMT
Binkley,

commons-collections contains predicated decorators for the standard 
collection types, i.e. Map, Set, List and Collection. You can use them 
to decorate an existing collection instance to perform the validation 
that you're describing.  For example, to construct a Map which doesn't 
permit null keys or values, use the following:

Map safeMap = PredicatedSet.decorate(new HashMap(), 
NotNullPredicate.INSTANCE, NotNullPredicate.INSTANCE);

The commons-collections package does support your requirement, but does 
so in a more general manner.

1. It uses predicates to separately validate the keys and values, so 
that any arbitrary validation rule can be defined, rather than just 
specifically validating against the type of the keys and values.

2. It is applied as a decorator to an existing collection instance so 
that you are free to chose the underlying collection implementation that 
is best suited to any given usage situation., so that you don't have to 
create and maintain 'safe' versions of each different collection 
implementation.

If this generality is too much, then to satisfy your requirement of not 
having to maintain your own libraries to support this, you can write a 
simple utility class to wrap the commons-collections classes in a more 
convenient manner. For example:

    public static Map safeHashMap(Class keyType, Class valueType)
    {
        return PredicatedMap.decorate(new HashMap(), new 
InstanceofPredicate(keyType), new InstanceofPredicate(valueType));
    }

Note that the predicated collection decorators may not perform the 
validation on all of the methods that your classes do. For example, the 
validation will take place on methods such as put(key, value) and 
putAll(map) methods, which will modify the map, but validation probably 
doesn't take place on methods such as containsKey(key) method.  It does, 
however, violate the Map contract to throw an IllegalArgumentException 
on such a method, rather than simply returning false. It's odd behaviour 
for a collection instance to throw an Exception just when some code 
tries to simply query it, rather than modify it, though I guess it can 
be useful behaviour if you want to try and trap errors as early as 
possible in the development process.

All that said, I would be interested in seeing more of your Java 5.0 
code. There has been recent talk of converting the commons-collections 
package to make use of Java 5.0 language features, resulting in a 
commons-colloctions15 package, a generified port of 
commons-collections.  I'm working on the port at the moment, but as it's 
at an early stage, it would be nice to get any sort of feedback from 
end-users.

Chris


>I'm looking through the commons collection for "safer" version of the standard 
>map, set and list implementations and not finding quite what I want.  To be 
>more specific, I'd like maps that disallow keys, and maps, sets and lists 
>which disallow null values.  Further, I'd like to enforce type safety on keys 
>and values (and work the JDKs before 5.0).
>
>To that end I wrote my own versions (trivial, to be sure, for HashMap, 
>TreeMap, HashSet, TreeSet, ArrayList and LinkedList) but hate reinventing the 
>wheel.  So I have two questions:
>
>1. Are there such things already and I just missed them.
>2. If there are not, could I donate my implementations to commons collections?
>
>My second question is quite selfish: I don't want to maintain separate code 
>bases every place I work (client or permanent) for such basic classes, and 
>would love to have them in a more public, copyright/license-safe place such 
>as Jakarta.  (If the LGPL is an issue, I have no problem with relicensing 
>with the Apache license or whatever other open source license is 
>appropriate.)
>
>A sample class is appended below to illustrate what I mean exactly.  I have 
>full unit tests for all and maven builds.  I don't want to be accused of 
>writing crapware.   :-) 
>
>
>Cheers,
>--binkley
>
>
>Note -- this is for JDK 5, but JDK 1.4 versions are virtually identical, just 
>without the generics or annotations.
>
>/*
> * Util - JDK collections extensions
> * Copyright (C) 2004 B. K. Oxley (binkley) <binkley@alumni.rice.edu>
> *
> * This library is free software; you can redistribute it and/or
> * modify it under the terms of the GNU Lesser General Public License
> * as published by the Free Software Foundation; either version 2.1 of
> * the License, or (at your option) any later version.
> *
> * This library is distributed in the hope that it will be useful, but
> * WITHOUT ANY WARRANTY; without even the implied warranty of
> * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> * Lesser General Public License for more details.
> *
> * You should have received a copy of the GNU Lesser General Public
> * License along with this library; if not, write to the Free Software
> * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
> * USA
> */
>
>package util;
>
>import java.util.HashMap;
>import java.util.Map;
>
>/**
> * <p>Provides a {@link Map} which forbids <code>null</code> keys and
values 
>and
> * requires that keys and values be of certain types.  This handles the large
> * majority of uses which map definite types (in contrast to generic
> * <code>Object</code> types) and which expect definite key and value 
>instances
> * (in contrast to <code>null</code>).</p>
> *
> * <p>All operations which take a key or a value, therefore, throw
> * <code>NullPointerException</code> if the argument is <code>null</code>,
or
> * throw <code>ClassCastException</code> if the argument is the wrong 
>type.</p>
> *
> * <p>Of note is one particular common idiom:</p> <pre>
> * if (null == map.get(key)) {
> *     doSomethingForMissingKey(key);
> * }</pre>
> *
> * <p>which will create a <code>null</code> value for <var>key</var>
and 
>return
> * it. <code>SafeHashMap</code> requires the more correct:</p> <pre>
> * if (!map.contains(key)) {
> *     doSomethingForMissingKey(key);
> * }</pre>
> *
> * <p>Typical use:</p> <pre>
> * final SafeHashMap propertyMap = new SafeHashMap(String.class,
> * Value.class);
> * propertyMap.insert("testActual", new Value());
> * propertyMap.insert("bar", new SubSomething()); // subclasses ok
> * propertyMap.insert(null, new Value()); // NullPointerException
> * propertyMap.insert("testActual", new Integer(3)); //
> * ClassCastException</pre>
> *
> * <p>Limitations with JDK 5 generics prevent the more obvious definition:</p>
> * <pre>
> * public SafeHashMap(final Class<K> keyClass, final Class<V> valueClass)
{
> *     // ...
> * }</pre> <p>As this makes impossible the straight-forward:</p> <pre>
> * public SafeHashMap() {
> *     this(Object.class, Object.class);
> * }</pre>
> *
> * @author <a href="binkley@alumni.rice.edu">B. K. Oxley (binkley)</a>
> * @version 1.0
> */
>public class SafeHashMap <K, V> extends HashMap<K, V> {
>    private final Class keyClass;
>    private final Class valueClass;
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> which accepts any class of

>key
>     * and value.
>     *
>     * @see SafeHashMap#SafeHashMap(Class, Class)
>     */
>    public SafeHashMap() {
>        this(Object.class, Object.class);
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> for a given 
><var>keyClass</var>
>     * and <var>valueClass</var>.
>     *
>     * @param keyClass the superclass of map keys
>     * @param valueClass the superclass of map values
>     *
>     * @see HashMap#HashMap()
>     */
>    public SafeHashMap(final Class keyClass, final Class valueClass) {
>        if (null == keyClass) throw new NullPointerException();
>        if (null == valueClass) throw new NullPointerException();
>
>        this.keyClass = keyClass;
>        this.valueClass = valueClass;
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> which accepts any class of

>key
>     * and value with the given <var>initialCapacity</var>.
>     *
>     * @param initialCapacity the initial capacity
>     *
>     * @see SafeHashMap#SafeHashMap(Class, Class, int)
>     */
>    public SafeHashMap(final int initialCapacity) {
>        this(Object.class, Object.class, initialCapacity);
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> for a given 
><var>keyClass</var>
>     * and <var>valueClass</var> with the given <var>initialCapacity</var>.
>     *
>     * @param keyClass the superclass of map keys
>     * @param valueClass the superclass of map values
>     * @param initialCapacity the initial capacity
>     *
>     * @see HashMap#HashMap(int)
>     */
>    public SafeHashMap(final Class keyClass, final Class valueClass,
>            final int initialCapacity) {
>        super(initialCapacity);
>
>        if (null == keyClass) throw new NullPointerException();
>        if (null == valueClass) throw new NullPointerException();
>
>        this.keyClass = keyClass;
>        this.valueClass = valueClass;
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> which accepts any class of

>key
>     * and value with the given <var>initialCapacity</var> and
>     * <var>loadFactor</var>.
>     *
>     * @param initialCapacity the initial capacity
>     * @param loadFactor the load factor
>     *
>     * @see SafeHashMap#SafeHashMap(Class, Class, int, float)
>     */
>    public SafeHashMap(final int initialCapacity, final float loadFactor) {
>        this(Object.class, Object.class, initialCapacity, loadFactor);
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> for a given 
><var>keyClass</var>
>     * and <var>valueClass</var> with the given <var>initialCapacity</var>
and
>     * <var>loadFactor</var>.
>     *
>     * @param keyClass the superclass of map keys
>     * @param valueClass the superclass of map values
>     * @param initialCapacity the initial capacity
>     * @param loadFactor the load factor
>     *
>     * @see HashMap#HashMap(int, float)
>     */
>    public SafeHashMap(final Class keyClass, final Class valueClass,
>            final int initialCapacity, final float loadFactor) {
>        super(initialCapacity, loadFactor);
>
>        if (null == keyClass) throw new NullPointerException();
>        if (null == valueClass) throw new NullPointerException();
>
>        this.keyClass = keyClass;
>        this.valueClass = valueClass;
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> which accepts any class of

>key
>     * and value with the given <var>map</var>.
>     *
>     * @param map the map
>     *
>     * @see SafeHashMap#SafeHashMap(Class, Class, Map)
>     */
>    public SafeHashMap(final Map<? extends K, ? extends V> map) {
>        this(Object.class, Object.class, map);
>    }
>
>    /**
>     * Constructs a new <code>SafeHashMap</code> for a given 
><var>keyClass</var>
>     * and <var>valueClass</var> with the given <var>map</var>.
>     *
>     * @param keyClass the superclass of map keys
>     * @param valueClass the superclass of map values
>     * @param map the map
>     *
>     * @see HashMap#HashMap(Map)
>     */
>    public SafeHashMap(final Class keyClass, final Class valueClass,
>            final Map<? extends K, ? extends V> map) {
>        super(map);
>
>        if (null == keyClass) throw new NullPointerException();
>        if (null == valueClass) throw new NullPointerException();
>
>        this.keyClass = keyClass;
>        this.valueClass = valueClass;
>    }
>
>    /**
>     * {@inheritDoc}
>     */
>    @Override public boolean containsKey(final Object key) {
>        validateKey(key);
>
>        return super.containsKey(key);
>    }
>
>    /**
>     * {@inheritDoc}
>     */
>    @Override public boolean containsValue(final Object value) {
>        validateValue(value);
>
>        return super.containsValue(value);
>    }
>
>    /**
>     * {@inheritDoc}
>     *
>     * @throws IllegalArgumentException if <var>key</var> is missing
>     */
>    @Override public V get(final Object key) {
>        validateKey(key);
>
>        if (!containsKey(key)) throw new IllegalArgumentException();
>
>        return super.get(key);
>    }
>
>    /**
>     * {@inheritDoc}
>     *
>     * @throws IllegalArgumentException if <var>key</var> is duplicate
>     */
>    @Override public V put(final K key, final V value) {
>        validateKey(key);
>        validateValue(value);
>
>        if (containsKey(key)) throw new IllegalArgumentException();
>
>        return super.put(key, value);
>    }
>
>    /**
>     * Sets the entry having the given <var>key</var> with <var>value</var>.
>     *
>     * @param key the map key
>     * @param value the map value
>     *
>     * @return the previous value
>     *
>     * @throws IllegalArgumentException if <var>key</var> is missing
>     */
>    public V set(final K key, final V value) {
>        validateKey(key);
>        validateValue(value);
>
>        if (!containsKey(key)) throw new IllegalArgumentException();
>
>        return super.put(key, value);
>    }
>
>    /**
>     * {@inheritDoc}
>     *
>     * @throws IllegalArgumentException if <var>key</var> is missing
>     */
>    @Override public V remove(final Object key) {
>        validateKey(key);
>
>        if (!containsKey(key)) throw new IllegalArgumentException();
>
>        return super.remove(key);
>    }
>
>    private void validateKey(final Object key) {
>        if (null == key) throw new NullPointerException();
>        if (!keyClass.isAssignableFrom(key.getClass()))
>            throw new ClassCastException();
>    }
>
>    private void validateValue(final Object v) {
>        if (null == v) throw new NullPointerException();
>        if (!valueClass.isAssignableFrom(v.getClass()))
>            throw new ClassCastException();
>    }
>}
>  
>


---------------------------------------------------------------------
To unsubscribe, e-mail: commons-user-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-user-help@jakarta.apache.org


Mime
View raw message