/******************************************************************************
* JBoss, a division of Red Hat *
* Copyright 2006, Red Hat Middleware, LLC, and individual *
* contributors as indicated by the @authors tag. See the *
* copyright.txt in the distribution for a full listing of *
* individual contributors. *
* *
* This 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 software 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 software; if not, write to the Free *
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA *
* 02110-1301 USA, or see the FSF site: http://www.fsf.org. *
******************************************************************************/
package org.jboss.portal.common.util;
import org.jboss.portal.common.NotYetImplemented;
import java.util.Map;
import java.util.Set;
import java.util.Collection;
import java.util.Iterator;
import java.util.HashMap;
/**
* @author <a href="mailto:julien@jboss.org">Julien Viet</a>
* @version $Revision: 1.1 $
*/
public class TypedMap<EK, EV, IK, IV> implements Map<EK, EV>
{
public abstract static class Converter<E, I>
{
/**
* Unwraps the key to the the internal key that will be stored in the map. This method calls the
* <code>assertKeyValidity(Object key)</code> and returns the same key. It can be overriden to provide a customized
* key that will be used instead of the external key.
*
* @param external the key to unwrap
* @return the unwrapped key
* @throws ClassCastException if the class of the specified key prevents it from being stored in this map
* @throws IllegalArgumentException if some aspect of this key prevents it from being stored in this map
*/
protected abstract I getInternal(E external) throws IllegalArgumentException, ClassCastException;
/**
* Wrap the internal key into its external representation, by default returns the same key. It can be overriden to
* provide a customized key that will be used instead of the internal key.
*/
protected abstract E getExternal(I internal);
public I unwrap(E external) throws IllegalArgumentException, ClassCastException, NullPointerException
{
if (external == null)
{
throw new NullPointerException("No null key accepted");
}
//
I internal = getInternal(external);
//
if (internal == null)
{
throw new IllegalArgumentException("The provided key " + external + " was converted to a null key");
}
//
return internal;
}
public E wrap(I internal) throws IllegalStateException
{
if (internal == null)
{
throw new IllegalStateException("Got an internal null key");
}
//
E external = getExternal(internal);
//
if (external == null)
{
throw new IllegalStateException("Converted an internal key to a null key " + internal);
}
//
return external;
}
public boolean safeEquals(I left, I right)
{
// Check the internal value, it should not be null
return !(left == null || right == null) && equals(left, right);
}
/**
* Compare internal values, the passed argument are never null.
*
* @param left the left value
* @param right the right value
* @return true if the values are equals
*/
protected abstract boolean equals(I left, I right);
}
/** The map accessor. */
private final MapAccessor<IK, IV> accessor;
/** The key converter. */
private final Converter<EK, IK> keyConverter;
/** The value converter. */
private final Converter<EV, IV> valueConverter;
public TypedMap(MapAccessor<IK, IV> accessor, Converter<EK, IK> keyConverter, Converter<EV, IV> valueConverter)
{
if (accessor == null)
{
throw new IllegalArgumentException();
}
if (keyConverter == null)
{
throw new IllegalArgumentException();
}
if (valueConverter == null)
{
throw new IllegalArgumentException();
}
this.accessor = accessor;
this.keyConverter = keyConverter;
this.valueConverter = valueConverter;
}
public TypedMap(Map<IK, IV> delegate, Converter<EK, IK> keyConv, Converter<EV, IV> valueConv)
{
this(new SimpleMapAccessor<IK, IV>(delegate), keyConv, valueConv);
}
public Converter<EK, IK> getKeyConverter()
{
return keyConverter;
}
public Converter<EV, IV> getValueConverter()
{
return valueConverter;
}
public final int size()
{
return accessor.getMap(false).size();
}
public final void clear()
{
if (!isEmpty())
{
accessor.getMap(true).clear();
}
}
public final boolean isEmpty()
{
return accessor.getMap(false).isEmpty();
}
public final boolean containsKey(Object key)
{
EK ek = (EK)key;
IK ik = keyConverter.unwrap(ek);
return accessor.getMap(false).containsKey(ik);
}
public final Set<EK> keySet()
{
return new KeySet();
}
public EV put(EK ek, EV ev)
{
IK ik = keyConverter.unwrap(ek);
IV iv = valueConverter.unwrap(ev);
iv = accessor.getMap(true).put(ik, iv);
return iv == null ? null : valueConverter.wrap(iv);
}
public final EV get(Object key)
{
EK ek = (EK)key;
IK ik = keyConverter.unwrap(ek);
IV iv = accessor.getMap(false).get(ik);
return iv == null ? null : valueConverter.wrap(iv);
}
public final EV remove(Object key)
{
EK ek = (EK)key;
IK ik = keyConverter.unwrap(ek);
IV iv = null;
if (!isEmpty())
{
iv = accessor.getMap(true).remove(ik);
}
return iv == null ? null : valueConverter.wrap(iv);
}
public final boolean containsValue(Object value)
{
EV ev = (EV)value;
IV iv = valueConverter.unwrap(ev);
return accessor.getMap(false).containsValue(iv);
}
public final Set<Entry<EK, EV>> entrySet()
{
return new TypedEntrySet();
}
public void putAll(Map<? extends EK, ? extends EV> em)
{
Map<IK, IV> im = convert(em);
accessor.getMap(true).putAll(im);
}
public Collection<EV> values()
{
return new ValueCollection();
}
/** Compare to parameters objects. */
public boolean equals(Object o)
{
if (o == this)
{
return true;
}
if (o instanceof Map)
{
Map<EK, EV> that = (Map<EK,EV>)o;
Map<IK, IV> delegate = this.accessor.getMap(false);
// Must have same sizes
if (that.size() != delegate.size())
{
return false;
}
//
for (Entry<EK, EV> thatEntry : that.entrySet())
{
EK thatKey = thatEntry.getKey();
EV thatValue = thatEntry.getValue();
//
try
{
// Unwrap key, mostly for checking its type is correct
keyConverter.unwrap(thatKey);
// Unwrap value
IV iv = valueConverter.unwrap(thatValue);
// Get the internal value
IV internalValue = delegate.get(thatKey);
// Perform value comparison
if (!valueConverter.safeEquals(internalValue, iv))
{
return false;
}
}
catch (IllegalArgumentException e)
{
return false;
}
catch (ClassCastException e)
{
return false;
}
catch (NullPointerException e)
{
return false;
}
}
//
return true;
}
//
return false;
}
public String toString()
{
return accessor.getMap(false).toString();
}
/**
* Validates and unwraps the map.
*
* @param t
* @return
* @throws IllegalArgumentException
* @throws NullPointerException
* @throws ClassCastException
*/
protected final Map<IK, IV> convert(Map<? extends EK, ? extends EV> t) throws IllegalArgumentException, NullPointerException, ClassCastException
{
if (t == null)
{
throw new NullPointerException("No null map can be accepted");
}
Map<IK, IV> u = new HashMap<IK, IV>(t.size());
for (Entry<? extends EK, ? extends EV> entry : t.entrySet())
{
IK ik = keyConverter.unwrap(entry.getKey());
IV iv = valueConverter.unwrap(entry.getValue());
u.put(ik, iv);
}
return u;
}
/**
* Replace the content with the new map which is validated before replacement.
*
* @param map the replacement map
* @throws ClassCastException
* @throws NullPointerException
* @throws IllegalArgumentException
*/
public void replace(Map<EK, EV> map) throws ClassCastException, NullPointerException, IllegalArgumentException
{
if (!map.isEmpty())
{
Map<IK, IV> tmp = convert(map);
//
Map<IK, IV> delegate = accessor.getMap(true);
delegate.clear();
delegate.putAll(tmp);
}
}
/**
* Validate the content.
*
* @throws ClassCastException
* @throws NullPointerException
* @throws IllegalArgumentException
*/
public void validate() throws ClassCastException, NullPointerException, IllegalArgumentException
{
for (Entry<IK, IV> entry : accessor.getMap(false).entrySet())
{
keyConverter.wrap(entry.getKey());
valueConverter.wrap(entry.getValue());
}
}
public class KeySet implements Set<EK>
{
/** . */
private final Set<IK> delegate;
public KeySet()
{
this.delegate = TypedMap.this.accessor.getMap(false).keySet();
}
public int size()
{
return delegate.size();
}
public void clear()
{
if (!isEmpty())
{
delegate.clear();
}
}
public boolean isEmpty()
{
return delegate.isEmpty();
}
public boolean contains(Object o)
{
EK ek;
try
{
ek = (EK)o;
}
catch (ClassCastException e)
{
return false;
}
try
{
IK ik = keyConverter.getInternal(ek);
return TypedMap.this.accessor.getMap(false).containsKey(ik);
}
catch (IllegalArgumentException e)
{
return false;
}
catch (ClassCastException e)
{
return false;
}
catch (NullPointerException e)
{
return false;
}
}
public Object[] toArray()
{
throw new NotYetImplemented("TypedEntrySet.toArray()");
}
public boolean add(EK ek)
{
throw new NotYetImplemented("TypedEntrySet.add(Object o)");
}
public boolean remove(Object o)
{
throw new NotYetImplemented("TypedEntrySet.remove(Object o)");
}
public boolean containsAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.addAll(Collection c)");
}
public boolean addAll(Collection<? extends EK> eks)
{
throw new NotYetImplemented("TypedEntrySet.containsAll(Collection c)");
}
public boolean removeAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.removeAll(Collection c)");
}
public boolean retainAll(Collection<?> c)
{
if (c == null)
{
throw new NullPointerException();
}
//
boolean changed = false;
for (Iterator i = iterator(); i.hasNext();)
{
Object key = i.next();
if (!c.contains(key))
{
i.remove();
changed = true;
}
}
return changed;
}
public Iterator<EK> iterator()
{
return new KeyIterator();
}
public <EK> EK[] toArray(EK a[])
{
throw new NotYetImplemented("TypedEntrySet.toArray(Object a[])");
}
public class KeyIterator implements Iterator<EK>
{
/** . */
private final Iterator<IK> delegate;
public KeyIterator()
{
this.delegate = KeySet.this.delegate.iterator();
}
public void remove()
{
delegate.remove();
}
public boolean hasNext()
{
return delegate.hasNext();
}
public EK next()
{
IK ik = delegate.next();
return keyConverter.wrap(ik);
}
}
}
public class ValueCollection implements Collection<EV>
{
/** . */
private final Collection<IV> delegate;
public ValueCollection()
{
this.delegate = TypedMap.this.accessor.getMap(false).values();
}
public int size()
{
return delegate.size();
}
public void clear()
{
delegate.clear();
}
public boolean isEmpty()
{
return delegate.isEmpty();
}
public Object[] toArray()
{
throw new NotYetImplemented("TypedEntrySet.toArray()");
}
public boolean add(EV ev)
{
throw new NotYetImplemented("TypedEntrySet.add(Object o)");
}
public boolean contains(Object o)
{
throw new NotYetImplemented("TypedEntrySet.contains(Object o)");
}
public boolean remove(Object o)
{
throw new NotYetImplemented("TypedEntrySet.remove(Object o)");
}
public boolean addAll(Collection<? extends EV> evs)
{
throw new NotYetImplemented("TypedEntrySet.addAll(Collection c)");
}
public boolean containsAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.containsAll(Collection c)");
}
public boolean removeAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.removeAll(Collection c)");
}
public boolean retainAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.retainAll(Collection c)");
}
public <T> T[] toArray(T[] ts)
{
throw new NotYetImplemented("TypedEntrySet.toArray(Object a[])");
}
public Iterator<EV> iterator()
{
return new ValueIterator();
}
public class ValueIterator implements Iterator<EV>
{
/** . */
private final Iterator<IV> delegate;
public ValueIterator()
{
this.delegate = ValueCollection.this.delegate.iterator();
}
public void remove()
{
delegate.remove();
}
public boolean hasNext()
{
return delegate.hasNext();
}
public EV next()
{
IV iv = delegate.next();
return valueConverter.wrap(iv);
}
}
}
public class TypedEntrySet implements Set<Entry<EK, EV>>
{
/** . */
private final Set<Entry<IK, IV>> delegate;
public TypedEntrySet()
{
this.delegate = TypedMap.this.accessor.getMap(false).entrySet();
}
public int size()
{
return delegate.size();
}
public void clear()
{
if (!isEmpty())
{
delegate.clear();
}
}
public boolean isEmpty()
{
return delegate.isEmpty();
}
public Object[] toArray()
{
throw new NotYetImplemented("TypedEntrySet.toArray()");
}
public boolean add(Entry<EK, EV> ekevEntry)
{
throw new NotYetImplemented("TypedEntrySet.add(Object o)");
}
public boolean contains(Object o)
{
throw new NotYetImplemented("TypedEntrySet.contains(Object o)");
}
public boolean remove(Object o)
{
throw new NotYetImplemented("TypedEntrySet.remove(Object o)");
}
public boolean addAll(Collection<? extends Entry<EK, EV>> entries)
{
throw new NotYetImplemented("TypedEntrySet.addAll(Collection c)");
}
public boolean containsAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.containsAll(Collection c)");
}
public boolean removeAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.removeAll(Collection c)");
}
public boolean retainAll(Collection<?> objects)
{
throw new NotYetImplemented("TypedEntrySet.retainAll(Collection c)");
}
public <T> T[] toArray(T[] ts)
{
throw new NotYetImplemented("TypedEntrySet.toArray(Object a[])");
}
public Iterator<Entry<EK, EV>> iterator()
{
return new TypedEntryIterator();
}
public class TypedEntryIterator implements Iterator<Entry<EK, EV>>
{
/** . */
private final Iterator<Entry<IK, IV>> delegate;
public TypedEntryIterator()
{
this.delegate = TypedEntrySet.this.delegate.iterator();
}
public void remove()
{
delegate.remove();
}
public boolean hasNext()
{
return delegate.hasNext();
}
public Entry<EK, EV> next()
{
Entry<IK, IV> entry = delegate.next();
return new TypedEntry(entry);
}
}
public class TypedEntry implements Entry<EK, EV>
{
/** . */
private final Entry<IK, IV> delegate;
public TypedEntry(Entry<IK, IV> delegate)
{
this.delegate = delegate;
}
public int hashCode()
{
return delegate.hashCode();
}
public boolean equals(Object obj)
{
return delegate.equals(obj);
}
public String toString()
{
return delegate.toString();
}
public EK getKey()
{
IK ik = delegate.getKey();
return keyConverter.wrap(ik);
}
public EV getValue()
{
IV iv = delegate.getValue();
return valueConverter.wrap(iv);
}
public EV setValue(Object value)
{
EV ev = (EV)value;
IV iv = valueConverter.unwrap(ev);
iv = delegate.setValue(iv);
return valueConverter.wrap(iv);
}
}
}
}