Skip to content

Commit

Permalink
Extend testing for constructor based creation of random instances
Browse files Browse the repository at this point in the history
  • Loading branch information
stewbis committed Sep 14, 2018
1 parent b17556d commit 8066d2d
Show file tree
Hide file tree
Showing 14 changed files with 731 additions and 80 deletions.
54 changes: 9 additions & 45 deletions src/main/java/org/exparity/stub/bean/BeanBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
import static org.exparity.stub.core.ValueFactories.*;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.time.Instant;
Expand All @@ -18,8 +16,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -35,6 +31,8 @@
import org.exparity.beans.core.TypeProperty;
import org.exparity.beans.core.naming.ForceRootNameNamingStrategy;
import org.exparity.beans.core.naming.LowerCaseNamingStrategy;
import org.exparity.stub.core.NoDefaultConstructorException;
import org.exparity.stub.core.ValueFactories;
import org.exparity.stub.core.ValueFactory;
import org.exparity.stub.random.RandomBuilder;
import org.slf4j.Logger;
Expand Down Expand Up @@ -560,27 +558,6 @@ private void populateProperty(final Object instance,
}
}

private Object[] createArguments(final java.lang.reflect.Type[] types) {
if (types.length == 0) {
return new Object[0];
} else {
Object[] args = new Object[types.length];
for (int i = 0; i < types.length; ++i) {
Type type = Type.type(getActualType(types[i], 0));
if (type.is(Map.class)) {
args[i] = Collections.emptyMap();
} else if (type.is(Set.class)) {
args[i] = Collections.emptySet();
} else if (type.is(List.class) || type.is(Collection.class)) {
args[i] = Collections.emptyList();
} else {
args[i] = createValue(getActualType(types[i], 0));
}
}
return args;
}
}

