Skip to content

Commit 2fe5c35

Browse files
committed
217. Classes can be instantiated by user defined instantiators.
1 parent 58c2dab commit 2fe5c35

34 files changed

+401
-584
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package pl.pojo.tester.api;
2+
3+
/**
4+
* This is a base class for all instantiators.
5+
*
6+
* @author Piotr Joński
7+
* @since 0.8.0
8+
*/
9+
public abstract class AbstractObjectInstantiator {
10+
11+
protected final Class<?> clazz;
12+
13+
/**
14+
* Creates new instantiator for defined class.
15+
*
16+
* @param clazz class that will be instantiated
17+
*/
18+
public AbstractObjectInstantiator(final Class<?> clazz) {
19+
this.clazz = clazz;
20+
}
21+
22+
/**
23+
* Produces new instances of given class.
24+
*
25+
* @return new object that class is defined in constructor.
26+
*/
27+
public abstract Object instantiate();
28+
29+
/**
30+
* @return class defined in constructor.
31+
*/
32+
public Class<?> getClazz() {
33+
return clazz;
34+
}
35+
36+
@Override
37+
public String toString() {
38+
return "AbstractObjectInstantiator{clazz=" + clazz + '}';
39+
}
40+
}

src/main/java/pl/pojo/tester/api/assertion/AbstractAssertion.java

+79-29
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,24 @@
11
package pl.pojo.tester.api.assertion;
22

3-
import org.apache.commons.collections4.MultiValuedMap;
4-
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
53
import org.slf4j.Logger;
4+
import pl.pojo.tester.api.AbstractObjectInstantiator;
65
import pl.pojo.tester.api.ClassAndFieldPredicatePair;
76
import pl.pojo.tester.api.ConstructorParameters;
87
import pl.pojo.tester.internal.field.AbstractFieldValueChanger;
8+
import pl.pojo.tester.internal.instantiator.SupplierInstantiator;
9+
import pl.pojo.tester.internal.instantiator.UserDefinedConstructorInstantiator;
10+
import pl.pojo.tester.internal.tester.AbstractTester;
11+
import pl.pojo.tester.internal.utils.ClassLoader;
912
import pl.pojo.tester.internal.utils.Permutator;
1013
import pl.pojo.tester.internal.utils.SublistFieldPermutator;
1114
import pl.pojo.tester.internal.utils.ThoroughFieldPermutator;
12-
import pl.pojo.tester.internal.tester.AbstractTester;
13-
import pl.pojo.tester.internal.utils.ClassLoader;
1415

1516
import java.util.Arrays;
1617
import java.util.HashSet;
18+
import java.util.LinkedList;
19+
import java.util.List;
1720
import java.util.Set;
21+
import java.util.function.Supplier;
1822
import java.util.stream.Collectors;
1923

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

43-
private final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters = new ArrayListValuedHashMap<>();
47+
private final List<AbstractObjectInstantiator> instantiators = new LinkedList<>();
4448
Set<AbstractTester> testers = new HashSet<>();
4549
private AbstractFieldValueChanger abstractFieldValueChanger;
4650
private Permutator permutator = new ThoroughFieldPermutator();
@@ -109,26 +113,6 @@ public AbstractAssertion testing(final Method method) {
109113
return this;
110114
}
111115

