From 07a028a9b44a44956932352e1e0a993ac07a928d Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:04:14 +0000 Subject: [PATCH 1/4] add deadline --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 87ffcbb..1a6a1a9 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/4ISSpVK4) **Java reflection** Ваша задача --- написать генератор экземпляров произвольных классов. From 522f9df8d163a6472a47f04745444aab7c5afaf6 Mon Sep 17 00:00:00 2001 From: poma12390 Date: Wed, 15 Oct 2025 23:26:29 +0300 Subject: [PATCH 2/4] add main logic --- .../java/org/example/GenerateExample.java | 19 +- .../org/example/classes/BinaryTreeNode.java | 3 + src/main/java/org/example/classes/Cart.java | 3 + .../java/org/example/classes/Example.java | 3 + .../java/org/example/classes/Product.java | 3 + .../java/org/example/classes/Rectangle.java | 3 + src/main/java/org/example/classes/Shape.java | 5 +- .../java/org/example/classes/Triangle.java | 3 + .../org/example/generator/Generatable.java | 10 + .../java/org/example/generator/Generator.java | 209 +++++++++++++++++- 10 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 src/main/java/org/example/generator/Generatable.java diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..d10b5eb 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,15 +1,24 @@ package org.example; - -import org.example.classes.Example; +import org.example.classes.*; import org.example.generator.Generator; public class GenerateExample { public static void main(String[] args) { - var gen = new Generator(); + var gen = new Generator(3); try { - Object generated = gen.generateValueOfType(Example.class); - System.out.println(generated); + System.out.println(gen.generateValueOfType(Example.class)); + + Shape shape = gen.generateValueOfType(Shape.class); + System.out.println("shape: " + shape.getClass().getSimpleName() + + " area=" + shape.getArea() + " per=" + shape.getPerimeter()); + + Cart cart = gen.generateValueOfType(Cart.class); + System.out.println("cart items: " + (cart.getItems() == null ? 0 : cart.getItems().size())); + + BinaryTreeNode root = gen.generateValueOfType(BinaryTreeNode.class); + System.out.println("tree root data: " + (root == null ? null : root.getData())); + } catch (Throwable e) { throw new RuntimeException(e); } diff --git a/src/main/java/org/example/classes/BinaryTreeNode.java b/src/main/java/org/example/classes/BinaryTreeNode.java index 046ff56..52a9157 100644 --- a/src/main/java/org/example/classes/BinaryTreeNode.java +++ b/src/main/java/org/example/classes/BinaryTreeNode.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..3b9e8dd 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/main/java/org/example/classes/Cart.java @@ -1,7 +1,10 @@ package org.example.classes; +import org.example.generator.Generatable; + import java.util.List; +@Generatable public class Cart { private List items; diff --git a/src/main/java/org/example/classes/Example.java b/src/main/java/org/example/classes/Example.java index eac9463..e0c8d1c 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/main/java/org/example/classes/Example.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Example { int i; diff --git a/src/main/java/org/example/classes/Product.java b/src/main/java/org/example/classes/Product.java index e7dcc89..ba7aa89 100644 --- a/src/main/java/org/example/classes/Product.java +++ b/src/main/java/org/example/classes/Product.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Product { private String name; private double price; diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..5f6b174 100644 --- a/src/main/java/org/example/classes/Rectangle.java +++ b/src/main/java/org/example/classes/Rectangle.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Rectangle implements Shape { private double length; private double width; diff --git a/src/main/java/org/example/classes/Shape.java b/src/main/java/org/example/classes/Shape.java index c20a851..a7d1c1c 100644 --- a/src/main/java/org/example/classes/Shape.java +++ b/src/main/java/org/example/classes/Shape.java @@ -1,6 +1,9 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable(implementations = { Rectangle.class, Triangle.class }) public interface Shape { double getArea(); double getPerimeter(); -} \ No newline at end of file +} diff --git a/src/main/java/org/example/classes/Triangle.java b/src/main/java/org/example/classes/Triangle.java index 011e96f..e2c4c5a 100644 --- a/src/main/java/org/example/classes/Triangle.java +++ b/src/main/java/org/example/classes/Triangle.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Triangle implements Shape { private double sideA; private double sideB; diff --git a/src/main/java/org/example/generator/Generatable.java b/src/main/java/org/example/generator/Generatable.java new file mode 100644 index 0000000..cb8d4c0 --- /dev/null +++ b/src/main/java/org/example/generator/Generatable.java @@ -0,0 +1,10 @@ +package org.example.generator; + +import java.lang.annotation.*; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Generatable { + Class[] implementations() default {}; + int maxDepth() default 0; +} diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 9d86bfb..924764f 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,18 +1,211 @@ package org.example.generator; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Random; +import java.lang.reflect.*; +import java.util.*; +import java.util.concurrent.ThreadLocalRandom; +@SuppressWarnings("unchecked") public class Generator { + private final Random rnd = ThreadLocalRandom.current(); + private final int defaultMaxDepth; - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { - Constructor[] constructors = clazz.getDeclaredConstructors(); + public Generator() { + this(3); + } + + public Generator(int defaultMaxDepth) { + this.defaultMaxDepth = Math.max(1, defaultMaxDepth); + } + + public T generateValueOfType(Class clazz) + throws ReflectiveOperationException { + return generate(clazz, new HashMap<>(), defaultMaxDepth); + } + + private boolean isWrapperOrString(Class c) { + return c == Boolean.class || c == Byte.class || c == Short.class || c == Integer.class + || c == Long.class || c == Float.class || c == Double.class + || c == Character.class || c == String.class; + } + + private Object primitiveValue(Class c) { + if (c == boolean.class || c == Boolean.class) return rnd.nextBoolean(); + if (c == byte.class || c == Byte.class) return (byte) rnd.nextInt(); + if (c == short.class || c == Short.class) return (short) rnd.nextInt(); + if (c == int.class || c == Integer.class) return rnd.nextInt(10_000); + if (c == long.class || c == Long.class) return rnd.nextLong(); + if (c == float.class || c == Float.class) return rnd.nextFloat() * 1000f; + if (c == double.class || c == Double.class) return rnd.nextDouble() * 1000d; + if (c == char.class || c == Character.class) return (char) ('a' + rnd.nextInt(26)); + if (c == String.class) return randomString(3 + rnd.nextInt(8)); + throw new IllegalArgumentException("Unsupported primitive: " + c); + } + + private String randomString(int n) { + var sb = new StringBuilder(n); + for (int i = 0; i < n; i++) { + int t = rnd.nextInt(3); + if (t == 0) sb.append((char) ('a' + rnd.nextInt(26))); + else if (t == 1) sb.append((char) ('A' + rnd.nextInt(26))); + else sb.append((char) ('0' + rnd.nextInt(10))); + } + return sb.toString(); + } + + private > E randomEnum(Class e) { + E[] vals = e.getEnumConstants(); + if (vals == null || vals.length == 0) return null; + return vals[rnd.nextInt(vals.length)]; + } + + private T generate(Class type, Map, Integer> depth, int depthBudget) + throws ReflectiveOperationException { + + if (type.isPrimitive() || isWrapperOrString(type)) { + return (T) primitiveValue(type); + } + if (type.isEnum()) { + T[] vals = type.getEnumConstants(); + if (vals == null || vals.length == 0) return null; + return vals[rnd.nextInt(vals.length)]; + } + + if (type.isArray()) { + int size = rnd.nextInt(3); + Class comp = type.getComponentType(); + Object arr = Array.newInstance(comp, size); + for (int i = 0; i < size; i++) { + Array.set(arr, i, generate(comp, depth, depthBudget)); + } + return (T) arr; + } - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); + Generatable ann = type.getAnnotation(Generatable.class); + if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { + if (ann == null || ann.implementations().length == 0) { + throw new IllegalArgumentException("Нельзя сгенерировать " + type.getName()); + } + Class impl = ann.implementations()[rnd.nextInt(ann.implementations().length)]; + return (T) generate(impl, depth, depthBudget); + } + + if (ann == null) { + throw new IllegalArgumentException("Класс " + type.getName() + " не помечен @Generatable"); + } + + int maxDepthForType = ann.maxDepth() > 0 ? ann.maxDepth() : depthBudget; + int used = depth.getOrDefault(type, 0); + if (used >= maxDepthForType) { + return null; + } + depth.put(type, used + 1); + + List> ctors = new ArrayList<>(List.of(type.getDeclaredConstructors())); + Collections.shuffle(ctors, rnd); + + for (Constructor ctor : ctors) { + ctor.setAccessible(true); + try { + Object[] args = resolveArgs(ctor.getGenericParameterTypes(), depth, maxDepthForType); + return (T) ctor.newInstance(args); + } catch (Exception ignored) { + } + } + + try { + Constructor noArg = type.getDeclaredConstructor(); + noArg.setAccessible(true); + return noArg.newInstance(); + } catch (Exception e) { + throw new IllegalStateException("Не удалось создать " + type.getName(), e); + } + } + + private Object[] resolveArgs(Type[] types, Map, Integer> depth, int depthBudget) + throws ReflectiveOperationException { + Object[] args = new Object[types.length]; + for (int i = 0; i < types.length; i++) { + args[i] = generateForType(types[i], depth, depthBudget); + } + return args; + } + + private Object generateForType(Type t, Map, Integer> depth, int depthBudget) + throws ReflectiveOperationException { + if (t instanceof Class c) { + if (Collection.class.isAssignableFrom(c)) { + return instantiateCollectionClass(c); + } + if (Optional.class.isAssignableFrom(c)) { + return Optional.empty(); + } + return generate(c, depth, depthBudget); + } else if (t instanceof ParameterizedType p) { + Class raw = (Class) p.getRawType(); + + if (Optional.class == raw) { + Type arg = p.getActualTypeArguments()[0]; + boolean present = rnd.nextBoolean(); + return present ? Optional.ofNullable(generateForType(arg, depth, depthBudget)) : Optional.empty(); + } + + if (Collection.class.isAssignableFrom(raw)) { + Collection coll = instantiateCollectionClass(raw); + Type elType = p.getActualTypeArguments()[0]; + int size = rnd.nextInt(3); // 0..2 + for (int i = 0; i < size; i++) { + coll.add(generateForType(elType, depth, depthBudget)); + } + return coll; + } + + if (Map.class.isAssignableFrom(raw)) { + Map m = instantiateMapClass(raw); + Type kt = p.getActualTypeArguments()[0]; + Type vt = p.getActualTypeArguments()[1]; + int size = rnd.nextInt(3); + for (int i = 0; i < size; i++) { + Object k = generateForType(kt, depth, depthBudget); + Object v = generateForType(vt, depth, depthBudget); + m.put(k, v); + } + return m; + } + + return generate((Class) raw, depth, depthBudget); + } + + return null; } + @SuppressWarnings("unchecked") + private Collection instantiateCollectionClass(Class raw) { + if (raw == List.class || raw == Collection.class) return new ArrayList<>(); + if (raw == Set.class) return new HashSet<>(); + if (Collection.class.isAssignableFrom(raw)) { + try { + Constructor c = raw.getDeclaredConstructor(); + c.setAccessible(true); + return (Collection) c.newInstance(); + } catch (Exception e) { + return new ArrayList<>(); + } + } + return new ArrayList<>(); + } + @SuppressWarnings("unchecked") + private Map instantiateMapClass(Class raw) { + if (raw == Map.class) return new LinkedHashMap<>(); + if (Map.class.isAssignableFrom(raw)) { + try { + Constructor c = raw.getDeclaredConstructor(); + c.setAccessible(true); + return (Map) c.newInstance(); + } catch (Exception e) { + return new LinkedHashMap<>(); + } + } + return new LinkedHashMap<>(); + } } From 924c2e10a275204f4a6b31fea4c139e90817042c Mon Sep 17 00:00:00 2001 From: poma12390 Date: Wed, 15 Oct 2025 23:43:10 +0300 Subject: [PATCH 3/4] delete max depth --- .../java/org/example/generator/Generator.java | 315 ++++++++++++------ .../org/example/generator/GeneratorTest.java | 110 ++++++ 2 files changed, 324 insertions(+), 101 deletions(-) create mode 100644 src/test/java/org/example/generator/GeneratorTest.java diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 924764f..8bf2792 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -6,20 +6,54 @@ @SuppressWarnings("unchecked") public class Generator { - private final Random rnd = ThreadLocalRandom.current(); - private final int defaultMaxDepth; + private final Random rnd; + + private static final int P_NULL = 10; + private static final int P_REUSE = 50; public Generator() { - this(3); + this(ThreadLocalRandom.current()); + } + + public Generator(int ignoredDepth) { + this(ThreadLocalRandom.current()); + } + + public Generator(Random rnd) { + this.rnd = Objects.requireNonNull(rnd); + } + + public T generateValueOfType(Class clazz) throws ReflectiveOperationException { + State st = new State(rnd); + return (T) generateGraphForClass(clazz, st); } - public Generator(int defaultMaxDepth) { - this.defaultMaxDepth = Math.max(1, defaultMaxDepth); + private static final class State { + final Random rnd; + final ArrayList pool = new ArrayList<>(); + State(Random rnd) { this.rnd = rnd; } } - public T generateValueOfType(Class clazz) - throws ReflectiveOperationException { - return generate(clazz, new HashMap<>(), defaultMaxDepth); + private Object generateGraphForClass(Class type, State st) throws ReflectiveOperationException { + if (type.isPrimitive() || isWrapperOrString(type)) return primitiveValue(type); + if (type.isEnum()) return pickEnumValue((Class>) type); + if (type.isArray()) return randomArray(type.getComponentType(), st); + if (Optional.class == type) return Optional.empty(); // сырая Optional без параметра + + Class concrete = resolveConcrete(type); + + if (Collection.class.isAssignableFrom(concrete)) { + return instantiateCollectionClass(concrete); + } + if (Map.class.isAssignableFrom(concrete)) { + return instantiateMapClass(concrete); + } + + Object shell = instantiateShell(concrete); + st.pool.add(shell); + + populateFields(shell, st); + return shell; } private boolean isWrapperOrString(Class c) { @@ -52,133 +86,213 @@ private String randomString(int n) { return sb.toString(); } - private > E randomEnum(Class e) { - E[] vals = e.getEnumConstants(); + private Object pickEnumValue(Class> e) { + Object[] vals = e.getEnumConstants(); if (vals == null || vals.length == 0) return null; return vals[rnd.nextInt(vals.length)]; } - private T generate(Class type, Map, Integer> depth, int depthBudget) - throws ReflectiveOperationException { + private Object randomArray(Class component, State st) throws ReflectiveOperationException { + int size = rnd.nextInt(3); // 0..2 + Object arr = Array.newInstance(component, size); + for (int i = 0; i < size; i++) { + Object v = generateReferenceLike(component, null, st); + Array.set(arr, i, v); + } + return arr; + } - if (type.isPrimitive() || isWrapperOrString(type)) { - return (T) primitiveValue(type); + private Class resolveConcrete(Class type) { + if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { + return type; } - if (type.isEnum()) { - T[] vals = type.getEnumConstants(); - if (vals == null || vals.length == 0) return null; - return vals[rnd.nextInt(vals.length)]; + Generatable ann = type.getAnnotation(Generatable.class); + if (ann == null || ann.implementations().length == 0) { + throw new IllegalArgumentException("Нельзя сгенерировать " + type.getName()); } + Class[] impls = ann.implementations(); + return impls[rnd.nextInt(impls.length)]; + } - if (type.isArray()) { - int size = rnd.nextInt(3); - Class comp = type.getComponentType(); - Object arr = Array.newInstance(comp, size); - for (int i = 0; i < size; i++) { - Array.set(arr, i, generate(comp, depth, depthBudget)); + private Object instantiateShell(Class type) throws ReflectiveOperationException { + if (!type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { + Generatable ann = type.getAnnotation(Generatable.class); + if (ann == null && !type.getName().startsWith("java.")) { + throw new IllegalArgumentException("Класс " + type.getName() + " не помечен @Generatable"); } - return (T) arr; } - Generatable ann = type.getAnnotation(Generatable.class); - if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { - if (ann == null || ann.implementations().length == 0) { - throw new IllegalArgumentException("Нельзя сгенерировать " + type.getName()); + // Конструктор с минимальным числом параметров + Constructor[] ctors = type.getDeclaredConstructors(); + Arrays.sort(ctors, Comparator.comparingInt(Constructor::getParameterCount)); + + for (Constructor ctor : ctors) { + try { + ctor.setAccessible(true); + Class[] ptypes = ctor.getParameterTypes(); + Object[] args = new Object[ptypes.length]; + for (int i = 0; i < ptypes.length; i++) { + Class p = ptypes[i]; + if (p.isPrimitive() || isWrapperOrString(p) || p.isEnum()) { + args[i] = primitiveValue(p); + } else if (p.isArray()) { + // оболочка: пустой массив + args[i] = Array.newInstance(p.getComponentType(), 0); + } else if (Optional.class.isAssignableFrom(p)) { + args[i] = Optional.empty(); + } else if (Collection.class.isAssignableFrom(p)) { + args[i] = instantiateCollectionClass(p); + } else if (Map.class.isAssignableFrom(p)) { + args[i] = instantiateMapClass(p); + } else { + args[i] = null; + } + } + return ctor.newInstance(args); + } catch (Exception ignored) { } - Class impl = ann.implementations()[rnd.nextInt(ann.implementations().length)]; - return (T) generate(impl, depth, depthBudget); } - if (ann == null) { - throw new IllegalArgumentException("Класс " + type.getName() + " не помечен @Generatable"); + Constructor noArg = type.getDeclaredConstructor(); + noArg.setAccessible(true); + return noArg.newInstance(); + } + + private void populateFields(Object obj, State st) throws ReflectiveOperationException { + Class c = obj.getClass(); + for (Field f : allFields(c)) { + if (Modifier.isStatic(f.getModifiers())) continue; + if (Modifier.isFinal(f.getModifiers())) continue; + f.setAccessible(true); + + Object cur = f.get(obj); + Class ft = f.getType(); + Type gft = f.getGenericType(); + + if (cur != null) { + if (cur instanceof Collection col) { + maybeFillCollection((Collection) col, gft, st, obj); + } else if (cur instanceof Map map) { + maybeFillMap((Map) map, gft, st, obj); + } + continue; + } + + Object val = generateForField(ft, gft, st, obj); + if (val != null || !ft.isPrimitive()) { + f.set(obj, val); + } else { + f.set(obj, primitiveValue(ft)); + } } + } - int maxDepthForType = ann.maxDepth() > 0 ? ann.maxDepth() : depthBudget; - int used = depth.getOrDefault(type, 0); - if (used >= maxDepthForType) { - return null; + private List allFields(Class c) { + ArrayList res = new ArrayList<>(); + for (Class k = c; k != null && k != Object.class; k = k.getSuperclass()) { + res.addAll(Arrays.asList(k.getDeclaredFields())); } - depth.put(type, used + 1); + return res; + } - List> ctors = new ArrayList<>(List.of(type.getDeclaredConstructors())); - Collections.shuffle(ctors, rnd); + private Object generateForField(Class ft, Type gft, State st, Object owner) throws ReflectiveOperationException { + if (ft.isPrimitive() || isWrapperOrString(ft)) return primitiveValue(ft); + if (ft.isEnum()) return pickEnumValue((Class>) ft); - for (Constructor ctor : ctors) { - ctor.setAccessible(true); - try { - Object[] args = resolveArgs(ctor.getGenericParameterTypes(), depth, maxDepthForType); - return (T) ctor.newInstance(args); - } catch (Exception ignored) { + if (ft.isArray()) { + int n = rnd.nextInt(3); + Object arr = Array.newInstance(ft.getComponentType(), n); + for (int i = 0; i < n; i++) { + Array.set(arr, i, generateReferenceLike(ft.getComponentType(), owner, st)); } + return arr; } - try { - Constructor noArg = type.getDeclaredConstructor(); - noArg.setAccessible(true); - return noArg.newInstance(); - } catch (Exception e) { - throw new IllegalStateException("Не удалось создать " + type.getName(), e); + if (Optional.class.isAssignableFrom(ft)) { + Type arg = (gft instanceof ParameterizedType pt) ? pt.getActualTypeArguments()[0] : Object.class; + boolean present = rnd.nextBoolean(); + if (!present) return Optional.empty(); + Object elem = generateForTypeToken(arg, st, owner); + return Optional.ofNullable(elem); + } + + if (Collection.class.isAssignableFrom(ft)) { + Collection c = instantiateCollectionClass(ft); + maybeFillCollection(c, gft, st, owner); + return c; } - } - private Object[] resolveArgs(Type[] types, Map, Integer> depth, int depthBudget) - throws ReflectiveOperationException { - Object[] args = new Object[types.length]; - for (int i = 0; i < types.length; i++) { - args[i] = generateForType(types[i], depth, depthBudget); + if (Map.class.isAssignableFrom(ft)) { + Map m = instantiateMapClass(ft); + maybeFillMap(m, gft, st, owner); + return m; } - return args; + + return generateReferenceLike(ft, owner, st); } - private Object generateForType(Type t, Map, Integer> depth, int depthBudget) - throws ReflectiveOperationException { + private Object generateForTypeToken(Type t, State st, Object owner) throws ReflectiveOperationException { if (t instanceof Class c) { - if (Collection.class.isAssignableFrom(c)) { - return instantiateCollectionClass(c); - } - if (Optional.class.isAssignableFrom(c)) { - return Optional.empty(); - } - return generate(c, depth, depthBudget); - } else if (t instanceof ParameterizedType p) { - Class raw = (Class) p.getRawType(); - - if (Optional.class == raw) { - Type arg = p.getActualTypeArguments()[0]; - boolean present = rnd.nextBoolean(); - return present ? Optional.ofNullable(generateForType(arg, depth, depthBudget)) : Optional.empty(); - } + return generateForField(c, c, st, owner); + } else if (t instanceof ParameterizedType pt) { + Class raw = (Class) pt.getRawType(); + return generateForField(raw, pt, st, owner); + } else { + return null; + } + } - if (Collection.class.isAssignableFrom(raw)) { - Collection coll = instantiateCollectionClass(raw); - Type elType = p.getActualTypeArguments()[0]; - int size = rnd.nextInt(3); // 0..2 - for (int i = 0; i < size; i++) { - coll.add(generateForType(elType, depth, depthBudget)); - } - return coll; - } + private void maybeFillCollection(Collection c, Type gft, State st, Object owner) throws ReflectiveOperationException { + Type elemType = (gft instanceof ParameterizedType pt) ? pt.getActualTypeArguments()[0] : Object.class; + int add = rnd.nextInt(3); // 0..2 + for (int i = 0; i < add; i++) { + c.add(generateForTypeToken(elemType, st, owner)); + } + } - if (Map.class.isAssignableFrom(raw)) { - Map m = instantiateMapClass(raw); - Type kt = p.getActualTypeArguments()[0]; - Type vt = p.getActualTypeArguments()[1]; - int size = rnd.nextInt(3); - for (int i = 0; i < size; i++) { - Object k = generateForType(kt, depth, depthBudget); - Object v = generateForType(vt, depth, depthBudget); - m.put(k, v); - } - return m; - } + private void maybeFillMap(Map m, Type gft, State st, Object owner) throws ReflectiveOperationException { + Type kt = Object.class, vt = Object.class; + if (gft instanceof ParameterizedType pt) { + kt = pt.getActualTypeArguments()[0]; + vt = pt.getActualTypeArguments()[1]; + } + int add = rnd.nextInt(3); + for (int i = 0; i < add; i++) { + Object k = generateForTypeToken(kt, st, owner); + Object v = generateForTypeToken(vt, st, owner); + m.put(k, v); + } + } - return generate((Class) raw, depth, depthBudget); + private Object generateReferenceLike(Class ft, Object owner, State st) throws ReflectiveOperationException { + if (!ft.isPrimitive() && rnd.nextInt(100) < P_NULL) return null; + + ArrayList candidates = new ArrayList<>(); + for (Object o : st.pool) { + if (o != owner && ft.isInstance(o)) candidates.add(o); + } + if (!candidates.isEmpty() && rnd.nextInt(100) < P_REUSE) { + return candidates.get(rnd.nextInt(candidates.size())); } - return null; + Class concrete = resolveConcrete(ft); + if (concrete.isPrimitive() || isWrapperOrString(concrete) || concrete.isEnum() || concrete.isArray()) { + return generateGraphForClass(concrete, st); + } + if (Collection.class.isAssignableFrom(concrete)) { + return instantiateCollectionClass(concrete); + } + if (Map.class.isAssignableFrom(concrete)) { + return instantiateMapClass(concrete); + } + + Object shell = instantiateShell(concrete); + st.pool.add(shell); + populateFields(shell, st); + return shell; } - @SuppressWarnings("unchecked") + private Collection instantiateCollectionClass(Class raw) { if (raw == List.class || raw == Collection.class) return new ArrayList<>(); if (raw == Set.class) return new HashSet<>(); @@ -194,7 +308,6 @@ private Collection instantiateCollectionClass(Class raw) { return new ArrayList<>(); } - @SuppressWarnings("unchecked") private Map instantiateMapClass(Class raw) { if (raw == Map.class) return new LinkedHashMap<>(); if (Map.class.isAssignableFrom(raw)) { diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java new file mode 100644 index 0000000..bc28dea --- /dev/null +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -0,0 +1,110 @@ +package org.example.generator; + +import org.example.classes.BinaryTreeNode; +import org.example.classes.Cart; +import org.example.classes.Example; +import org.example.classes.Product; +import org.example.classes.Rectangle; +import org.example.classes.Shape; +import org.example.classes.Triangle; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.time.Duration; +import java.util.EnumSet; +import java.util.IdentityHashMap; +import java.util.LinkedList; +import java.util.Queue; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class GeneratorTest { + + @Test + void generateCartWithProducts() throws Exception { + Generator gen = new Generator(3); + Cart cart = gen.generateValueOfType(Cart.class); + assertNotNull(cart); + assertNotNull(cart.getItems(), "Cart.getItems() не должен быть null"); + } + + enum LocalE { A, B, C } + + @Test + void generateEnum() throws Exception { + Generator gen = new Generator(3); + LocalE e = gen.generateValueOfType(LocalE.class); + assertNotNull(e); + assertTrue(EnumSet.allOf(LocalE.class).contains(e)); + } + + @Test + void unannotatedInterfaceFails() { + interface Unannotated {} + Generator gen = new Generator(3); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> gen.generateValueOfType(Unannotated.class)); + assertTrue(ex.getMessage().contains("Нельзя сгенерировать")); + } + + static Stream> allTypes() { + return Stream.of( + Example.class, + Product.class, + Cart.class, + Rectangle.class, + Triangle.class, + BinaryTreeNode.class, + Shape.class + ); + } + + @ParameterizedTest + @MethodSource("allTypes") + void generatesAllKnownTypes(Class type) { + Generator gen = new Generator(3); + + Object obj = assertTimeoutPreemptively( + Duration.ofSeconds(1), + () -> gen.generateValueOfType((Class) type) + ); + + assertNotNull(obj); + + if (type == Shape.class) { + assertTrue(obj instanceof Rectangle || obj instanceof Triangle); + } + } + @Test + void generatesGraphWithoutHanging_BinaryTreeNode() { + Generator gen = new Generator(new java.util.Random(42)); + BinaryTreeNode root = assertTimeoutPreemptively( + Duration.ofSeconds(1), + () -> gen.generateValueOfType(BinaryTreeNode.class) + ); + assertNotNull(root); + + IdentityHashMap seen = new IdentityHashMap<>(); + Queue q = new LinkedList<>(); + q.add(root); + while (!q.isEmpty() && seen.size() < 10_000) { + BinaryTreeNode n = q.poll(); + if (n == null || seen.put(n, Boolean.TRUE) != null) continue; + q.add(n.getLeft()); + q.add(n.getRight()); + } + assertTrue(seen.size() >= 1); + } + + @Test + void generatesGraphWithSharingForInterface() throws Exception { + Generator gen = new Generator(new java.util.Random(123)); + Object s = gen.generateValueOfType(Shape.class); + assertNotNull(s); + } +} From 5b071659e79c9d4a4a63983b0db7c94dc52175a6 Mon Sep 17 00:00:00 2001 From: poma12390 Date: Thu, 30 Oct 2025 17:31:13 +0300 Subject: [PATCH 4/4] fix to random constructor --- src/main/java/org/example/generator/Generator.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 8bf2792..3529a47 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -122,11 +122,12 @@ private Object instantiateShell(Class type) throws ReflectiveOperationExcepti } } - // Конструктор с минимальным числом параметров + // Берём конструкторы в случайном порядке Constructor[] ctors = type.getDeclaredConstructors(); - Arrays.sort(ctors, Comparator.comparingInt(Constructor::getParameterCount)); + List> shuffled = new ArrayList<>(Arrays.asList(ctors)); + Collections.shuffle(shuffled, rnd); - for (Constructor ctor : ctors) { + for (Constructor ctor : shuffled) { try { ctor.setAccessible(true); Class[] ptypes = ctor.getParameterTypes(); @@ -149,7 +150,8 @@ private Object instantiateShell(Class type) throws ReflectiveOperationExcepti } } return ctor.newInstance(args); - } catch (Exception ignored) { + } catch (Throwable ignore) { + // пробуем следующий случайный конструктор } }