Skip to content

Commit

Permalink
feat: support native abstract classes
Browse files Browse the repository at this point in the history
  • Loading branch information
Nylle committed Nov 18, 2023
1 parent 17f85cd commit 45614a3
Show file tree
Hide file tree
Showing 17 changed files with 342 additions and 1 deletion.
39 changes: 38 additions & 1 deletion src/main/java/com/github/nylle/javafixture/SpecimenFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
return new MapSpecimen<>(type, context, this);
}

if (type.isParameterized() && !type.isInterface()) {
if (type.isParameterized() && !type.isInterface() && !type.isAbstract()) {
return new GenericSpecimen<>(type, context, this);
}

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

if (type.isParameterized() && type.isAbstract()) {
if (context.getConfiguration().experimentalInterfaces() && type.isAbstract()) {
return subClassOrProxy(type);
}

return new GenericSpecimen<>(type, context, this);
}

if (type.isArray()) {
return new ArraySpecimen<>(type, context, this);
}
Expand All @@ -80,6 +88,9 @@ public <T> ISpecimen<T> build(final SpecimenType<T> type) {
}

if (type.isAbstract()) {
if (context.getConfiguration().experimentalInterfaces()) {
return subClassOrProxy(type);
}
return new AbstractSpecimen<>(type, context, this);
}

Expand Down Expand Up @@ -115,6 +126,32 @@ private <T> ISpecimen<T> implementationOrProxy(final SpecimenType<T> interfaceTy
}
}

private <T> ISpecimen<T> subClassOrProxy(final SpecimenType<T> abstractType) {
try (ScanResult scanResult = new ClassGraph().enableAllInfo().scan()) {
var subClasses = scanResult.getSubclasses(abstractType.asClass()).stream()
.filter(x -> !x.isAbstract())
.filter(x -> isNotParametrized(x) || abstractType.isParameterized())
.filter(x -> isNotParametrized(x) || typeParametersMatch(x, abstractType))
.collect(Collectors.toList());

if (subClasses.isEmpty()) {
return new AbstractSpecimen<>(abstractType, context, this);
}

var implementingClass = subClasses.get(new Random().nextInt(subClasses.size()));
if (isNotParametrized(implementingClass)) {
return new ObjectSpecimen<>(SpecimenType.fromClass(implementingClass.loadClass()), context, this);
}

return new GenericSpecimen<>(
SpecimenType.fromRawType(implementingClass.loadClass(), resolveTypeArguments(abstractType, implementingClass)),
context,
this);
} catch (Exception ex) {
return new AbstractSpecimen<>(abstractType, context, this);
}
}

private static boolean isNotParametrized(ClassInfo classInfo) {
return classInfo.getTypeSignature() == null || classInfo.getTypeSignature().getTypeParameters().isEmpty();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.github.nylle.javafixture.annotations.testcases.TestCase;
import com.github.nylle.javafixture.annotations.testcases.TestWithCases;
import com.github.nylle.javafixture.specimen.AbstractSpecimen;
import com.github.nylle.javafixture.specimen.ArraySpecimen;
import com.github.nylle.javafixture.specimen.CollectionSpecimen;
import com.github.nylle.javafixture.specimen.EnumSpecimen;
Expand All @@ -16,6 +17,14 @@
import com.github.nylle.javafixture.testobjects.TestEnum;
import com.github.nylle.javafixture.testobjects.TestObjectGeneric;
import com.github.nylle.javafixture.testobjects.TestPrimitive;
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithAbstractImplementation;
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithGenericImplementation;
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithImplementation;
import com.github.nylle.javafixture.testobjects.abstractclasses.AbstractClassWithoutImplementation;
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassTUWithGenericImplementationT;
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassTUWithGenericImplementationTU;
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassTUWithGenericImplementationU;
import com.github.nylle.javafixture.testobjects.abstractclasses.GenericAbstractClassWithImplementation;
import com.github.nylle.javafixture.testobjects.example.IContract;
import com.github.nylle.javafixture.testobjects.interfaces.GenericInterfaceTUWithGenericImplementationT;
import com.github.nylle.javafixture.testobjects.interfaces.GenericInterfaceTUWithGenericImplementationTU;
Expand Down Expand Up @@ -179,4 +188,92 @@ void ifGenericImplementationOnlyUsesSecondTypeArgumentOfGenericInterface() {
}
}
}

@Nested
@DisplayName("For abstract classes")
class AbstractClasses {

Context context = new Context(Configuration.configure().experimentalInterfaces(true));

@TestWithCases
@TestCase(bool1 = true, class2 = ObjectSpecimen.class)
@TestCase(bool1 = false, class2 = AbstractSpecimen.class)
@DisplayName("only scans for sub-classes if experimentalInterfaces is")
void abstractImplementationsAreOnlySupportedIfExperimentalInterfacesAreEnabled(boolean experimental, Class<?> expected) {
var context = new Context(Configuration.configure().experimentalInterfaces(experimental));

assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithImplementation.class))).isExactlyInstanceOf(expected);
}

@Nested
@DisplayName("creates AbstractSpecimen if")
class CreatesAbstractSpecimen {

@Test
@DisplayName("no subclasses found")
void createsAbstractSpecimenIfNoSubclassFound() {
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithoutImplementation.class)))
.isExactlyInstanceOf(AbstractSpecimen.class);
}

