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 17, 2017
1 parent 58c2dab commit 2fe5c35
Show file tree
Hide file tree
Showing 34 changed files with 401 additions and 584 deletions.
40 changes: 40 additions & 0 deletions src/main/java/pl/pojo/tester/api/AbstractObjectInstantiator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package pl.pojo.tester.api;

/**
* This is a base class for all instantiators.
*
* @author Piotr Joński
* @since 0.8.0
*/
public abstract class AbstractObjectInstantiator {

protected final Class<?> clazz;

/**
* Creates new instantiator for defined class.
*
* @param clazz class that will be instantiated
*/
public AbstractObjectInstantiator(final Class<?> clazz) {
this.clazz = clazz;
}

/**
* Produces new instances of given class.
*
* @return new object that class is defined in constructor.
*/
public abstract Object instantiate();

/**
* @return class defined in constructor.
*/
public Class<?> getClazz() {
return clazz;
}

@Override
public String toString() {
return "AbstractObjectInstantiator{clazz=" + clazz + '}';
}
}
108 changes: 79 additions & 29 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.AbstractObjectInstantiator;
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.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 @@ -109,26 +113,6 @@ public AbstractAssertion testing(final Method method) {
return this;
}

/**
* Performs specified tests on classes using declared field value changer.
*
* @see Method
* @see AbstractFieldValueChanger
*/
public void areWellImplemented() {
if (testers.isEmpty()) {
testers = DEFAULT_TESTERS;
}
if (abstractFieldValueChanger != null) {
testers.forEach(tester -> tester.setFieldValuesChanger(abstractFieldValueChanger));
}

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

runAssertions();
}

/**
* Indicates, that class should be constructed using given constructor parameters. Constructor will be selected
* based on constructor parameter's types.
Expand Down Expand Up @@ -164,8 +148,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 All @@ -188,7 +172,6 @@ public AbstractAssertion create(final Class<?> clazz,
return create(clazz, constructorParameter);
}


/**
* Indicates, that class should be constructed using given constructor parameters. Constructor will be selected
* based on constructor parameter's types.
Expand All @@ -202,10 +185,76 @@ 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 instantiator instantiator which will create instance of given class
* @return itself
* @see ConstructorParameters
*/
public AbstractAssertion create(final AbstractObjectInstantiator instantiator) {
checkNotNull("clazz", instantiator.getClazz());
checkNotNull("instantiator", instantiator);

return create(instantiator.getClazz(), instantiator::instantiate);
}

/**
* 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;
}

/**
* Performs specified tests on classes using declared field value changer.
*
* @see Method
* @see AbstractFieldValueChanger
*/
public void areWellImplemented() {
if (testers.isEmpty()) {
testers = DEFAULT_TESTERS;
}
if (abstractFieldValueChanger != null) {
testers.forEach(tester -> tester.setFieldValuesChanger(abstractFieldValueChanger));
}

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

runAssertions();
}

protected abstract void runAssertions();

protected void logTestersAndClasses(final Logger logger,
Expand All @@ -218,6 +267,7 @@ protected void logTestersAndClasses(final Logger logger,
logger.debug("Running {} testers on {} classes", testers.size(), classAndFieldPredicatePairs.length);
logger.debug("Testers: {}", testers);
logger.debug("Classes: {}", classes);
logger.debug("Predefined instantiators: {}", instantiators);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package pl.pojo.tester.internal.instantiator;

import pl.pojo.tester.api.AbstractObjectInstantiator;

abstract class AbstractInternalInstantiator extends AbstractObjectInstantiator {

AbstractInternalInstantiator(final Class<?> clazz) {
super(clazz);
}

abstract boolean canInstantiate();

@Override
public String toString() {
return "AbstractInternalInstantiator{clazz=" + clazz + '}';
}
}
Original file line number Diff line number Diff line change
@@ -1,66 +1,35 @@
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 pl.pojo.tester.api.AbstractObjectInstantiator;

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 {
abstract class AbstractMultiConstructorInstantiator extends AbstractInternalInstantiator {

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);
AbstractMultiConstructorInstantiator(final Class<?> clazz, final List<AbstractObjectInstantiator> instantiators) {
super(clazz);
this.instantiators = instantiators;
}

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);
}
protected Object createFindingBestConstructor() {
final Optional<AbstractObjectInstantiator> instantiator = findFirstMatchingInPredefined();

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
}
if (instantiator.isPresent()) {
return instantiator.get()
.instantiate();
}
return null;
}

protected Object createFindingBestConstructor() {
final Constructor<?>[] constructors = clazz.getDeclaredConstructors();
return Arrays.stream(constructors)
.map(this::createObjectFromConstructor)
Expand All @@ -69,42 +38,26 @@ protected Object createFindingBestConstructor() {
.orElseThrow(this::createObjectInstantiationException);
}

private Optional<AbstractObjectInstantiator> findFirstMatchingInPredefined() {
return instantiators.stream()
.filter(eachInstantiator -> clazz.equals(eachInstantiator.getClazz()))
.findFirst();
}

protected abstract Object createObjectFromArgsConstructor(final Class<?>[] parameterTypes, Object[] parameters);

protected abstract Object createObjectFromNoArgsConstructor(final Constructor<?> constructor);

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

This file was deleted.

Loading

0 comments on commit 2fe5c35

Please sign in to comment.