package com.squareup.burst;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import static com.squareup.burst.Util.checkNotNull;
/**
* A wrapper around {@link Constructor} that can also set fields reflectively.
*/
final class TestConstructor {
private final Constructor<?> constructor;
private final Field[] fields;
public TestConstructor(Constructor<?> constructor, Field... fields) {
this.constructor = checkNotNull(constructor, "constructor");
this.fields = fields;
for (Field field : fields) {
field.setAccessible(true);
}
}
public String getName() {
return constructor.getName();
}
public Class<?>[] getConstructorParameterTypes() {
return constructor.getParameterTypes();
}
/**
* Returns an array of the Class objects associated with the fields this constructor was
* initialized with, followed by the parameter types of this constructor.
* If the constructor was declared with no fields and no parameters, an empty array will be
* returned.
*/
public Class<?>[] getVariationTypes() {
final Class<?>[] ctorTypes = constructor.getParameterTypes();
final Class<?>[] allTypes = new Class<?>[ctorTypes.length + fields.length];
System.arraycopy(ctorTypes, 0, allTypes, 0, ctorTypes.length);
for (int i = 0; i < fields.length; i++) {
allTypes[i + ctorTypes.length] = fields[i].getType();
}
return allTypes;
}
/**
* Calls {@link Constructor#newInstance(java.lang.Object...)}, as
* well as initializing all the fields passed to
* {@link TestConstructor#TestConstructor(Constructor, Field...)}.
* <p>
* Constructor arguments should be first in the array, followed by field arguments.
*/
public Object newInstance(Object[] args)
throws IllegalAccessException, InvocationTargetException, InstantiationException {
if (args.length != getVariationTypes().length) {
throw new IllegalArgumentException(String.format(
"Constructor takes %d values, only %d passed", getVariationTypes().length, args.length));
}
// Partition arg list
final Object[] ctorArgs = extractConstructorArgs(args);
final Object[] fieldArgs = extractFieldArgs(args);
final Object instance = newInstanceWithoutFields(ctorArgs);
initializeFieldsOnInstance(instance, fieldArgs);
return instance;
}
/**
* @return array containing the elements of <code>args</code> that apply to the underlying
* constructor.
*/
public Object[] extractConstructorArgs(final Object[] args) {
return Arrays.copyOfRange(args, 0, constructor.getParameterTypes().length);
}
/**
* @return array containing the elements of <code>args</code> that apply to fields.
*/
public Object[] extractFieldArgs(final Object[] args) {
return Arrays.copyOfRange(args, constructor.getParameterTypes().length, args.length);
}
/**
* Creates a new instance by calling the underlying constructor, without initializing
* any fields.
*/
public Object newInstanceWithoutFields(Object[] args)
throws IllegalAccessException, InvocationTargetException, InstantiationException {
return constructor.newInstance(args);
}
/**
* Sets the fields on an instance (as passed to
* {@link TestConstructor#TestConstructor(Constructor, Field...)}) to <code>args</code>.
* The number of arguments in the array must be equal to the number of fields.
*/
public void initializeFieldsOnInstance(final Object instance, final Object[] args)
throws IllegalAccessException {
if (fields.length != args.length) {
throw new IllegalArgumentException(String.format(
"Requires values for %d fields, only %d values passed", fields.length, args.length));
}
for (int i = 0; i < fields.length; i++) {
fields[i].set(instance, args[i]);
}
}
}