package org.limewire.util;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Provides a means to ensure <code>Maps</code>, <code>Collections</code>,
* <code>Sets</code> and <code>Lists</code> contain objects of a specific type
* only. <code>GenericsUtils</code> and its static methods are intended for
* checking the type-safety of de-serialized objects.
*/
public class GenericsUtils {
/** The mode {@link GenericsUtils} should use when scanning through objects. */
public enum ScanMode {
/** Throw an exception on bad objects. */
EXCEPTION,
/** Remove the bad objects in place. */
REMOVE,
/** Create a new copy without the bad objects (if necessary). */
NEW_COPY_REMOVED
}
private GenericsUtils() {
}
/**
* Scans the object 'o' to make sure that it is a map,
* all keys are type K, all values are type V, and all
* values within V are of type T.
* If o is not a map, a ClassCastException is thrown.
*
* The given ScanMode is used while scanning. If the ScanMode
* is NEW_COPY_REMOVED, this throws an exception.
*
* @param o
* @param remove
* @return
*/
@SuppressWarnings({ "cast", "unchecked" })
public static <K, V extends List, T> Map<K, List<T>> scanForMapOfList(Object o, Class<K> k, Class<V> v, Class<T> t, ScanMode mode) {
Map map = (Map)scanForMapOfCollection(o, k, v, t, mode);
return (Map<K, List<T>>)map;
}
/**
* Scans the object 'o' to make sure that it is a map,
* all keys are type K, all values are type V, and all
* values within V are of type T.
* If o is not a map, a ClassCastException is thrown.
*
* The given ScanMode is used while scanning. If the ScanMode
* is NEW_COPY_REMOVED, this throws an exception.
*
* @param o
* @param remove
* @return
*/
@SuppressWarnings("unchecked")
public static <K, V extends Collection, T> Map<K, Collection<T>> scanForMapOfCollection(Object o, Class<K> k, Class<V> v, Class<T> t, ScanMode mode) {
if(mode == ScanMode.NEW_COPY_REMOVED)
throw new IllegalArgumentException(ScanMode.NEW_COPY_REMOVED + " is not supported");
if(o instanceof Map) {
Map map = (Map)o;
for(Iterator i = map.entrySet().iterator(); i.hasNext(); ) {
Map.Entry entry = (Map.Entry)i.next();
Object key = entry.getKey();
Object value = entry.getValue();
if(key == null || value == null ||
!k.isAssignableFrom(key.getClass()) ||
!v.isAssignableFrom(value.getClass())) {
switch(mode) {
case EXCEPTION:
StringBuilder errorReport = new StringBuilder();
if (key == null)
errorReport.append("key is null ");
else if (!k.isAssignableFrom(key.getClass()))
errorReport.append("key class not assignable ")
.append(key.getClass()).append(" to ").append(k);
if (value == null)
errorReport.append("value is null for key ").append(key);
else if (!v.isAssignableFrom(value.getClass()))
errorReport.append("value class not assignable ")
.append(value.getClass()).append(" to ").append(v);
throw new ClassCastException(errorReport.toString());
case REMOVE:
i.remove();
break;
}
} else { // value is valid, validate the entries within it.
scanForCollection(value, t, mode);
}
}
return map;
} else {
throw new ClassCastException();
}
}
/**
* Utility method for calling scanForMap(o, k, v, mode, null). If
* NEW_COPY_REMOVED is the ScanMode, this will throw a NullPointerException.
*/
public static <K, V> Map<K, V> scanForMap(Object o, Class<K> k, Class<V> v, ScanMode mode) {
if (mode == ScanMode.NEW_COPY_REMOVED)
throw new IllegalArgumentException(
"must use scanForMap(Object, Class, Class, ScanMode, Class");
else
return scanForMap(o, k, v, mode, null);
}
/**
* Scans the object 'o' to make sure that it is a map, all keys are type K
* and all values are type V. If o is not a map, a ClassCastException is
* thrown.
* <p>
* The given ScanMode is used while scanning. If the ScanMode is
* NEW_COPY_REMOVED, then a Class must be given to create the copy with bad
* elements removed, if necessary.
*/
@SuppressWarnings("unchecked")
public static <K, V> Map<K, V> scanForMap(Object o, Class<K> k, Class<V> v, ScanMode mode,
Class<? extends Map<K, V>> createFromThis) {
if (o instanceof Map) {
Map map = (Map) o;
for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
Object key = entry.getKey();
Object value = entry.getValue();
if (key == null || value == null || !k.isAssignableFrom(key.getClass())
|| !v.isAssignableFrom(value.getClass())) {
switch (mode) {
case EXCEPTION:
StringBuilder errorReport = new StringBuilder();
if (key == null)
errorReport.append("key is null ");
else if (!k.isAssignableFrom(key.getClass()))
errorReport.append("key class not assignable ").append(key.getClass())
.append(" to ").append(k);
if (value == null)
errorReport.append("value is null for key ").append(key);
else if (!v.isAssignableFrom(value.getClass()))
errorReport.append("value class not assignable ").append(
value.getClass()).append(" to ").append(v);
throw new ClassCastException(errorReport.toString());
case REMOVE:
i.remove();
break;
case NEW_COPY_REMOVED:
return copyAndFilterMap(newMap(createFromThis), map, k, v);
}
}
}
return map;
} else {
throw new ClassCastException();
}
}
/**
* Utility method for calling scanForCollection(o, v, mode, null). If
* NEW_COPY_REMOVED is the ScanMode, this will throw a NullPointerException.
*/
public static <V> Collection<V> scanForCollection(Object o, Class<V> v, ScanMode mode) {
if (mode == ScanMode.NEW_COPY_REMOVED)
throw new IllegalArgumentException(
"must use scanForCollection(Object, Class, ScanMode, Class");
else
return scanForCollection(o, v, mode, null);
}
/**
* Scans the object 'o' to make sure that it is a Collection, and all values
* are type V. If o is not a Collection, a ClassCastException is thrown.
* <p>
* The given ScanMode is used while scanning. If the ScanMode is
* NEW_COPY_REMOVED, then a Class must be given to create the copy with bad
* elements removed, if necessary.
*/
@SuppressWarnings("unchecked")
public static <V> Collection<V> scanForCollection(Object o, Class<V> v, ScanMode mode,
Class<? extends Collection<V>> createFromThis) {
if (o instanceof Collection) {
Collection c = (Collection) o;
for (Iterator i = c.iterator(); i.hasNext();) {
Object value = i.next();
if (value == null || !v.isAssignableFrom(value.getClass())) {
switch (mode) {
case EXCEPTION:
throw new ClassCastException("wanted an instanceof: " + v + ", but was: ["
+ value + "] of type: "
+ (value == null ? "null" : value.getClass().getName()));
case REMOVE:
i.remove();
break;
case NEW_COPY_REMOVED:
return copyAndFilterCollection(newCollection(createFromThis), c, v);
}
}
}
return c;
} else {
throw new ClassCastException();
}
}
/**
* Utility method for calling scanForSet(o, v, mode, null). If
* NEW_COPY_REMOVED is the ScanMode, this will throw a NullPointerException.
*/
public static <V> Set<V> scanForSet(Object o, Class<V> v, ScanMode mode) {
if (mode == ScanMode.NEW_COPY_REMOVED)
throw new IllegalArgumentException("must use scanForSet(Object, Class, ScanMode, Class");
else
return scanForSet(o, v, mode, null);
}
/**
* Scans the object 'o' to make sure that it is a Set, and all values are
* type V. If o is not a Set, a ClassCastException is thrown.
* <p>
* The given ScanMode is used while scanning. If the ScanMode is
* NEW_COPY_REMOVED, then a Class must be given to create the copy with bad
* elements removed, if necessary.
*/
public static <V> Set<V> scanForSet(Object o, Class<V> v, ScanMode mode,
Class<? extends Set<V>> createFromThis) {
if (o instanceof Set) {
return (Set<V>) scanForCollection(o, v, mode, createFromThis);
} else {
throw new ClassCastException();
}
}
/**
* Utility method for calling scanForList(o, v, mode, null). If
* NEW_COPY_REMOVED is the ScanMode, this will throw a NullPointerException.
*/
public static <V> List<V> scanForList(Object o, Class<V> v, ScanMode mode) {
if (mode == ScanMode.NEW_COPY_REMOVED)
throw new IllegalArgumentException(
"must use scanForList(Object, Class, ScanMode, Class");
else
return scanForList(o, v, mode, null);
}
/**
* Scans the object 'o' to make sure that it is a List, and all values are
* type V. If o is not a List, a ClassCastException is thrown.
* <p>
* The given ScanMode is used while scanning. If the ScanMode is
* NEW_COPY_REMOVED, then a Class must be given to create the copy with bad
* elements removed, if necessary.
*/
public static <V> List<V> scanForList(Object o, Class<V> v, ScanMode mode,
Class<? extends List<V>> createFromThis) {
if (o instanceof List) {
return (List<V>) scanForCollection(o, v, mode, createFromThis);
} else {
throw new ClassCastException();
}
}
/** Returns a copy of the original, with all unassignable items removed. */
@SuppressWarnings("unchecked")
private static <K, V> Map<K, V> copyAndFilterMap(Map<K, V> copy, Map original, Class<K> k,
Class<V> v) {
for (Iterator i = original.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry) i.next();
Object key = entry.getKey();
Object value = entry.getValue();
if (key != null && value != null && k.isAssignableFrom(key.getClass())
&& v.isAssignableFrom(value.getClass())) {
copy.put((K) key, (V) value);
}
}
return copy;
}
/** Returns a copy of the original, with all unassignable items removed. */
@SuppressWarnings("unchecked")
private static <V> Collection<V> copyAndFilterCollection(Collection<V> copy,
Collection original, Class<V> v) {
for (Iterator i = original.iterator(); i.hasNext();) {
Object value = i.next();
if (value != null && v.isAssignableFrom(value.getClass())) {
copy.add((V) value);
}
}
return copy;
}
/** Constructs a new class from this. */
private static <V, T extends Collection<V>> T newCollection(Class<? extends T> creator) {
try {
return creator.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
/** Constructs a new class from this. */
private static <K, V, T extends Map<K, V>> T newMap(Class<? extends T> creator) {
try {
return creator.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}