private Class<? extends Object> getActualType(final java.lang.reflect.Type type, final int typeOrdinal) {
if (type instanceof Class) {
return (Class<? extends Object>) type;
Expand Down Expand Up @@ -674,7 +651,7 @@ private <E> E createValue(final Class<E> type) {
} else if (type.isEnum()) {
return createValue(aRandomEnum(type), type);
} else {
return createValue(aNewInstanceOf(type), type);
return createValue(ValueFactories.anEmptyInstanceOf(type), type);
}
}
case EMPTY: {
Expand All @@ -684,7 +661,7 @@ private <E> E createValue(final Class<E> type) {
} else if (type.isEnum()) {
return null;
} else {
return createValue(aNewInstanceOf(type), type);
return createValue(ValueFactories.anEmptyInstanceOf(type), type);
}
}
default:
Expand Down Expand Up @@ -775,28 +752,15 @@ private <K, V> Map<K, V> createMap(final Class<K> keyType,

private T createNewInstance() {
try {
Constructor<T> constructor = findConstructor();
java.lang.reflect.Type[] params = constructor.getGenericParameterTypes();
Object[] initargs = createArguments(params);
return constructor.newInstance(initargs);
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
return ValueFactories.anEmptyInstanceOf(type).createValue();
} catch (NoDefaultConstructorException e) {
throw e;
} catch (Exception e) {
throw new BeanBuilderException("Failed to instantiate '" + this.type + "'. Error [" + e.getMessage() + "]",
e);
}
}

private Constructor<T> findConstructor() {
List<Constructor<T>> constructors = Arrays.asList((Constructor<T>[]) this.type.getConstructors());
Collections.sort(constructors, new Comparator<Constructor<T>>() {

@Override
public int compare(final Constructor<T> o1, final Constructor<T> o2) {
return Integer.valueOf(o1.getParameterCount()).compareTo(o2.getParameterCount());
}
});
return constructors.get(0);
}

private int collectionSize(final BeanPropertyPath path) {
return selectNotNull(this.collectionSizeForPaths.get(path.fullPath()),
this.collectionSizeForPaths.get(path.fullPathWithNoIndexes()),
Expand All @@ -811,7 +775,7 @@ private String propertyName(final BeanPropertyPath path) {
private <X> List<ValueFactory<X>> createInstanceOfFactoriesForTypes(final Class<? extends X>... subtypes) {
List<ValueFactory<X>> factories = new ArrayList<>();
for (Class<? extends X> subtype : subtypes) {
factories.add((ValueFactory<X>) aNewInstanceOf(subtype));
factories.add((ValueFactory<X>) ValueFactories.anEmptyInstanceOf(subtype));
}
return factories;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.exparity.stub.core;

/**
* Typed exception so it's explicit when failure is due to a missing default constructor
*
* @author Stewart Bissett
*/
public class NoDefaultConstructorException extends RuntimeException {

private static final long serialVersionUID = 1L;

public NoDefaultConstructorException(final Class<?> type, final InstantiationException e) {
super("Class '" + type.getSimpleName() + "'has no default constructor", e);
}
}
10 changes: 6 additions & 4 deletions src/main/java/org/exparity/stub/core/ValueFactories.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@
import java.util.Date;
import java.util.List;

import org.exparity.stub.bean.BeanBuilder;
import org.exparity.stub.random.RandomBuilder;


/**
* Static factory for creating instances of {@link ValueFactory} and {@link ArrayFactory} for use in the {@link BeanBuilder} and {@link RandomBuilder}
* Static factory for creating instances of {@link ValueFactory} and {@link ArrayFactory} for use in the {@link org.exparity.stub.bean.BeanBuilder} and {@link RandomBuilder}
*
* @author Stewart Bissett
*/
Expand Down Expand Up @@ -216,10 +216,12 @@ public static <A> ArrayFactory<A> aRandomArrayOf(final ValueFactory<A> typeFacto
* @param type the type to create a new instance of
* @return an {@link ArrayFactory} which returns a new unpopulated instance of the given type.
*/
public static <T> ValueFactory<T> aNewInstanceOf(final Class<T> type) {
public static <T> ValueFactory<T> anEmptyInstanceOf(final Class<T> type) {
return () -> {
try {
return type.newInstance();
} catch (InstantiationException e) {
throw new NoDefaultConstructorException(type, e);
} catch (Exception e) {
throw new ValueFactoryException(
"Failed to instantiate instance of '" + type.getCanonicalName() + "'",
Expand All @@ -244,7 +246,7 @@ public static <T> ValueFactory<T> oneOf(final ValueFactory<T>... factories) {
* @return an {@link ValueFactory} which returns a random instance of the given type by using one of the supplied {@link ValueFactory} instances.
*/
public static <T> ValueFactory<T> oneOf(final Collection<ValueFactory<T>> factories) {
return () -> new ArrayList<ValueFactory<T>>(factories).get(nextInt(factories.size())).createValue();
return () -> new ArrayList<>(factories).get(nextInt(factories.size())).createValue();
}

/**
Expand Down
117 changes: 114 additions & 3 deletions src/main/java/org/exparity/stub/random/RandomBuilder.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
package org.exparity.stub.random;

import static java.util.Collections.singleton;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static java.util.Comparator.comparing;
import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.apache.commons.lang.math.RandomUtils.*;
import static org.apache.commons.lang.time.DateUtils.addSeconds;
import static org.exparity.stub.core.ValueFactories.anEmptyInstanceOf;
import static org.exparity.stub.core.ValueFactories.theValue;

import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.math.MathContext;
import java.math.RoundingMode;
Expand All @@ -20,7 +30,11 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;

import org.exparity.beans.Type;
import org.exparity.stub.bean.BeanBuilder;
import org.exparity.stub.bean.BeanBuilderException;
import org.exparity.stub.core.ValueFactories;
Expand Down Expand Up @@ -72,6 +86,12 @@ public static interface RandomRestriction {
put(String.class, ValueFactories.aRandomString());
put(BigDecimal.class, ValueFactories.aRandomDecimal());
put(Date.class, ValueFactories.aRandomDate());
put(Date.class, ValueFactories.aRandomDate());
put(LocalDate.class, ValueFactories.aRandomLocalDate());
put(LocalTime.class, ValueFactories.aRandomLocalTime());
put(LocalDateTime.class, ValueFactories.aRandomLocalDateTime());
put(ZonedDateTime.class, ValueFactories.aRandomZonedDateTime());
put(Instant.class, ValueFactories.aRandomInstant());
}
};

Expand Down Expand Up @@ -802,14 +822,105 @@ public static <P> RandomRestriction collectionSizeForProperty(final String prope
private static <T> ValueFactory<T> instanceFactoryFor(final Class<T> type,
final RandomRestriction... restrictions) {
ValueFactory<T> factory = RANDOM_FACTORIES.get(type);
if (factory == null) {
if (factory != null) {
return factory;
} else if (type.isEnum()) {
return ValueFactories.aRandomEnum(type);
} else if (isBean(type)) {
BeanBuilder<T> builder = BeanBuilder.aRandomInstanceOf(type);
for (RandomRestriction restriction : restrictions) {
restriction.applyTo(builder);
}
factory = ValueFactories.theValue(builder.build());
return ValueFactories.theValue(builder.build());
} else {
Optional<Constructor<T>> constructor = findConstructor(type);
if (constructor.isPresent()) {
return theValue(createInstance(constructor.get()));
} else {
return theValue(anEmptyInstanceOf(type).createValue());
}
}
}

private static boolean isBean(final Class<?> type) {
// TODO Look for default constructor and get/set of properties
return hasDefaultConstructor(type);
}

private static boolean hasDefaultConstructor(final Class<?> type) {
return Stream.of(type.getConstructors()).map(Constructor::getParameterCount).anyMatch(count -> count == 0);
}

@SuppressWarnings("unchecked")
private static <T> Optional<Constructor<T>> findConstructor(final Class<T> type) {
return Stream.of((Constructor<T>[]) type.getConstructors()).min(comparing(Constructor::getParameterCount));
}

private static <T> T createInstance(final Constructor<T> constructor) {
try {
java.lang.reflect.Type[] params = constructor.getGenericParameterTypes();
Object[] initargs = createArguments(params);
return constructor.newInstance(initargs);
} catch (InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException e) {
throw new RandomBuilderException("Could not instantiate type " + constructor.getDeclaringClass().getName()
+ " using "
+ constructor, e);
}
}

private static Object[] createArguments(final java.lang.reflect.Type[] types) {
if (types.length == 0) {
return new Object[0];
} else {
Object[] args = new Object[types.length];
for (int i = 0; i < types.length; ++i) {
Class<? extends Object> rawType = getRawType(types[i]);
Type type = Type.type(rawType);
if (type.isArray()) {
// TODO use size restrictions to populate correct size
Class<?> arrayType = rawType.getComponentType();
Object value = Array.newInstance(arrayType, 0);
args[i] = value;
} else if (type.is(Map.class)) {
// TODO use size restrictions to populate correct size
args[i] = singletonMap(getActualType(types[i], 0), getActualType(types[i], 1));
} else if (type.is(Set.class)) {
// TODO use size restrictions to populate correct size
args[i] = singleton(aRandomInstanceOf(getActualType(types[i], 0)));
} else if (type.is(List.class) || type.is(Collection.class)) {
// TODO use size restrictions to populate correct size
args[i] = singletonList(aRandomInstanceOf(getActualType(types[i], 0)));
} else {
args[i] = aRandomInstanceOf(rawType);
}
}
return args;
}
}

@SuppressWarnings("unchecked")
private static Class<? extends Object> getActualType(final java.lang.reflect.Type type, final int typeOrdinal) {
if (type instanceof Class) {
return (Class<? extends Object>) type;
} else if (type instanceof ParameterizedType) {
return getActualType(((ParameterizedType) type).getActualTypeArguments()[typeOrdinal], 0);
} else {
throw new IllegalArgumentException("Unknown type subclass '" + type.getClass());
}
}

@SuppressWarnings("unchecked")
private static Class<? extends Object> getRawType(final java.lang.reflect.Type type) {
if (type instanceof Class) {
return (Class<? extends Object>) type;
} else if (type instanceof ParameterizedType) {
return getRawType(((ParameterizedType) type).getRawType());
} else {
throw new IllegalArgumentException("Unknown type subclass '" + type.getClass());
}
return factory;
}

}
14 changes: 7 additions & 7 deletions src/main/java/org/exparity/stub/stub/StubBuilder.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.exparity.stub.stub;

import static org.exparity.stub.core.ValueFactories.aNewInstanceOf;
import static org.exparity.stub.core.ValueFactories.anEmptyInstanceOf;
import static org.exparity.stub.core.ValueFactories.oneOf;

import java.lang.reflect.Type;
Expand Down Expand Up @@ -33,7 +33,7 @@ public static <T> StubBuilder<T> aRandomStubOf(final Class<T> type) {
throw new IllegalArgumentException(
"Use StubBuilder.aRandomStubOf(final TypeReference<T> typeRef) method to create prototypes for generic types. See javadocs on method for example.");
}
return new StubBuilder<T>(type);
return new StubBuilder<>(type);
}

/**
Expand Down Expand Up @@ -63,18 +63,18 @@ public static <T> StubBuilder<T> aRandomStubOf(final Class<T> type) {
* @param type the type to return the {@link StubBuilder} for
*/
public static <T> StubBuilder<T> aRandomStubOf(final TypeReference<T> type) {
return new StubBuilder<T>(type.getType());
return new StubBuilder<>(type.getType());
}

private final StubDefinition<T> definition;
private final StubFactory factory = new StubFactory();

private StubBuilder(final Type type) {
this.definition = new StubDefinition<T>(type);
this.definition = new StubDefinition<>(type);
}

private StubBuilder(final Class<T> type) {
this.definition = new StubDefinition<T>(type);
this.definition = new StubDefinition<>(type);
}

/**
Expand Down Expand Up @@ -200,9 +200,9 @@ public T build() {
}

private <X> List<ValueFactory<X>> createInstanceOfFactoriesForTypes(final Class<? extends X>... subtypes) {
List<ValueFactory<X>> factories = new ArrayList<ValueFactory<X>>();
List<ValueFactory<X>> factories = new ArrayList<>();
for (Class<? extends X> subtype : subtypes) {
factories.add((ValueFactory<X>) aNewInstanceOf(subtype));
factories.add((ValueFactory<X>) anEmptyInstanceOf(subtype));
}
return factories;
}
Expand Down
Loading

0 comments on commit 8066d2d

Please sign in to comment.