112-
/**
113-
* Performs specified tests on classes using declared field value changer.
114-
*
115-
* @see Method
116-
* @see AbstractFieldValueChanger
117-
*/
118-
public void areWellImplemented() {
119-
if (testers.isEmpty()) {
120-
testers = DEFAULT_TESTERS;
121-
}
122-
if (abstractFieldValueChanger != null) {
123-
testers.forEach(tester -> tester.setFieldValuesChanger(abstractFieldValueChanger));
124-
}
125-
126-
testers.forEach(tester -> tester.setPermutator(permutator));
127-
testers.forEach(tester -> tester.setUserDefinedConstructors(constructorParameters));
128-
129-
runAssertions();
130-
}
131-
132116
/**
133117
* Indicates, that class should be constructed using given constructor parameters. Constructor will be selected
134118
* based on constructor parameter's types.
@@ -164,8 +148,8 @@ public AbstractAssertion create(final String qualifiedClassName,
164148
checkNotNull("constructorParameters", constructorParameters);
165149

166150
final Class<?> clazz = ClassLoader.loadClass(qualifiedClassName);
167-
this.constructorParameters.put(clazz, constructorParameters);
168-
return this;
151+
152+
return create(clazz, constructorParameters);
169153
}
170154

171155
/**
@@ -188,7 +172,6 @@ public AbstractAssertion create(final Class<?> clazz,
188172
return create(clazz, constructorParameter);
189173
}
190174

191-
192175
/**
193176
* Indicates, that class should be constructed using given constructor parameters. Constructor will be selected
194177
* based on constructor parameter's types.
@@ -202,10 +185,76 @@ public AbstractAssertion create(final Class<?> clazz, final ConstructorParameter
202185
checkNotNull("clazz", clazz);
203186
checkNotNull("constructorParameters", constructorParameters);
204187

205-
this.constructorParameters.put(clazz, constructorParameters);
188+
final UserDefinedConstructorInstantiator instantiator = new UserDefinedConstructorInstantiator(clazz,
189+
constructorParameters);
190+
return create(clazz, instantiator);
191+
}
192+
193+
/**
194+
* Indicates, that class should be constructed using given instantiator.
195+
*
196+
* @param instantiator instantiator which will create instance of given class
197+
* @return itself
198+
* @see ConstructorParameters
199+
*/
200+
public AbstractAssertion create(final AbstractObjectInstantiator instantiator) {
201+
checkNotNull("clazz", instantiator.getClazz());
202+
checkNotNull("instantiator", instantiator);
203+
204+
return create(instantiator.getClazz(), instantiator::instantiate);
205+
}
206+
207+
/**
208+
* Indicates, that class should be constructed using given instantiator.
209+
*
210+
* @param clazz class to instantiate
211+
* @param instantiator instantiator which will create instance of given class
212+
* @return itself
213+
* @see ConstructorParameters
214+
*/
215+
public AbstractAssertion create(final Class<?> clazz, final AbstractObjectInstantiator instantiator) {
216+
checkNotNull("clazz", clazz);
217+
checkNotNull("instantiator", instantiator);
218+
219+
return create(clazz, instantiator::instantiate);
220+
}
221+
222+
/**
223+
* Indicates, that class should be constructed using given supplier.
224+
*
225+
* @param clazz class to instantiate
226+
* @param supplier supplier that will create given class
227+
* @return itself
228+
* @see ConstructorParameters
229+
*/
230+
public AbstractAssertion create(final Class<?> clazz, final Supplier<?> supplier) {
231+
checkNotNull("clazz", clazz);
232+
checkNotNull("supplier", supplier);
233+
234+
this.instantiators.add(new SupplierInstantiator(clazz, supplier));
206235
return this;
207236
}
208237

238+
/**
239+
* Performs specified tests on classes using declared field value changer.
240+
*
241+
* @see Method
242+
* @see AbstractFieldValueChanger
243+
*/
244+
public void areWellImplemented() {
245+
if (testers.isEmpty()) {
246+
testers = DEFAULT_TESTERS;
247+
}
248+
if (abstractFieldValueChanger != null) {
249+
testers.forEach(tester -> tester.setFieldValuesChanger(abstractFieldValueChanger));
250+
}
251+
252+
testers.forEach(tester -> tester.setPermutator(permutator));
253+
testers.forEach(tester -> tester.setUserDefinedInstantiators(instantiators));
254+
255+
runAssertions();
256+
}
257+
209258
protected abstract void runAssertions();
210259

211260
protected void logTestersAndClasses(final Logger logger,
@@ -218,6 +267,7 @@ protected void logTestersAndClasses(final Logger logger,
218267
logger.debug("Running {} testers on {} classes", testers.size(), classAndFieldPredicatePairs.length);
219268
logger.debug("Testers: {}", testers);
220269
logger.debug("Classes: {}", classes);
270+
logger.debug("Predefined instantiators: {}", instantiators);
221271
}
222272
}
223273
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package pl.pojo.tester.internal.instantiator;
2+
3+
import pl.pojo.tester.api.AbstractObjectInstantiator;
4+
5+
abstract class AbstractInternalInstantiator extends AbstractObjectInstantiator {
6+
7+
AbstractInternalInstantiator(final Class<?> clazz) {
8+
super(clazz);
9+
}
10+
11+
abstract boolean canInstantiate();
12+
13+
@Override
14+
public String toString() {
15+
return "AbstractInternalInstantiator{clazz=" + clazz + '}';
16+
}
17+
}

src/main/java/pl/pojo/tester/internal/instantiator/AbstractMultiConstructorInstantiator.java

+20-67
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,35 @@
11
package pl.pojo.tester.internal.instantiator;
22

33

4-
import org.apache.commons.collections4.MultiValuedMap;
54
import org.slf4j.Logger;
65
import org.slf4j.LoggerFactory;
7-
import pl.pojo.tester.api.ConstructorParameters;
8-
import pl.pojo.tester.internal.utils.CollectionUtils;
6+
import pl.pojo.tester.api.AbstractObjectInstantiator;
97

108
import java.lang.reflect.Constructor;
11-
import java.lang.reflect.Modifier;
129
import java.util.Arrays;
13-
import java.util.Collection;
10+
import java.util.List;
1411
import java.util.Objects;
15-
import java.util.stream.Stream;
12+
import java.util.Optional;
1613

1714

