Skip to content

Commit 45614a3

Browse files
committed
feat: support native abstract classes
1 parent 17f85cd commit 45614a3

17 files changed

+342
-1
lines changed

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
5151
return new MapSpecimen<>(type, context, this);
5252
}
5353

54-
if (type.isParameterized() && !type.isInterface()) {
54+
if (type.isParameterized() && !type.isInterface() && !type.isAbstract()) {
5555
return new GenericSpecimen<>(type, context, this);
5656
}
5757

@@ -63,6 +63,14 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
6363
return new GenericSpecimen<>(type, context, this);
6464
}
6565

66+
if (type.isParameterized() && type.isAbstract()) {
67+
if (context.getConfiguration().experimentalInterfaces() && type.isAbstract()) {
68+
return subClassOrProxy(type);
69+
}
70+
71+
return new GenericSpecimen<>(type, context, this);
72+
}
73+
6674
if (type.isArray()) {
6775
return new ArraySpecimen<>(type, context, this);
6876
}
@@ -80,6 +88,9 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
8088
}
8189

8290
if (type.isAbstract()) {
91+
if (context.getConfiguration().experimentalInterfaces()) {
92+
return subClassOrProxy(type);
93+
}
8394
return new AbstractSpecimen<>(type, context, this);
8495
}
8596

@@ -115,6 +126,32 @@ private <T> ISpecimen<T> implementationOrProxy(final SpecimenType<T> interfaceTy
115126
}
116127
}
117128

129+
private <T> ISpecimen<T> subClassOrProxy(final SpecimenType<T> abstractType) {
130+
try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {
131+
var subClasses = scanResult.getSubclasses(abstractType.asClass()).stream()
132+
.filter(x -> !x.isAbstract())
133+
.filter(x -> isNotParametrized(x) || abstractType.isParameterized())
134+
.filter(x -> isNotParametrized(x) || typeParametersMatch(x, abstractType))
135+
.collect(Collectors.toList());
136+
137+
if (subClasses.isEmpty()) {
138+
return new AbstractSpecimen<>(abstractType, context, this);
139+
}
140+
141+
var implementingClass = subClasses.get(new Random().nextInt(subClasses.size()));
142+
if (isNotParametrized(implementingClass)) {
143+
return new ObjectSpecimen<>(SpecimenType.fromClass(implementingClass.loadClass()), context, this);
144+
}
145+
146+
return new GenericSpecimen<>(
147+
SpecimenType.fromRawType(implementingClass.loadClass(), resolveTypeArguments(abstractType, implementingClass)),
148+
context,
149+
this);
150+
} catch (Exception ex) {
151+
return new AbstractSpecimen<>(abstractType, context, this);
152+
}
153+
}
154+
118155
private static boolean isNotParametrized(ClassInfo classInfo) {
119156
return classInfo.getTypeSignature() == null || classInfo.getTypeSignature().getTypeParameters().isEmpty();
120157
}

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

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.github.nylle.javafixture.annotations.testcases.TestCase;
44
import com.github.nylle.javafixture.annotations.testcases.TestWithCases;
5+
import com.github.nylle.javafixture.specimen.AbstractSpecimen;
56
import com.github.nylle.javafixture.specimen.ArraySpecimen;
67
import com.github.nylle.javafixture.specimen.CollectionSpecimen;
78
import com.github.nylle.javafixture.specimen.EnumSpecimen;
@@ -16,6 +17,14 @@
1617
import com.github.nylle.javafixture.testobjects.TestEnum;
1718
import com.github.nylle.javafixture.testobjects.TestObjectGeneric;
1819
import com.github.nylle.javafixture.testobjects.TestPrimitive;
20+
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithAbstractImplementation;
21+
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithGenericImplementation;
22+
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithImplementation;
23+
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithoutImplementation;
24+
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassTUWithGenericImplementationT;
25+
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassTUWithGenericImplementationTU;
26+
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassTUWithGenericImplementationU;
27+
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassWithImplementation;
1928
import com.github.nylle.javafixture.testobjects.example.IContract;
2029
import com.github.nylle.javafixture.testobjects.interfaces.GenericInterfaceTUWithGenericImplementationT;
2130
import com.github.nylle.javafixture.testobjects.interfaces.GenericInterfaceTUWithGenericImplementationTU;
@@ -179,4 +188,92 @@ void ifGenericImplementationOnlyUsesSecondTypeArgumentOfGenericInterface() {
179188
}
180189
}
181190
}
191+
192+
@Nested
193+
@DisplayName("For abstract classes")
194+
class AbstractClasses {
195+
196+
Context context = new Context(Configuration.configure().experimentalInterfaces(true));
197+
198+
@TestWithCases
199+
@TestCase(bool1 = true, class2 = ObjectSpecimen.class)
200+
@TestCase(bool1 = false, class2 = AbstractSpecimen.class)
201+
@DisplayName("only scans for sub-classes if experimentalInterfaces is")
202+
void abstractImplementationsAreOnlySupportedIfExperimentalInterfacesAreEnabled(boolean experimental, Class<?> expected) {
203+
var context = new Context(Configuration.configure().experimentalInterfaces(experimental));
204+
205+
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithImplementation.class))).isExactlyInstanceOf(expected);
206+
}
207+
208+
@Nested
209+
@DisplayName("creates AbstractSpecimen if")
210+
class CreatesAbstractSpecimen {
211+
212+
@Test
213+
@DisplayName("no subclasses found")
214+
void createsAbstractSpecimenIfNoSubclassFound() {
215+
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithoutImplementation.class)))
216+
.isExactlyInstanceOf(AbstractSpecimen.class);
217+
}
218+
219+
@Test
220+
@DisplayName("only abstract subclasses found")
221+
void createsAbstractSpecimenIfOnlyAbstractSubclassesFound() {
222+
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithAbstractImplementation.class)))
223+
.isExactlyInstanceOf(AbstractSpecimen.class);
224+
}
225+
226+
@Test
227+
@DisplayName("subclass is generic and superclass is not")
228+
void createsAbstractSpecimenIfSubclassIsGenericAndSuperclassIsNot() {
229+
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithGenericImplementation.class)))
230+
.isExactlyInstanceOf(AbstractSpecimen.class);
231+
}
232+
}
233+
234+
@Nested
235+
@DisplayName("creates ObjectSpecimen if")
236+
class CreatesObjectSpecimen {
237+
238+
@Test
239+
@DisplayName("subclass is not generic and superclass is not generic")
240+
void createsObjectSpecimenIfBothSubclassAndSuperclassAreNotGeneric() {
241+
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithImplementation.class)))
242+
.isExactlyInstanceOf(ObjectSpecimen.class);
243+
}
244+
245+
@Test
246+
@DisplayName("subclass is not generic and superclass is generic")
247+
void createsObjectSpecimenIfSubclassIsNotGenericAndSuperclassIs() {
248+
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassWithImplementation<Integer, String>>() {}))
249+
.isExactlyInstanceOf(ObjectSpecimen.class);
250+
}
251+
}
252+
253+
@Nested
254+
@DisplayName("creates GenericSpecimen if")
255+
class CreatesGenericSpecimen {
256+
257+
@Test
258+
@DisplayName("subclass is generic and superclass is generic")
259+
void createsGenericSpecimenIfSubclassAndSuperclassAreGeneric() {
260+
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassTUWithGenericImplementationTU<String, Integer>>() {}))
261+
.isExactlyInstanceOf(GenericSpecimen.class);
262+
}
263+
264+
@Test
265+
@DisplayName("generic subclass only uses first type-argument of generic superclass")
266+
void createsGenericSpecimenIfGenericSubclassOnlyUsesFirstTypeArgumentOfGenericSuperclass() {
267+
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassTUWithGenericImplementationT<String, Integer>>() {}))
268+
.isExactlyInstanceOf(GenericSpecimen.class);
269+
}
270+
271+
@Test
272+
@DisplayName("generic subclass only uses second type-argument of generic superclass")
273+
void createsGenericSpecimenIfGenericSubclassOnlyUsesSecondTypeArgumentOfGenericSuperclass() {
274+
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassTUWithGenericImplementationU<String, Integer>>() {}))
275+
.isExactlyInstanceOf(GenericSpecimen.class);
276+
}
277+
}
278+
}
182279
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
import com.github.nylle.javafixture.testobjects.TestObject;
4+
5+
public abstract class AbstractClassWithAbstractImplementation {
6+
int publicField = 1;
7+
8+
abstract TestObject getTestObject();
9+
10+
abstract void setTestDto(TestObject value);
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
import com.github.nylle.javafixture.testobjects.TestObject;
4+
5+
public abstract class AbstractClassWithAbstractImplementationImpl extends AbstractClassWithAbstractImplementation {
6+
7+
abstract TestObject getTestObject();
8+
9+
abstract void setTestDto(TestObject value);
10+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
public abstract class AbstractClassWithGenericImplementation {
4+
5+
abstract String getString();
6+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
public class AbstractClassWithGenericImplementationImpl<T> extends AbstractClassWithGenericImplementation {
4+
private T t;
5+
private String string;
6+
7+
@Override
8+
public String getString() {
9+
return string;
10+
}
11+
12+
public T getT() {
13+
return t;
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
import com.github.nylle.javafixture.testobjects.TestObject;
4+
5+
public abstract class AbstractClassWithImplementation {
6+
int publicField = 1;
7+
8+
abstract TestObject getTestObject();
9+
10+
abstract void setTestDto(TestObject value);
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
import com.github.nylle.javafixture.testobjects.TestObject;
4+
5+
public class AbstractClassWithImplementationImpl extends AbstractClassWithImplementation {
6+
@Override
7+
public TestObject getTestObject() {
8+
return null;
9+
}
10+
11+
@Override
12+
public void setTestDto(TestObject value) {
13+
14+
}
15+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
import com.github.nylle.javafixture.testobjects.TestObject;
4+
5+
public abstract class AbstractClassWithoutImplementation {
6+
int publicField = 1;
7+
8+
abstract TestObject getTestObject();
9+
10+
abstract void setTestDto(TestObject value);
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.github.nylle.javafixture.testobjects.abstractclasses;
2+
3+
public abstract class GenericAbstractClassTUWithGenericImplementationT<T, U> {
4+
int publicField = 1;
5+
6+
abstract T getT();
7+
8+
abstract void setT(T value);
9+
10+
abstract U getU();
11+
}

0 commit comments

Comments
 (0)