Skip to content

Commit 0acb2e3

Browse files
authored
Merge pull request #51 from Nylle/JAVAFIXTURE-38_absract-class-creation
JAVAFIXTURE-38: Abstract class creation
2 parents 671605a + 8301f2b commit 0acb2e3

8 files changed

Lines changed: 235 additions & 32 deletions

File tree

src/main/java/com/github/nylle/javafixture/InstanceFactory.java

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import javassist.util.proxy.ProxyFactory;
88

99
import java.lang.reflect.Constructor;
10+
import java.lang.reflect.Method;
1011
import java.lang.reflect.Modifier;
1112
import java.lang.reflect.Proxy;
1213
import java.util.HashMap;
@@ -28,26 +29,32 @@ public InstanceFactory(SpecimenFactory specimenFactory) {
2829
}
2930

3031
public <T> T construct(final SpecimenType<T> type) {
31-
var constructors = stream(type.asClass().getConstructors())
32+
var constructors = type.getDeclaredConstructors()
33+
.stream()
3234
.filter(x -> Modifier.isPublic(x.getModifiers()))
3335
.collect(toList());
3436

3537
if (constructors.isEmpty()) {
3638
throw new SpecimenException(format("Cannot construct %s: no public constructor found", type.asClass()));
3739
}
3840

39-
Constructor<?> constructor = constructors.get(random.nextInt(constructors.size()));
41+
return construct(type, constructors.get(random.nextInt(constructors.size())));
42+
}
4043

