Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

217. Classes can be instantiated by user defined instantiators. #222

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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