Skip to content

Instantly share code, notes, and snippets.

@MichalBrylka
Last active October 24, 2025 15:00
Show Gist options
  • Select an option

  • Save MichalBrylka/a63b150697831e677b710ecf895f3631 to your computer and use it in GitHub Desktop.

Select an option

Save MichalBrylka/a63b150697831e677b710ecf895f3631 to your computer and use it in GitHub Desktop.
Random item
import java.lang.reflect.*;
import java.util.*;
public class DefaultObjectGenerator {
public <T> T create(Class<T> clazz) {
try {
// Base cases: primitive, string, enum
if (isSimpleType(clazz)) return getPrimitive(clazz);
if (clazz.isEnum()) return clazz.cast(createEnumValue(clazz));
T instance = clazz.getDeclaredConstructor().newInstance();
fillFields(instance);
return instance;
} catch (Exception e) {
throw new RuntimeException("Failed to fill " + clazz.getSimpleName(), e);
}
}
private void fillFields(Object obj) {
try {
for (Method method : Arrays.stream(obj.getClass().getMethods())
.filter(m -> m.getName().startsWith("set") && m.getParameterCount() == 1)
.sorted(Comparator.comparing(Method::getName))
.toList()
) {
Class<?> paramType = method.getParameterTypes()[0];
Object value = createValueForType(paramType);
if (value != null)
method.invoke(obj, value);
}
// handle nested batches again (recursive)
for (Method method : Arrays.stream(obj.getClass().getMethods())
.filter(m -> m.getName().startsWith("get") && m.getParameterCount() == 0)
.sorted(Comparator.comparing(Method::getName))
.toList()
) {
Object val = method.invoke(obj);
if (val != null && isBatchType(val.getClass())) {
fillBatch(val, method);
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to populate " + obj.getClass().getSimpleName(), e);
}
}
private void fillBatch(Object batchObj, Method getter) throws Exception {
// Extract the generic parameter type <T> of Batch<T>
Type returnType = getter.getGenericReturnType();
if (!(returnType instanceof ParameterizedType pt)) return;
Type[] typeArgs = pt.getActualTypeArguments();
if (typeArgs.length != 1 || !(typeArgs[0] instanceof Class<?> itemType)) return;
int count = 1;
Method addMethod = batchObj.getClass().getMethod("add");
for (int i = 0; i < count; i++) {
Object item = addMethod.invoke(batchObj); // batch internally creates new T
if (item != null) {
fillFields(item);
}
}
}
// Utility methods
private static boolean isBatchType(Class<?> clazz) {
return clazz.getSimpleName().equals("Batch");
}
private static boolean isSimpleType(Class<?> clazz) {
return clazz == String.class ||
clazz == byte.class || clazz == Byte.class ||
clazz == short.class || clazz == Short.class ||
clazz == int.class || clazz == Integer.class ||
clazz == long.class || clazz == Long.class ||
clazz == double.class || clazz == Double.class ||
clazz == float.class || clazz == Float.class ||
clazz == char.class || clazz == Character.class ||
clazz == boolean.class || clazz == Boolean.class;
}
@SuppressWarnings("unchecked")
private <T> T getPrimitive(Class<T> clazz) {
if (clazz == String.class) return null;
if (clazz == byte.class || clazz == Byte.class) return (T) Byte.valueOf((byte) 0);
if (clazz == short.class || clazz == Short.class) return (T) Short.valueOf((short) 0);
if (clazz == int.class || clazz == Integer.class) return (T) Integer.valueOf(0);
if (clazz == long.class || clazz == Long.class) return (T) Long.valueOf(0L);
if (clazz == double.class || clazz == Double.class) return (T) Double.valueOf(0.0);
if (clazz == float.class || clazz == Float.class) return (T) Float.valueOf(0.0f);
if (clazz == boolean.class || clazz == Boolean.class) return (T) Boolean.FALSE;
if (clazz == char.class || clazz == Character.class) return (T) Character.valueOf('\0');
return null;
}
private Object createValueForType(Class<?> type) {
if (isSimpleType(type)) return getPrimitive(type);
if (type.isEnum()) return createEnumValue(type);
return create(type);
}
private <E> E createEnumValue(Class<E> enumClass) {
return null;
}
}
import java.lang.reflect.*;
import java.util.*;
public class RandomObjectGenerator {
private final Random random;
public RandomObjectGenerator(long seed) {
this.random = new Random(seed);
}
public <T> T randomize(Class<T> clazz) {
try {
// Base cases: primitive, string, enum
if (isSimpleType(clazz)) return randomPrimitive(clazz);
if (clazz.isEnum()) return clazz.cast(randomEnumValue(clazz));
T instance = clazz.getDeclaredConstructor().newInstance();
randomizeFields(instance);
return instance;
} catch (Exception e) {
throw new RuntimeException("Failed to randomize " + clazz.getSimpleName(), e);
}
}
private void randomizeFields(Object obj) {
try {
for (Method method : Arrays.stream(obj.getClass().getMethods())
.filter(m -> m.getName().startsWith("set") && m.getParameterCount() == 1)
.sorted(Comparator.comparing(Method::getName))
.toList()
) {
Class<?> paramType = method.getParameterTypes()[0];
Object randomValue = randomValueForType(paramType);
if (randomValue != null) {
method.invoke(obj, randomValue);
}
}
// handle nested batches again (recursive)
for (Method method : Arrays.stream(obj.getClass().getMethods())
.filter(m -> m.getName().startsWith("get") && m.getParameterCount() == 0)
.sorted(Comparator.comparing(Method::getName))
.toList()
) {
Object val = method.invoke(obj);
if (val != null && isBatchType(val.getClass())) {
fillBatch(val, method);
}
}
} catch (Exception e) {
throw new RuntimeException("Failed to populate " + obj.getClass().getSimpleName(), e);
}
}
private void fillBatch(Object batchObj, Method getter) throws Exception {
// Extract the generic parameter type <T> of Batch<T>
Type returnType = getter.getGenericReturnType();
if (!(returnType instanceof ParameterizedType pt)) return;
Type[] typeArgs = pt.getActualTypeArguments();
if (typeArgs.length != 1 || !(typeArgs[0] instanceof Class<?> itemType)) return;
int count = random.nextInt(1, 4);
Method addMethod = batchObj.getClass().getMethod("add");
for (int i = 0; i < count; i++) {
Object item = addMethod.invoke(batchObj); // batch internally creates new T
if (item != null) {
randomizeFields(item);
}
}
}
// Utility methods
private static boolean isBatchType(Class<?> clazz) {
return clazz.getSimpleName().equals("Batch");
}
private static boolean isSimpleType(Class<?> clazz) {
return clazz == String.class ||
clazz == byte.class || clazz == Byte.class ||
clazz == short.class || clazz == Short.class ||
clazz == int.class || clazz == Integer.class ||
clazz == long.class || clazz == Long.class ||
clazz == double.class || clazz == Double.class ||
clazz == float.class || clazz == Float.class ||
clazz == char.class || clazz == Character.class ||
clazz == boolean.class || clazz == Boolean.class;
}
@SuppressWarnings("unchecked")
private <T> T randomPrimitive(Class<T> clazz) {
if (clazz == String.class) return (T) ("Random" + random.nextInt(1000));
if (clazz == byte.class || clazz == Byte.class) return (T) Byte.valueOf((byte) random.nextInt(100));
if (clazz == short.class || clazz == Short.class) return (T) Short.valueOf((short) random.nextInt(100));
if (clazz == int.class || clazz == Integer.class) return (T) Integer.valueOf(random.nextInt(100));
if (clazz == long.class || clazz == Long.class) return (T) Long.valueOf(random.nextLong(100));
if (clazz == double.class || clazz == Double.class) return (T) Double.valueOf(random.nextDouble());
if (clazz == float.class || clazz == Float.class) return (T) Float.valueOf(random.nextFloat());
if (clazz == char.class || clazz == Character.class)
return (T) Character.valueOf((char) (32 + random.nextInt(95)));
if (clazz == boolean.class || clazz == Boolean.class) return (T) Boolean.valueOf(random.nextBoolean());
return null;
}
private Object randomValueForType(Class<?> type) {
if (isSimpleType(type)) return randomPrimitive(type);
if (type.isEnum()) return randomEnumValue(type);
return randomize(type);
}
private <E> E randomEnumValue(Class<E> enumClass) {
E[] values = enumClass.getEnumConstants();
return values[random.nextInt(values.length)];
}
}
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import org.junit.jupiter.api.*;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static org.assertj.core.api.Assertions.*;
class RandomObjectGeneratorTest {
// 🔹 Provide both class and expected deterministic value for primitives
static Stream<Arguments> simpleClasses() {
return Stream.of(
Arguments.of(String.class, "Random130"),
Arguments.of(Integer.class, 30),
Arguments.of(Boolean.class, true),
Arguments.of(Double.class, 0.727563)
);
}
// 🔹 Provide expected attributes for complex classes
static Stream<Arguments> complexClasses() {
return Stream.of(
Arguments.of(Address.class, new Address("Random130", 63)),
Arguments.of(Job.class, new Job("Random763", 30)),
Arguments.of(Person.class, new Person("Random970", 84, true, Role.USER, new Address("Random763", 48), GetSampleBatch()))
);
}
private static Batch<Job> GetSampleBatch() {
Batch<Job> jobBatch = new Batch<>(Job.class);
// Add 3 jobs
Job job1 = jobBatch.add();
job1.setTitle("Random519");
job1.setSalary(18);
Job job2 = jobBatch.add();
job2.setTitle("Random182");
job2.setSalary(93);
Job job3 = jobBatch.add();
job3.setTitle("Random276");
job3.setSalary(2);
return jobBatch;
}
@ParameterizedTest
@MethodSource("simpleClasses")
@DisplayName("should generate deterministic primitive-like values given seed=42")
void testSimpleRandomGenerationDeterministic(Class<?> clazz, Object expected) {
Object result = new RandomObjectGenerator(42).randomize(clazz);
assertThat(result)
.as("random instance for %s", clazz.getSimpleName())
.isNotNull();
if (expected instanceof Double d)
assertThat((Double) result).isCloseTo(d, within(1e-6));
else if (expected instanceof Float f)
assertThat((Double) result).isCloseTo(f, within(1e-6));
else
assertThat(result).isEqualTo(expected);
}
@ParameterizedTest
@MethodSource("complexClasses")
@DisplayName("should generate deterministic complex objects given seed=42")
void testComplexRandomGenerationDeterministic(Class<?> clazz, Object expected) {
Object result = new RandomObjectGenerator(42).randomize(clazz);
assertThat(result).usingRecursiveComparison().isEqualTo(expected);
}
@Test
@DisplayName("should fill Batch<Job> recursively and deterministically")
void testBatchFilling() {
Person person = new RandomObjectGenerator(42).randomize(Person.class);
assertThat(person).isNotNull();
assertThat(person.getJobs()).isNotNull();
List<Job> jobs = person.getJobs().getItems();
assertThat(jobs)
.isNotNull()
.isNotEmpty()
.allSatisfy(job -> {
assertThat(job.getTitle()).startsWith("Random");
assertThat(job.getSalary()).isBetween(0, 99);
});
// deterministic for seed 42 — number of jobs created between 1–3
assertThat(jobs.size()).isEqualTo(3);
}
}
enum Role {
ADMIN, USER, GUEST
}
@lombok.AllArgsConstructor
@lombok.NoArgsConstructor
@lombok.Getter
@lombok.Setter
class Address {
private String city;
private int zip;
}
@lombok.NoArgsConstructor
@lombok.AllArgsConstructor
@lombok.Getter
@lombok.Setter
class Job {
private String title;
private int salary;
}
@lombok.Getter
@lombok.AllArgsConstructor
@lombok.NoArgsConstructor
class Person {
@lombok.Setter String name;
@lombok.Setter int age;
@lombok.Setter boolean active;
@lombok.Setter Role role;
@lombok.Setter Address address = null;
Batch<Job> jobs = new Batch<>(Job.class);
}
/**
* Updated Batch<T> that internally creates new items on add()
*/
class Batch<T> {
private final List<T> list = new ArrayList<>();
private final Class<T> type;
public Batch(Class<T> type) {
this.type = type;
}
@SuppressWarnings("unchecked")
public T add() {
try {
try {
T item = type.getDeclaredConstructor().newInstance();
list.add(item);
return item;
} catch (Exception e) {
throw new RuntimeException("Unable to create new Batch item", e);
}
} catch (Exception e) {
throw new RuntimeException("Unable to add new Batch item", e);
}
}
public List<T> getItems() {
return list;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment