Skip to content

Commit

Permalink
217. Classes can be instantiated by user defined instantiators.
Browse files Browse the repository at this point in the history
  • Loading branch information
sta-szek committed Dec 16, 2017
1 parent 73049f9 commit 647fc35
Show file tree
Hide file tree
Showing 33 changed files with 338 additions and 532 deletions.
54 changes: 45 additions & 9 deletions src/main/java/pl/pojo/tester/api/assertion/AbstractAssertion.java
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package pl.pojo.tester.api.assertion;

import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.slf4j.Logger;
import pl.pojo.tester.api.ClassAndFieldPredicatePair;
import pl.pojo.tester.api.ConstructorParameters;
import pl.pojo.tester.internal.field.AbstractFieldValueChanger;
import pl.pojo.tester.internal.instantiator.AbstractObjectInstantiator;
import pl.pojo.tester.internal.instantiator.SupplierInstantiator;
import pl.pojo.tester.internal.instantiator.UserDefinedConstructorInstantiator;
import pl.pojo.tester.internal.tester.AbstractTester;
import pl.pojo.tester.internal.utils.ClassLoader;
import pl.pojo.tester.internal.utils.Permutator;
import pl.pojo.tester.internal.utils.SublistFieldPermutator;
import pl.pojo.tester.internal.utils.ThoroughFieldPermutator;
import pl.pojo.tester.internal.tester.AbstractTester;
import pl.pojo.tester.internal.utils.ClassLoader;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;
import java.util.stream.Collectors;

import static pl.pojo.tester.internal.preconditions.ParameterPreconditions.checkNotBlank;
Expand All @@ -40,7 +44,7 @@ public abstract class AbstractAssertion {
.forEach(DEFAULT_TESTERS::add);
}

private final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters = new ArrayListValuedHashMap<>();
private final List<AbstractObjectInstantiator> instantiators = new LinkedList<>();
Set<AbstractTester> testers = new HashSet<>();
private AbstractFieldValueChanger abstractFieldValueChanger;
private Permutator permutator = new ThoroughFieldPermutator();
Expand Down Expand Up @@ -124,7 +128,7 @@ public void areWellImplemented() {
}

testers.forEach(tester -> tester.setPermutator(permutator));
testers.forEach(tester -> tester.setUserDefinedConstructors(constructorParameters));
testers.forEach(tester -> tester.setUserDefinedInstantiators(instantiators));

runAssertions();
}
Expand Down Expand Up @@ -164,8 +168,8 @@ public AbstractAssertion create(final String qualifiedClassName,
checkNotNull("constructorParameters", constructorParameters);

final Class<?> clazz = ClassLoader.loadClass(qualifiedClassName);
this.constructorParameters.put(clazz, constructorParameters);
return this;

return create(clazz, constructorParameters);
}

/**
Expand Down Expand Up @@ -202,7 +206,39 @@ public AbstractAssertion create(final Class<?> clazz, final ConstructorParameter
checkNotNull("clazz", clazz);
checkNotNull("constructorParameters", constructorParameters);

this.constructorParameters.put(clazz, constructorParameters);
final UserDefinedConstructorInstantiator instantiator = new UserDefinedConstructorInstantiator(clazz,
constructorParameters);
return create(clazz, instantiator);
}

/**
* Indicates, that class should be constructed using given instantiator.
*
* @param clazz class to instantiate
* @param instantiator instantiator which will create instance of given class
* @return itself
* @see ConstructorParameters
*/
public AbstractAssertion create(final Class<?> clazz, final AbstractObjectInstantiator instantiator) {
checkNotNull("clazz", clazz);
checkNotNull("instantiator", instantiator);

return create(clazz, instantiator::instantiate);
}

