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) <[EMAIL PROTECTED]>
*
* 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 [EMAIL PROTECTED] 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="[EMAIL PROTECTED]">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;
   }

   /**
    * [EMAIL PROTECTED]
    */
   @Override public boolean containsKey(final Object key) {
       validateKey(key);

       return super.containsKey(key);
   }

   /**
    * [EMAIL PROTECTED]
    */
   @Override public boolean containsValue(final Object value) {
       validateValue(value);

       return super.containsValue(value);
   }

   /**
    * [EMAIL PROTECTED]
    *
    * @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);
   }

   /**
    * [EMAIL PROTECTED]
    *
    * @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);
   }

   /**
    * [EMAIL PROTECTED]
    *
    * @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: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]



Reply via email to