You are viewing a plain text version of this content. The canonical link for it is here.
Posted to user@commons.apache.org by "B. K. Oxley (binkley)" <bi...@alumni.rice.edu> on 2004/10/09 20:36:43 UTC

"safe" maps, sets and lists

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) <bi...@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


Re: [collections] "safe" maps, sets and lists

Posted by Stephen Colebourne <sc...@btopenworld.com>.
It sounds like you should look at

LazyMap/LazyList, for populating missing map/list entries
Predicated*, for validating input
Typed*, for validating against a type
The functor subpackage for standard factory/predicate implementations

Stephen

----- Original Message -----
From: "B. K. Oxley (binkley)" <bi...@alumni.rice.edu>
> 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) <bi...@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
>


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