/**
* Indicates, that class should be constructed using given supplier.
*
* @param clazz class to instantiate
* @param supplier supplier that will create given class
* @return itself
* @see ConstructorParameters
*/
public AbstractAssertion create(final Class<?> clazz, final Supplier<?> supplier) {
checkNotNull("clazz", clazz);
checkNotNull("supplier", supplier);

this.instantiators.add(new SupplierInstantiator(clazz, supplier));
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,66 +1,34 @@
package pl.pojo.tester.internal.instantiator;


import org.apache.commons.collections4.MultiValuedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.pojo.tester.api.ConstructorParameters;
import pl.pojo.tester.internal.utils.CollectionUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;
import java.util.Optional;


abstract class AbstractMultiConstructorInstantiator extends AbstractObjectInstantiator {

private static final Logger LOGGER = LoggerFactory.getLogger(AbstractMultiConstructorInstantiator.class);
private final List<AbstractObjectInstantiator> instantiators;

AbstractMultiConstructorInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
super(clazz, constructorParameters);
}

protected Object instantiateUsingUserParameters() {
final Collection<ConstructorParameters> userConstructorParameters = constructorParameters.get(clazz);
if (userDefinedOwnParametersForThisClass(userConstructorParameters)) {
final Object result = tryToInstantiateUsing(userConstructorParameters);
if (result != null) {
return result;
}
LOGGER.warn("Could not instantiate class {} with user defined parameters. "
+ "Trying create instance finding best constructor", clazz);
}
return null;
}

private boolean userDefinedOwnParametersForThisClass(final Collection<ConstructorParameters> userConstructorParameters) {
return CollectionUtils.isNotEmpty(userConstructorParameters);
}

private Object tryToInstantiateUsing(final Collection<ConstructorParameters> userConstructorParameters) {
for (final ConstructorParameters param : userConstructorParameters) {
Class<?>[] parameterTypes = param.getParametersTypes();
try {
Object[] parameters = param.getParameters();
if (isInnerClass()) {
parameterTypes = putEnclosingClassAsFirstParameterType(clazz.getEnclosingClass(), parameterTypes);
final Object enclosingClassInstance = instantiateEnclosingClass();
parameters = putEnclosingClassInstanceAsFirstParameter(enclosingClassInstance, parameters);
}
return createObjectFromArgsConstructor(parameterTypes, parameters);
} catch (final ObjectInstantiationException e) {
LOGGER.debug("ObjectInstantiationException:", e);
// ignore, try all user defined constructor parameters and types
}
}
return null;
AbstractMultiConstructorInstantiator(final Class<?> clazz, final List<AbstractObjectInstantiator> instantiators) {
super(clazz);
this.instantiators = instantiators;
}

protected Object createFindingBestConstructor() {
final Optional<AbstractObjectInstantiator> instantiator = instantiators.stream()
.filter(i -> clazz.equals(i.clazz))
.findFirst();
if (instantiator.isPresent()) {
return instantiator.get()
.instantiate();
}
final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
return Arrays.stream(constructors)
.map(this::createObjectFromConstructor)
Expand All @@ -75,36 +43,14 @@ protected Object createFindingBestConstructor() {

protected abstract ObjectInstantiationException createObjectInstantiationException();

private Object instantiateEnclosingClass() {
final Class<?> enclosingClass = clazz.getEnclosingClass();
return Instantiable.forClass(enclosingClass, constructorParameters)
.instantiate();
}

private Class[] putEnclosingClassAsFirstParameterType(final Class<?> enclosingClass,
final Class<?>[] constructorParametersTypes) {
return Stream.concat(Stream.of(enclosingClass), Arrays.stream(constructorParametersTypes))
.toArray(Class[]::new);
}

private boolean isInnerClass() {
return clazz.getEnclosingClass() != null && !Modifier.isStatic(clazz.getModifiers());
}

private Object[] putEnclosingClassInstanceAsFirstParameter(final Object enclosingClassInstance,
final Object[] arguments) {
return Stream.concat(Stream.of(enclosingClassInstance), Arrays.stream(arguments))
.toArray(Object[]::new);
}

private Object createObjectFromConstructor(final Constructor<?> constructor) {
makeAccessible(constructor);
if (constructor.getParameterCount() == 0) {
return createObjectFromNoArgsConstructor(constructor);
} else {
try {
final Object[] parameters = Instantiable.instantiateClasses(constructor.getParameterTypes(),
constructorParameters);
instantiators);
return createObjectFromArgsConstructor(constructor.getParameterTypes(), parameters);
} catch (final Exception e) {
LOGGER.debug("Exception:", e);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
package pl.pojo.tester.internal.instantiator;

import org.apache.commons.collections4.MultiValuedMap;
import pl.pojo.tester.api.ConstructorParameters;

abstract class AbstractObjectInstantiator {
public abstract class AbstractObjectInstantiator {

protected final Class<?> clazz;
protected final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters;

AbstractObjectInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
AbstractObjectInstantiator(final Class<?> clazz) {
this.clazz = clazz;
this.constructorParameters = constructorParameters;
}

public abstract Object instantiate();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
package pl.pojo.tester.internal.instantiator;

import org.apache.commons.collections4.MultiValuedMap;
import pl.pojo.tester.api.ConstructorParameters;

import java.lang.reflect.Array;

class ArrayInstantiator extends AbstractObjectInstantiator {

private static final int DEFAULT_ARRAY_LENGTH = 0;

ArrayInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
super(clazz, constructorParameters);
ArrayInstantiator(final Class<?> clazz) {
super(clazz);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,24 @@
package pl.pojo.tester.internal.instantiator;


import org.apache.commons.collections4.MultiValuedMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pl.pojo.tester.api.ConstructorParameters;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.List;

class BestConstructorInstantiator extends AbstractMultiConstructorInstantiator {

private static final Logger LOGGER = LoggerFactory.getLogger(BestConstructorInstantiator.class);

BestConstructorInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
super(clazz, constructorParameters);
BestConstructorInstantiator(final Class<?> clazz, final List<AbstractObjectInstantiator> instantiators) {
super(clazz, instantiators);
}

@Override
public Object instantiate() {
Object result = instantiateUsingUserParameters();
if (result == null) {
result = createFindingBestConstructor();
}
return result;
return createFindingBestConstructor();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package pl.pojo.tester.internal.instantiator;


import org.apache.commons.collections4.MultiValuedMap;
import pl.pojo.tester.api.ConstructorParameters;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
Expand Down Expand Up @@ -58,9 +55,8 @@ class CollectionInstantiator extends AbstractObjectInstantiator {
PREPARED_OBJECTS.put(Iterable.class, new ArrayList<>());
}

CollectionInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
super(clazz, constructorParameters);
CollectionInstantiator(final Class<?> clazz) {
super(clazz);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
package pl.pojo.tester.internal.instantiator;


import org.apache.commons.collections4.MultiValuedMap;
import pl.pojo.tester.api.ConstructorParameters;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.Arrays;

class DefaultConstructorInstantiator extends AbstractObjectInstantiator {

DefaultConstructorInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
super(clazz, constructorParameters);
DefaultConstructorInstantiator(final Class<?> clazz) {
super(clazz);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package pl.pojo.tester.internal.instantiator;


import org.apache.commons.collections4.MultiValuedMap;
import pl.pojo.tester.api.ConstructorParameters;

import java.util.Random;

class EnumInstantiator extends AbstractObjectInstantiator {

EnumInstantiator(final Class<?> clazz,
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
super(clazz, constructorParameters);
EnumInstantiator(final Class<?> clazz) {
super(clazz);
}

@Override
Expand Down
Loading

0 comments on commit 647fc35

Please sign in to comment.