41-
try {
42-
return (T) constructor.newInstance(
43-
stream(constructor.getGenericParameterTypes())
44-
.map(t -> specimenFactory.build(SpecimenType.fromClass(t)))
45-
.map(s -> s.create())
46-
.toArray(size -> new Object[size]));
47-
} catch (Exception e) {
48-
throw new SpecimenException(format("Unable to construct class %s with constructor %s: %s",
49-
type.asClass(), constructor.toString(), e.getMessage()), e);
44+
public <T> T manufacture(final SpecimenType<T> type) {
45+
var results = type.getFactoryMethods()
46+
.stream()
47+
.filter(x -> Modifier.isPublic(x.getModifiers()))
48+
.map(x -> manufactureOrNull(x))
49+
.filter(x -> x != null)
50+
.map(x -> (T) x)
51+
.collect(toList());
52+
53+
if (results.isEmpty()) {
54+
throw new SpecimenException(format("Cannot manufacture %s", type.asClass()));
5055
}
56+
57+
return results.get(random.nextInt(results.size()));
5158
}
5259

5360
public <T> T instantiate(final SpecimenType<T> type) {
@@ -73,6 +80,18 @@ public <T> Object proxy(final SpecimenType<T> type, final Map<String, ISpecimen<
7380
return createProxyForAbstract(type, specimens);
7481
}
7582

83+
private <T> T construct(final SpecimenType<T> type, final Constructor<?> constructor) {
84+
try {
85+
constructor.setAccessible(true);
86+
return (T) constructor.newInstance(stream(constructor.getGenericParameterTypes())
87+
.map(t -> specimenFactory.build(SpecimenType.fromClass(t)))
88+
.map(s -> s.create())
89+
.toArray());
90+
} catch (Exception e) {
91+
throw new SpecimenException(format("Unable to construct class %s with constructor %s: %s", type.asClass(), constructor.toString(), e.getMessage()), e);
92+
}
93+
}
94+
7695
private <T> T createProxyForAbstract(final SpecimenType<T> type, final Map<String, ISpecimen<?>> specimens) {
7796
try {
7897
var factory = new ProxyFactory();
@@ -82,4 +101,15 @@ private <T> T createProxyForAbstract(final SpecimenType<T> type, final Map<Strin
82101
throw new SpecimenException(format("Unable to construct abstract class %s: %s", type.asClass(), e.getMessage()), e);
83102
}
84103
}
104+
105+
private <T> T manufactureOrNull(final Method method) {
106+
try {
107+
return (T) method.invoke(stream(method.getGenericParameterTypes())
108+
.map(t -> specimenFactory.build(SpecimenType.fromClass(t)))
109+
.map(s -> s.create())
110+
.toArray());
111+
} catch (Exception ex) {
112+
return null;
113+
}
114+
}
85115
}

src/main/java/com/github/nylle/javafixture/SpecimenFactory.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.nylle.javafixture;
22

3+
import com.github.nylle.javafixture.specimen.AbstractSpecimen;
34
import com.github.nylle.javafixture.specimen.ArraySpecimen;
45
import com.github.nylle.javafixture.specimen.CollectionSpecimen;
56
import com.github.nylle.javafixture.specimen.EnumSpecimen;
@@ -48,10 +49,14 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
4849
return new TimeSpecimen<>(type, context);
4950
}
5051

51-
if (type.isAbstract()) {
52+
if (type.isInterface()) {
5253
return new InterfaceSpecimen<>(type, context, this);
5354
}
5455

56+
if (type.isAbstract()) {
57+
return new AbstractSpecimen<>(type, context, this);
58+
}
59+
5560
return new ObjectSpecimen<>(type, context, this);
5661
}
5762
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.github.nylle.javafixture.specimen;
2+
3+
import com.github.nylle.javafixture.Context;
4+
import com.github.nylle.javafixture.CustomizationContext;
5+
import com.github.nylle.javafixture.ISpecimen;
6+
import com.github.nylle.javafixture.InstanceFactory;
7+
import com.github.nylle.javafixture.SpecimenException;
8+
import com.github.nylle.javafixture.SpecimenFactory;
9+
import com.github.nylle.javafixture.SpecimenType;
10+
11+
import static com.github.nylle.javafixture.CustomizationContext.noContext;
12+
13+
public class AbstractSpecimen<T> implements ISpecimen<T> {
14+
15+
private final SpecimenType<T> type;
16+
private final Context context;
17+
private final InstanceFactory instanceFactory;
18+
19+
public AbstractSpecimen(final SpecimenType<T> type, final Context context, final SpecimenFactory specimenFactory) {
20+
21+
if (type == null) {
22+
throw new IllegalArgumentException("type: null");
23+
}
24+
25+
if (context == null) {
26+
throw new IllegalArgumentException("context: null");
27+
}
28+
29+
if (specimenFactory == null) {
30+
throw new IllegalArgumentException("specimenFactory: null");
31+
}
32+
33+
if (!type.isAbstract() || type.isMap() || type.isCollection()) {
34+
throw new IllegalArgumentException("type: " + type.getName());
35+
}
36+
37+
this.type = type;
38+
this.context = context;
39+
this.instanceFactory = new InstanceFactory(specimenFactory);
40+
}
41+
42+
@Override
43+
public T create() {
44+
return create(noContext());
45+
}
46+
47+
@Override
48+
public T create(final CustomizationContext customizationContext) {
49+
if (context.isCached(type)) {
50+
return (T) context.cached(type);
51+
}
52+
53+
try {
54+
return (T) context.cached(type, instanceFactory.proxy(type));
55+
} catch(SpecimenException ignored) {
56+
return (T) context.cached(type, instanceFactory.manufacture(type));
57+
}
58+
}
59+
}
60+

src/main/java/com/github/nylle/javafixture/specimen/InterfaceSpecimen.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public InterfaceSpecimen(final SpecimenType<T> type, final Context context, fina
2929
throw new IllegalArgumentException("specimenFactory: null");
3030
}
3131

32-
if (!type.isAbstract() || type.isMap() || type.isCollection()) {
32+
if (!type.isInterface() || type.isMap() || type.isCollection()) {
3333
throw new IllegalArgumentException("type: " + type.getName());
3434
}
3535

src/test/java/com/github/nylle/javafixture/InstanceFactoryTest.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.github.nylle.javafixture;
22

33
import com.github.nylle.javafixture.testobjects.TestObjectWithGenericConstructor;
4+
import com.github.nylle.javafixture.testobjects.TestObjectWithNonPublicFactoryMethods;
45
import com.github.nylle.javafixture.testobjects.TestObjectWithPrivateConstructor;
56
import org.junit.jupiter.api.Test;
67

8+
import java.nio.charset.Charset;
79
import java.util.Optional;
810

911
import static com.github.nylle.javafixture.SpecimenType.fromClass;
@@ -14,7 +16,6 @@ class InstanceFactoryTest {
1416

1517
@Test
1618
void canCreateInstanceFromConstructor() {
17-
1819
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));
1920

2021
TestObjectWithGenericConstructor result = sut.construct(fromClass(TestObjectWithGenericConstructor.class));
@@ -39,12 +40,30 @@ void fieldsNotSetByConstructorAreNull() {
3940

4041
@Test
4142
void canOnlyUsePublicConstructor() {
42-
4343
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));
4444

4545
assertThatExceptionOfType(SpecimenException.class)
4646
.isThrownBy(() -> sut.construct(fromClass(TestObjectWithPrivateConstructor.class)))
4747
.withMessageContaining("no public constructor found")
4848
.withNoCause();
4949
}
50+
51+
@Test
52+
void canCreateInstanceFromAbstractClassUsingFactoryMethod() {
53+
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));
54+
55+
var actual = sut.manufacture(new SpecimenType<Charset>() {});
56+
57+
assertThat(actual).isInstanceOf(Charset.class);
58+
}
59+
60+
@Test
61+
void canOnlyUsePublicFactoryMethods() {
62+
var sut = new InstanceFactory(new SpecimenFactory(new Context(Configuration.configure())));
63+
64+
assertThatExceptionOfType(SpecimenException.class)
65+
.isThrownBy(() -> sut.manufacture(fromClass(TestObjectWithNonPublicFactoryMethods.class)))
66+
.withMessageContaining("Cannot manufacture class")
67+
.withNoCause();
68+
}
5069
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package com.github.nylle.javafixture.specimen;
2+
3+
import com.github.nylle.javafixture.Configuration;
4+
import com.github.nylle.javafixture.Context;
5+
import com.github.nylle.javafixture.SpecimenFactory;
6+
import com.github.nylle.javafixture.SpecimenType;
7+
import com.github.nylle.javafixture.testobjects.TestAbstractClass;
8+
import com.github.nylle.javafixture.testobjects.TestInterface;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.nio.charset.Charset;
13+
import java.util.Map;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
17+
18+
class AbstractSpecimenTest {
19+
20+
private SpecimenFactory specimenFactory;
21+
private Context context;
22+
23+
@BeforeEach
24+
void setup() {
25+
context = new Context(new Configuration(2, 2, 3));
26+
specimenFactory = new SpecimenFactory(context);
27+
}
28+
29+
@Test
30+
void onlyAbstractTypes() {
31+
assertThatThrownBy(() -> new AbstractSpecimen<>(SpecimenType.fromClass(Map.class), context, specimenFactory))
32+
.isInstanceOf(IllegalArgumentException.class)
33+
.hasMessageContaining("type: " + Map.class.getName());
34+
}
35+
36+
@Test
37+
void typeIsRequired() {
38+
assertThatThrownBy(() -> new AbstractSpecimen<>((SpecimenType) null, context, specimenFactory))
39+
.isInstanceOf(IllegalArgumentException.class)
40+
.hasMessageContaining("type: null");
41+
}
42+
43+
@Test
44+
void contextIsRequired() {
45+
assertThatThrownBy(() -> new AbstractSpecimen<>(SpecimenType.fromClass(TestInterface.class), null, specimenFactory))
46+
.isInstanceOf(IllegalArgumentException.class)
47+
.hasMessageContaining("context: null");
48+
}
49+
50+
@Test
51+
void specimenFactoryIsRequired() {
52+
assertThatThrownBy(() -> new AbstractSpecimen<>(SpecimenType.fromClass(TestInterface.class), context, null))
53+
.isInstanceOf(IllegalArgumentException.class)
54+
.hasMessageContaining("specimenFactory: null");
55+
}
56+
57+
@Test
58+
void createAbstractClass() {
59+
var sut = new AbstractSpecimen<TestAbstractClass>(SpecimenType.fromClass(TestAbstractClass.class), context, specimenFactory);
60+
61+
var actual = sut.create();
62+
63+
assertThat(actual).isInstanceOf(TestAbstractClass.class);
64+
assertThat(actual.getString()).isNotBlank();
65+
assertThat(actual.abstractMethod()).isNotBlank();
66+
}
67+
68+
@Test
69+
void createAbstractClassWithoutConstructor() {
70+
var sut = new AbstractSpecimen<Charset>(SpecimenType.fromClass(Charset.class), context, specimenFactory);
71+
72+
var actual = sut.create();
73+
74+
assertThat(actual).isInstanceOf(Charset.class);
75+
}
76+
77+
@Test
78+
void resultIsCached() {
79+
80+
var original = new AbstractSpecimen<TestAbstractClass>(SpecimenType.fromClass(TestAbstractClass.class), context, specimenFactory).create();
81+
var cached = new AbstractSpecimen<TestAbstractClass>(SpecimenType.fromClass(TestAbstractClass.class), context, specimenFactory).create();
82+
83+
assertThat(original).isInstanceOf(TestAbstractClass.class);
84+
assertThat(original).isSameAs(cached);
85+
assertThat(original.toString()).isEqualTo(cached.toString());
86+
assertThat(original.getString()).isSameAs(cached.getString());
87+
}
88+
}
89+

