package org.ovirt.engine.api.restapi.types;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import org.ovirt.engine.api.model.BaseResource;
import org.ovirt.engine.api.restapi.utils.GuidUtils;
public class MappingTestHelper {
private static final String SET_ROOT = "set";
private static final String GET_ROOT = "get";
/**
* Populate a JAXB model type by recursively walking element tree and
* setting leaf nodes to randomized values.
*
* @param clz
* the model type
* @return a populated instance
*/
public static Object populate(Class<?> clz) throws Exception {
List<Class<?>> seen = getSetMethodTypes(clz);
return populate(instantiate(clz), clz, seen, 1);
}
private static List<Class<?>> getSetMethodTypes(Class<?> clz) {
List<Class<?>> types = new ArrayList<>();
for (Method method : clz.getMethods()) {
if (isSetter(method)) {
Class<?> type = method.getParameterTypes()[0];
if (BaseResource.class.isAssignableFrom(type) && !types.contains(type)) {
types.add(type);
}
}
}
return types;
}
/**
* Populate a JAXB model type by recursively walking element tree and
* setting leaf nodes to randomized values.
*
* @param model
* the model instance
* @param clz
* the model type
* @param seen
* model types seen so far
* @return a populated instance
*/
public static Object populate(Object model, Class<?> clz, List<Class<?>> seen, int level) throws Exception {
for (Method method : clz.getMethods()) {
if (isSetter(method)) {
if (takesPrimitive(method)) {
random(method, model);
} else if (takesEnum(method)) {
shuffle(method, model);
}
else if(takesBigDecimal(method)) {
populateBigDecimal(method, model);
} else if (takesXmlGregorianCalendar(method)) {
populateXmlGregorianCalendar(method, model);
}
else {
descend(method, model, scope(seen), level);
}
} else if (isGetter(method) && returnsList(method)) {
fill(method, model, seen, level);
}
}
return model;
}
private static void populateBigDecimal(Method method, Object model) throws Exception {
method.invoke(model, new BigDecimal(rand(100)));
}
private static void populateXmlGregorianCalendar(Method method, Object model) throws Exception {
method.invoke(model, DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar(1111, 10, 29)));
}
private static Object instantiate(Class<?> clz) throws Exception {
Object model = null;
model = clz.newInstance();
return model;
}
private static boolean takesPrimitive(Method m) {
return m.getParameterTypes().length == 1 && (
takesString(m) ||
takesBoolean(m) ||
takesShort(m) ||
takesInteger(m) ||
takesLong(m) ||
takesDouble(m)
);
}
private static void random(Method m, Object model) throws Exception {
Object value = null;
if (takesString(m)) {
value = garble(m);
}
else if (takesShort(m)) {
value = (short) rand(100);
}
else if (takesInteger(m)) {
value = rand(100);
}
else if (takesLong(m)) {
value = (long) rand(1000000000);
}
else if (takesBoolean(m)) {
value = Math.random() < 0.5D;
}
else if (takesDouble(m)) {
value = Math.random();
}
if (value != null) {
m.invoke(model, value);
}
}
@SafeVarargs
public static <E extends Enum<E>> E shuffle(Class<E> enumType, E... excludeValues) {
final Set<E> valuesSet = complementOf(enumType, excludeValues);
final E[] values = valuesSet.toArray((E[]) Array.newInstance(enumType, valuesSet.size()));
return values[rand(values.length)];
}
private static <E extends Enum<E>> EnumSet<E> complementOf(Class<E> enumType, E[] excludeValues) {
final EnumSet<E> result = EnumSet.allOf(enumType);
result.removeAll(Arrays.asList(excludeValues));
return result;
}
private static void shuffle(Method method, Object model) throws Exception {
Class<? extends Enum> enumType = (Class<? extends Enum>)method.getParameterTypes()[0];
method.invoke(model, shuffle(enumType));
}
private static void descend(Method method, Object model, List<Class<?>> seen, int level) throws Exception {
Object child = method.getParameterTypes()[0].newInstance();
method.invoke(model, child);
if (level == 1 || (unseen(method, seen) && (level <= 3))) {
populate(child, child.getClass(), seen, ++level);
}
}
@SuppressWarnings("unchecked")
private static void fill(Method method, Object model, List<Class<?>> seen, int level) throws Exception {
ParameterizedType returnType = (ParameterizedType) method.getGenericReturnType();
Class<?> childType = (Class<?>) returnType.getActualTypeArguments()[0];
if (level == 1 || unseen(childType, seen)) {
List<Object> list = (List<Object>) method.invoke(model);
Object child = null;
if (childType.isEnum()) {
Object[] labels = childType.getEnumConstants();
child = labels[rand(labels.length)];
} else {
child = childType.newInstance();
}
list.add(child);
populate(child, child.getClass(), seen, ++level);
}
}
private static boolean isGetter(Method m) {
return m.getName().startsWith(GET_ROOT);
}
private static boolean isSetter(Method m) {
return m.getName().startsWith(SET_ROOT);
}
private static boolean takesString(Method m) {
return String.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesShort(Method m) {
return Short.TYPE.equals(m.getParameterTypes()[0])
|| Short.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesInteger(Method m) {
return Integer.TYPE.equals(m.getParameterTypes()[0])
|| Integer.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesLong(Method m) {
return Long.TYPE.equals(m.getParameterTypes()[0])
|| Long.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesBoolean(Method m) {
return Boolean.TYPE.equals(m.getParameterTypes()[0])
|| Boolean.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesDouble(Method m) {
return Double.TYPE.equals(m.getParameterTypes()[0])
|| Double.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesBigDecimal(Method m) {
return BigDecimal.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesXmlGregorianCalendar(Method m) {
return XMLGregorianCalendar.class.equals(m.getParameterTypes()[0]);
}
private static boolean takesEnum(Method m) {
return m.getParameterTypes()[0].isEnum();
}
private static boolean returnsList(Method m) {
return List.class.equals(m.getReturnType());
}
private static boolean unseen(Class<?> type, List<Class<?>> seen) {
boolean ret = !seen.contains(type);
if (ret) {
seen.add(type);
}
return ret;
}
private static boolean unseen(Method m, List<Class<?>> seen) {
return unseen(m.getParameterTypes()[0], seen);
}
private static List<Class<?>> scope(List<Class<?>> seen) {
return new ArrayList<>(seen);
}
public static int rand(int ceiling) {
return (int) Math.floor(Math.random() * 0.9999 * ceiling);
}
private static Object garble(Method m) {
return m.getName().endsWith("Id") ? GuidUtils.asGuid(UUID.randomUUID().toString()).toString()
: new String(new byte[] { (byte) (65 + rand(26)), (byte) (65 + rand(26)),
(byte) (65 + rand(26)) });
}
}