@Test
@DisplayName("only abstract subclasses found")
void createsAbstractSpecimenIfOnlyAbstractSubclassesFound() {
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithAbstractImplementation.class)))
.isExactlyInstanceOf(AbstractSpecimen.class);
}

@Test
@DisplayName("subclass is generic and superclass is not")
void createsAbstractSpecimenIfSubclassIsGenericAndSuperclassIsNot() {
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithGenericImplementation.class)))
.isExactlyInstanceOf(AbstractSpecimen.class);
}
}

@Nested
@DisplayName("creates ObjectSpecimen if")
class CreatesObjectSpecimen {

@Test
@DisplayName("subclass is not generic and superclass is not generic")
void createsObjectSpecimenIfBothSubclassAndSuperclassAreNotGeneric() {
assertThat(new SpecimenFactory(context).build(SpecimenType.fromClass(AbstractClassWithImplementation.class)))
.isExactlyInstanceOf(ObjectSpecimen.class);
}

@Test
@DisplayName("subclass is not generic and superclass is generic")
void createsObjectSpecimenIfSubclassIsNotGenericAndSuperclassIs() {
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassWithImplementation<Integer, String>>() {}))
.isExactlyInstanceOf(ObjectSpecimen.class);
}
}

@Nested
@DisplayName("creates GenericSpecimen if")
class CreatesGenericSpecimen {

@Test
@DisplayName("subclass is generic and superclass is generic")
void createsGenericSpecimenIfSubclassAndSuperclassAreGeneric() {
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassTUWithGenericImplementationTU<String, Integer>>() {}))
.isExactlyInstanceOf(GenericSpecimen.class);
}

@Test
@DisplayName("generic subclass only uses first type-argument of generic superclass")
void createsGenericSpecimenIfGenericSubclassOnlyUsesFirstTypeArgumentOfGenericSuperclass() {
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassTUWithGenericImplementationT<String, Integer>>() {}))
.isExactlyInstanceOf(GenericSpecimen.class);
}

@Test
@DisplayName("generic subclass only uses second type-argument of generic superclass")
void createsGenericSpecimenIfGenericSubclassOnlyUsesSecondTypeArgumentOfGenericSuperclass() {
assertThat(new SpecimenFactory(context).build(new SpecimenType<GenericAbstractClassTUWithGenericImplementationU<String, Integer>>() {}))
.isExactlyInstanceOf(GenericSpecimen.class);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

import com.github.nylle.javafixture.testobjects.TestObject;

public abstract class AbstractClassWithAbstractImplementation {
int publicField = 1;

abstract TestObject getTestObject();

abstract void setTestDto(TestObject value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

import com.github.nylle.javafixture.testobjects.TestObject;

public abstract class AbstractClassWithAbstractImplementationImpl extends AbstractClassWithAbstractImplementation {

abstract TestObject getTestObject();

abstract void setTestDto(TestObject value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public abstract class AbstractClassWithGenericImplementation {

abstract String getString();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public class AbstractClassWithGenericImplementationImpl<T> extends AbstractClassWithGenericImplementation {
private T t;
private String string;

@Override
public String getString() {
return string;
}

public T getT() {
return t;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

import com.github.nylle.javafixture.testobjects.TestObject;

public abstract class AbstractClassWithImplementation {
int publicField = 1;

abstract TestObject getTestObject();

abstract void setTestDto(TestObject value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

import com.github.nylle.javafixture.testobjects.TestObject;

public class AbstractClassWithImplementationImpl extends AbstractClassWithImplementation {
@Override
public TestObject getTestObject() {
return null;
}

@Override
public void setTestDto(TestObject value) {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

import com.github.nylle.javafixture.testobjects.TestObject;

public abstract class AbstractClassWithoutImplementation {
int publicField = 1;

abstract TestObject getTestObject();

abstract void setTestDto(TestObject value);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public abstract class GenericAbstractClassTUWithGenericImplementationT<T, U> {
int publicField = 1;

abstract T getT();

abstract void setT(T value);

abstract U getU();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public class GenericAbstractClassTUWithGenericImplementationTImpl<T> extends GenericAbstractClassTUWithGenericImplementationT<T, String> {
private T t;
private String u;

@Override
public T getT() {
return t;
}

@Override
public void setT(T value) {
t = value;
}

@Override
public String getU() {
return u;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public abstract class GenericAbstractClassTUWithGenericImplementationTU<T, U> {
int publicField = 1;

abstract T getT();

abstract void setT(T value);

abstract U getU();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public class GenericAbstractClassTUWithGenericImplementationTUImpl<T, U> extends GenericAbstractClassTUWithGenericImplementationTU<T, U> {
private T t;
private U u;

@Override
public T getT() {
return t;
}

@Override
public void setT(T value) {
t = value;
}

@Override
public U getU() {
return u;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public abstract class GenericAbstractClassTUWithGenericImplementationU<T, U> {
int publicField = 1;

abstract T getT();

abstract void setT(T value);

abstract U getU();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public class GenericAbstractClassTUWithGenericImplementationUImpl<U> extends GenericAbstractClassTUWithGenericImplementationU<String, U> {
private String t;
private U u;

@Override
public String getT() {
return t;
}

@Override
public void setT(String value) {
t = value;
}

@Override
public U getU() {
return u;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.github.nylle.javafixture.testobjects.abstractclasses;

public abstract class GenericAbstractClassWithImplementation<T, U> {
int publicField = 1;

abstract T getT();

abstract void setT(T value);

abstract U getU();
}
Loading

0 comments on commit 45614a3

Please sign in to comment.