us.ihmc.euclid.referenceFrame.api.ReflectionBasedBuilder Maven / Gradle / Ivy
package us.ihmc.euclid.referenceFrame.api;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.ejml.data.DMatrix;
import org.ejml.data.DMatrixRMaj;
import us.ihmc.euclid.referenceFrame.FrameConvexPolygon2D;
import us.ihmc.euclid.referenceFrame.FramePoint3D;
import us.ihmc.euclid.referenceFrame.ReferenceFrame;
import us.ihmc.euclid.referenceFrame.interfaces.ReferenceFrameHolder;
import us.ihmc.euclid.tools.EuclidCoreRandomTools;
/**
* The class provides tools for generating random objects and cloning them.
*
* Random generators are separated into 2 categories: frame and frameless type builders. Frame types
* represent geometries that defined with a {@link ReferenceFrame}, for instance
* {@link FramePoint3D} or {@link FrameConvexPolygon2D}, while frameless types represent all other
* types.
*
*
* This interface is part of the API testing framework.
*
*
* @see EuclidFrameAPITester
* @author Sylvain Bertrand
*/
public class ReflectionBasedBuilder
{
private final Map, RandomFrameTypeBuilder> frameTypeBuilders = new HashMap<>();
private final Map, RandomFramelessTypeBuilder> framelessTypeBuilders = new HashMap<>();
/**
* Create a new builder.
*
* Custom random generators can be registered.
*
*/
public ReflectionBasedBuilder()
{
framelessTypeBuilders.put(double.class, random -> EuclidCoreRandomTools.nextDouble(random, 10.0));
framelessTypeBuilders.put(float.class, random -> (float) EuclidCoreRandomTools.nextDouble(random, 10.0));
framelessTypeBuilders.put(boolean.class, random -> random.nextBoolean());
framelessTypeBuilders.put(int.class, random -> random.nextInt(1000) - 500);
framelessTypeBuilders.put(char.class, random -> (char) (random.nextInt(1000) - 500));
framelessTypeBuilders.put(long.class, random -> (long) (random.nextInt(1000) - 500));
framelessTypeBuilders.put(double[].class, random -> random.doubles(20, -10.0, 10.0).toArray());
framelessTypeBuilders.put(float[].class, random -> nextFloatArray(random));
framelessTypeBuilders.put(boolean[].class, random -> nextBooleanArray(random));
framelessTypeBuilders.put(int[].class, random -> random.ints(20, -100, 100).toArray());
framelessTypeBuilders.put(char[].class, random -> nextCharArray(random));
framelessTypeBuilders.put(long[].class, random -> random.longs(20, -100, 100).toArray());
framelessTypeBuilders.put(DMatrix.class, random -> EuclidCoreRandomTools.nextDMatrixRMaj(random, 20, 20));
}
/**
* Registers a new random generator that can generate random frame object to this builder.
*
* @param frameTypeBuilder the generator to register.
*/
public void registerFrameTypeBuilder(RandomFrameTypeBuilder frameTypeBuilder)
{
frameTypeBuilders.put(frameTypeBuilder.newInstance(new Random(), ReferenceFrame.getWorldFrame()).getClass(), frameTypeBuilder);
}
/**
* Registers a new random generator that can generate random frameless object to this builder.
*
* @param framelessTypeBuilder the generator to register.
*/
public void registerFramelessTypeBuilder(RandomFramelessTypeBuilder framelessTypeBuilder)
{
framelessTypeBuilders.put(framelessTypeBuilder.newInstance(new Random()).getClass(), framelessTypeBuilder);
}
/**
* Registers random generators declared as static method in the given {@code classes}.
*
* Convention used to extract the random generators:
*
* - Any static method declaring a non-void return type and with a single parameter type that is
* {@link Random} is extracted into a random frameless type generator.
*
- Any static method declaring a non-void return type and with two parameter types that are in
* order {@link Random} and {@link ReferenceFrame} is extracted into a random frame type generator.
*
*
*
* @param classes the classes from which to extract random generators.
*/
public void registerRandomGeneratorClasses(Class>... classes)
{
for (Class> clazz : classes)
registerRandomGeneratorsFromClass(clazz);
}
private void registerRandomGeneratorsFromClass(Class> classDeclaringRandomGenerators)
{
List frameTypeRandomGenerators = Stream.of(classDeclaringRandomGenerators.getMethods()).filter(method -> isFrameRandomGenerator(method))
.collect(Collectors.toList());
for (Method generator : frameTypeRandomGenerators)
{
if (generator.getReturnType() == null || generator.getReturnType() == void.class)
continue;
frameTypeBuilders.put(generator.getReturnType(), (random, frame) ->
{
try
{
return (ReferenceFrameHolder) generator.invoke(null, random, frame);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new ReflectionBasedBuilderException(e);
}
});
}
List framelessTypeGenerators = Stream.of(classDeclaringRandomGenerators.getMethods()).filter(method -> isFramelessRandomGenerator(method))
.collect(Collectors.toList());
for (Method generator : framelessTypeGenerators)
{
framelessTypeBuilders.put(generator.getReturnType(), random ->
{
try
{
return generator.invoke(null, random);
}
catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
{
throw new ReflectionBasedBuilderException(e);
}
});
}
}
private boolean isFramelessRandomGenerator(Method method)
{
return method.getParameterCount() == 1 && method.getParameterTypes()[0] == Random.class;
}
private boolean isFrameRandomGenerator(Method method)
{
if (method.getParameterCount() != 2)
return false;
if (method.getParameterTypes()[0] != Random.class || method.getParameterTypes()[1] != ReferenceFrame.class)
return false;
return ReferenceFrameHolder.class.isAssignableFrom(method.getReturnType());
}
Object next(Random random, ReferenceFrame frame, Class> type)
{
if (Object.class.equals(type))
return null;
if (Collection.class.equals(type))
return null;
Object object = nextFrameType(random, type, frame);
if (object != null)
return object;
object = nextFramelessType(random, type);
if (object != null)
return object;
throw new ReflectionBasedBuilderException("Unknown class: " + type.getSimpleName()
+ "\nThis class can be handled simply by registering a class that declares random generator as method with the following signature:\n\t"
+ type.getSimpleName() + " generatorNameDoesNotMatter(Random)\nor:\n\t" + type.getSimpleName()
+ " generatorNameDoesNotMatter(Random, ReferenceFrame) if the type is a frame type.\nNote that the return type can be any sub-class of "
+ type.getSimpleName() + "\nThe class declaring the generator is to be registered using " + ReflectionBasedBuilder.class.getSimpleName()
+ ".registerRandomGeneratorClasses(Class>...).");
}
Object[] next(Random random, ReferenceFrame frame, Class>[] types)
{
Object[] instances = new Object[types.length];
for (int i = 0; i < types.length; i++)
{
Class> type = types[i];
if (type.isArray() && !type.getComponentType().isPrimitive())
{
Class> componentType = type.getComponentType();
Object[] array = (Object[]) Array.newInstance(componentType, random.nextInt(15));
for (int j = 0; j < array.length; j++)
{
array[j] = next(random, frame, componentType);
if (array[j] == null)
return null;
}
instances[i] = array;
}
else
{
instances[i] = next(random, frame, type);
if (instances[i] == null)
return null;
}
}
return instances;
}
Object[] clone(Object[] parametersToClone)
{
Object[] clone = (Object[]) Array.newInstance(parametersToClone.getClass().getComponentType(), parametersToClone.length);
for (int i = 0; i < parametersToClone.length; i++)
{
Class extends Object> parameterType = parametersToClone[i].getClass();
if (ReferenceFrame.class.isAssignableFrom(parameterType))
{
clone[i] = parametersToClone[i];
}
else if (parameterType.isPrimitive() || parametersToClone[i] instanceof Number || parametersToClone[i] instanceof Boolean)
{
clone[i] = parametersToClone[i];
}
else if (DMatrix.class.isAssignableFrom(parameterType))
{
clone[i] = new DMatrixRMaj((DMatrix) parametersToClone[i]);
}
else if (float[].class.equals(parameterType))
{
float[] arrayToClone = (float[]) parametersToClone[i];
clone[i] = new float[arrayToClone.length];
System.arraycopy(arrayToClone, 0, clone[i], 0, arrayToClone.length);
}
else if (double[].class.equals(parameterType))
{
double[] arrayToClone = (double[]) parametersToClone[i];
clone[i] = new double[arrayToClone.length];
System.arraycopy(arrayToClone, 0, clone[i], 0, arrayToClone.length);
}
else if (int[].class.equals(parameterType))
{
int[] arrayToClone = (int[]) parametersToClone[i];
clone[i] = new int[arrayToClone.length];
System.arraycopy(arrayToClone, 0, clone[i], 0, arrayToClone.length);
}
else if (parameterType.isArray())
{
clone[i] = clone((Object[]) parametersToClone[i]);
}
else if (parameterType.isAnonymousClass())
{
clone[i] = parametersToClone[i];
}
else
{
try
{
ReferenceFrame frame = ReferenceFrame.getWorldFrame();
if (parametersToClone[i] instanceof ReferenceFrameHolder)
frame = ((ReferenceFrameHolder) parametersToClone[i]).getReferenceFrame();
clone[i] = next(new Random(), frame, parameterType);
Method setter = null;
for (Method method : parameterType.getMethods())
{
if (!method.getName().equals("set"))
continue;
if (method.getParameterTypes().length != 1)
continue;
if (method.getParameterTypes()[0].isAssignableFrom(parameterType))
{
setter = method;
break;
}
}
if (setter == null)
setter = parameterType.getMethod("set", parameterType);
setter.invoke(clone[i], parametersToClone[i]);
}
catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e)
{
System.err.println("Unhandled type: " + parameterType.getSimpleName());
return null;
}
catch (InvocationTargetException e)
{
System.err.println("Unhandled type: " + parameterType.getSimpleName());
e.getTargetException().printStackTrace();
return null;
}
}
}
return clone;
}
private Object nextFramelessType(Random random, Class> type)
{
List matchingBuilders = framelessTypeBuilders.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getKey()))
.map(Entry::getValue).collect(Collectors.toList());
if (matchingBuilders.isEmpty())
return null;
else
return matchingBuilders.get(random.nextInt(matchingBuilders.size())).newInstance(random);
}
private Object nextFrameType(Random random, Class> type, ReferenceFrame referenceFrame)
{
List matchingBuilders = frameTypeBuilders.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getKey()))
.map(Entry::getValue).collect(Collectors.toList());
if (matchingBuilders.isEmpty())
return null;
else
return matchingBuilders.get(random.nextInt(matchingBuilders.size())).newInstance(random, referenceFrame);
}
private static float[] nextFloatArray(Random random)
{
float[] next = new float[20];
for (int i = 0; i < next.length; i++)
next[i] = random.nextFloat();
return next;
}
private static boolean[] nextBooleanArray(Random random)
{
boolean[] next = new boolean[20];
for (int i = 0; i < next.length; i++)
next[i] = random.nextBoolean();
return next;
}
private static char[] nextCharArray(Random random)
{
char[] next = new char[20];
for (int i = 0; i < next.length; i++)
next[i] = (char) random.nextInt();
return next;
}
public static class ReflectionBasedBuilderException extends RuntimeException
{
private static final long serialVersionUID = 2307564351876519850L;
public ReflectionBasedBuilderException()
{
}
public ReflectionBasedBuilderException(String message, Throwable cause)
{
super(message, cause);
}
public ReflectionBasedBuilderException(String message)
{
super(message);
}
public ReflectionBasedBuilderException(Throwable cause)
{
super(cause);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy