Add support for cloning maps.

This commit is contained in:
Kristian S. Stangeland 2012-12-27 15:06:25 +01:00
parent f0e7b9aec7
commit d16e0bf1c6
2 changed files with 61 additions and 18 deletions

View File

@ -15,9 +15,15 @@
<arguments> <arguments>
</arguments> </arguments>
</buildCommand> </buildCommand>
<buildCommand>
<name>net.sourceforge.metrics.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec> </buildSpec>
<natures> <natures>
<nature>org.eclipse.m2e.core.maven2Nature</nature> <nature>org.eclipse.m2e.core.maven2Nature</nature>
<nature>org.eclipse.jdt.core.javanature</nature> <nature>org.eclipse.jdt.core.javanature</nature>
<nature>net.sourceforge.metrics.nature</nature>
</natures> </natures>
</projectDescription> </projectDescription>

View File

@ -20,6 +20,8 @@ package com.comphenix.protocol.reflect.cloning;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.util.Collection; import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
/** /**
* Attempts to clone collection and array classes. * Attempts to clone collection and array classes.
@ -43,7 +45,7 @@ public class CollectionCloner implements Cloner {
return false; return false;
Class<?> clazz = source.getClass(); Class<?> clazz = source.getClass();
return Collection.class.isAssignableFrom(clazz) || clazz.isArray(); return Collection.class.isAssignableFrom(clazz) || Map.class.isAssignableFrom(clazz) || clazz.isArray();
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -55,31 +57,32 @@ public class CollectionCloner implements Cloner {
Class<?> clazz = source.getClass(); Class<?> clazz = source.getClass();
if (source instanceof Collection) { if (source instanceof Collection) {
Collection<Object> copy = null; Collection<Object> copy = cloneConstructor(Collection.class, clazz, source);
// Not all collections implement "clone", but most *do* implement the "copy constructor" pattern
try {
Constructor<?> constructCopy = clazz.getConstructor(Collection.class);
copy = (Collection<Object>) constructCopy.newInstance(source);
} catch (NoSuchMethodException e) {
copy = (Collection<Object>) cloneObject(clazz, source);
} catch (Exception e) {
throw new RuntimeException("Cannot construct collection.", e);
}
// Next, clone each element in the collection // Next, clone each element in the collection
copy.clear(); copy.clear();
for (Object element : (Collection<Object>) source) { for (Object element : (Collection<Object>) source) {
if (defaultCloner.canClone(element)) copy.add(getClone(element, source));
copy.add(defaultCloner.clone(element)); }
else
throw new IllegalArgumentException("Cannot clone " + element + " in collection " + source); return copy;
} else if (source instanceof Map) {
Map<Object, Object> copy = cloneConstructor(Map.class, clazz, source);
// Next, clone each element in the collection
copy.clear();
for (Entry<Object, Object> element : ((Map<Object, Object>) source).entrySet()) {
Object key = getClone(element.getKey(), source);
Object value = getClone(element.getValue(), source);
copy.put(key, value);
} }
return copy; return copy;
// Second possibility
} else if (clazz.isArray()) { } else if (clazz.isArray()) {
// Get the length // Get the length
int lenght = Array.getLength(source); int lenght = Array.getLength(source);
@ -110,6 +113,19 @@ public class CollectionCloner implements Cloner {
throw new IllegalArgumentException(source + " is not an array nor a Collection."); throw new IllegalArgumentException(source + " is not an array nor a Collection.");
} }
/**
* Clone an element using the default cloner.
* @param element - the element to clone.
* @param container - where the element is stored.
* @return The cloned element.
*/
private Object getClone(Object element, Object container) {
if (defaultCloner.canClone(element))
return defaultCloner.clone(element);
else
throw new IllegalArgumentException("Cannot clone " + element + " in container " + container);
}
/** /**
* Clone a primitive or immutable array by calling its clone method. * Clone a primitive or immutable array by calling its clone method.
* @param component - the component type of the array. * @param component - the component type of the array.
@ -138,6 +154,27 @@ public class CollectionCloner implements Cloner {
return ((Object[]) source).clone(); return ((Object[]) source).clone();
} }
/**
* Clone an object by calling its clone constructor, or alternatively, a "clone" method.
* @param superclass - the superclass we expect in the clone constructor.
* @param clazz - the class of the object.
* @param source - the object itself.
* @return A cloned object.
*/
@SuppressWarnings("unchecked")
private <T> T cloneConstructor(Class<?> superclass, Class<?> clazz, Object source) {
// Not all collections or maps implement "clone", but most *do* implement the "copy constructor" pattern
try {
Constructor<?> constructCopy = clazz.getConstructor(Collection.class);
return (T) constructCopy.newInstance(source);
} catch (NoSuchMethodException e) {
return (T) cloneObject(clazz, source);
} catch (Exception e) {
throw new RuntimeException("Cannot construct collection.", e);
}
}
/** /**
* Clone an object by calling "clone" using reflection. * Clone an object by calling "clone" using reflection.
* @param clazz - the class type. * @param clazz - the class type.