18-
abstract class AbstractMultiConstructorInstantiator extends AbstractObjectInstantiator {
15+
abstract class AbstractMultiConstructorInstantiator extends AbstractInternalInstantiator {
1916

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

22-
AbstractMultiConstructorInstantiator(final Class<?> clazz,
23-
final MultiValuedMap<Class<?>, ConstructorParameters> constructorParameters) {
24-
super(clazz, constructorParameters);
20+
AbstractMultiConstructorInstantiator(final Class<?> clazz, final List<AbstractObjectInstantiator> instantiators) {
21+
super(clazz);
22+
this.instantiators = instantiators;
2523
}
2624

27-
protected Object instantiateUsingUserParameters() {
28-
final Collection<ConstructorParameters> userConstructorParameters = constructorParameters.get(clazz);
29-
if (userDefinedOwnParametersForThisClass(userConstructorParameters)) {
30-
final Object result = tryToInstantiateUsing(userConstructorParameters);
31-
if (result != null) {
32-
return result;
33-
}
34-
LOGGER.warn("Could not instantiate class {} with user defined parameters. "
35-
+ "Trying create instance finding best constructor", clazz);
36-
}
37-
return null;
38-
}
39-
40-
private boolean userDefinedOwnParametersForThisClass(final Collection<ConstructorParameters> userConstructorParameters) {
41-
return CollectionUtils.isNotEmpty(userConstructorParameters);
42-
}
25+
protected Object createFindingBestConstructor() {
26+
final Optional<AbstractObjectInstantiator> instantiator = findFirstMatchingInPredefined();
4327

44-
private Object tryToInstantiateUsing(final Collection<ConstructorParameters> userConstructorParameters) {
45-
for (final ConstructorParameters param : userConstructorParameters) {
46-
Class<?>[] parameterTypes = param.getParametersTypes();
47-
try {
48-
Object[] parameters = param.getParameters();
49-
if (isInnerClass()) {
50-
parameterTypes = putEnclosingClassAsFirstParameterType(clazz.getEnclosingClass(), parameterTypes);
51-
final Object enclosingClassInstance = instantiateEnclosingClass();
52-
parameters = putEnclosingClassInstanceAsFirstParameter(enclosingClassInstance, parameters);
53-
}
54-
return createObjectFromArgsConstructor(parameterTypes, parameters);
55-
} catch (final ObjectInstantiationException e) {
56-
LOGGER.debug("ObjectInstantiationException:", e);
57-
// ignore, try all user defined constructor parameters and types
58-
}
28+
if (instantiator.isPresent()) {
29+
return instantiator.get()
30+
.instantiate();
5931
}
60-
return null;
61-
}
6232

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

41+
private Optional<AbstractObjectInstantiator> findFirstMatchingInPredefined() {
42+
return instantiators.stream()
43+
.filter(eachInstantiator -> clazz.equals(eachInstantiator.getClazz()))
44+
.findFirst();
45+
}
46+
7247
protected abstract Object createObjectFromArgsConstructor(final Class<?>[] parameterTypes, Object[] parameters);
7348

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

7651
protected abstract ObjectInstantiationException createObjectInstantiationException();
7752

78-
private Object instantiateEnclosingClass() {
79-
final Class<?> enclosingClass = clazz.getEnclosingClass();
80-
return Instantiable.forClass(enclosingClass, constructorParameters)
81-
.instantiate();
82-
}
83-
84-
private Class[] putEnclosingClassAsFirstParameterType(final Class<?> enclosingClass,
85-
final Class<?>[] constructorParametersTypes) {
86-
return Stream.concat(Stream.of(enclosingClass), Arrays.stream(constructorParametersTypes))
87-
.toArray(Class[]::new);
88-
}
89-
90-
private boolean isInnerClass() {
91-
return clazz.getEnclosingClass() != null && !Modifier.isStatic(clazz.getModifiers());
92-
}
93-
94-
private Object[] putEnclosingClassInstanceAsFirstParameter(final Object enclosingClassInstance,
95-
final Object[] arguments) {
96-
return Stream.concat(Stream.of(enclosingClassInstance), Arrays.stream(arguments))
97-
.toArray(Object[]::new);
98-
}
99-
10053
private Object createObjectFromConstructor(final Constructor<?> constructor) {
10154
makeAccessible(constructor);
10255
if (constructor.getParameterCount() == 0) {
10356
return createObjectFromNoArgsConstructor(constructor);
10457
} else {
10558
try {
10659
final Object[] parameters = Instantiable.instantiateClasses(constructor.getParameterTypes(),
107-
constructorParameters);
60+
instantiators);
10861
return createObjectFromArgsConstructor(constructor.getParameterTypes(), parameters);
10962
} catch (final Exception e) {
11063
LOGGER.debug("Exception:", e);

src/main/java/pl/pojo/tester/internal/instantiator/AbstractObjectInstantiator.java

-20
This file was deleted.

0 commit comments

Comments
 (0)