src/test/java/com/github/nylle/javafixture/specimen/InterfaceSpecimenTest.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
import com.github.nylle.javafixture.Context;
55
import com.github.nylle.javafixture.SpecimenFactory;
66
import com.github.nylle.javafixture.SpecimenType;
7-
import com.github.nylle.javafixture.testobjects.TestAbstractClass;
87
import com.github.nylle.javafixture.testobjects.TestInterface;
98
import com.github.nylle.javafixture.testobjects.TestObject;
109
import org.junit.jupiter.api.BeforeEach;
1110
import org.junit.jupiter.api.Test;
1211

13-
import java.util.Map;
12+
import java.nio.charset.Charset;
1413

1514
import static org.assertj.core.api.Assertions.assertThat;
1615
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -27,10 +26,10 @@ void setup() {
2726
}
2827

2928
@Test
30-
void onlyAbstractTypes() {
31-
assertThatThrownBy(() -> new InterfaceSpecimen<>(SpecimenType.fromClass(Map.class), context, specimenFactory))
29+
void onlyInterfaceTypes() {
30+
assertThatThrownBy(() -> new InterfaceSpecimen<>(SpecimenType.fromClass(Charset.class), context, specimenFactory))
3231
.isInstanceOf(IllegalArgumentException.class)
33-
.hasMessageContaining("type: " + Map.class.getName());
32+
.hasMessageContaining("type: " + Charset.class.getName());
3433
}
3534

3635
@Test
@@ -68,18 +67,6 @@ void createInterface() {
6867
assertThat(actual.getTestObject()).isInstanceOf(TestObject.class);
6968
}
7069

71-
@Test
72-
void createAbstractClass() {
73-
var sut = new InterfaceSpecimen<TestAbstractClass>(SpecimenType.fromClass(TestAbstractClass.class), context, specimenFactory);
74-
75-
var actual = sut.create();
76-
77-
assertThat(actual).isInstanceOf(TestAbstractClass.class);
78-
assertThat(actual.getString()).isNotBlank();
79-
assertThat(actual.abstractMethod()).isNotBlank();
80-
}
81-
82-
8370
@Test
8471
void resultIsCached() {
8572

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.github.nylle.javafixture.testobjects;
2+
3+
public class TestObjectWithNonPublicFactoryMethods {
4+
private String value;
5+
6+
protected static TestObjectWithNonPublicFactoryMethods privateStaticMethodReturningABoolean() {
7+
return new TestObjectWithNonPublicFactoryMethods();
8+
}
9+
10+
private static TestObjectWithNonPublicFactoryMethods privateStaticFactoryMethod() {
11+
return new TestObjectWithNonPublicFactoryMethods();
12+
}
13+
}

0 commit comments

Comments
 (0)