From f4e385fc961dd383c48f010f9315d0b18c4afc6a Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 16:31:18 +0000 Subject: [PATCH 01/14] 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 2941ba915111dc6d86b27b480f575b449b596c24 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Thu, 9 Oct 2025 16:48:05 +0300 Subject: [PATCH 02/14] setup project structure --- build.gradle.kts | 1 + .../java/org/example/GenerateExample.java | 17 --------- .../org/example/generator/Generatable.java | 11 ++++++ .../generator/GenerationException.java | 7 ++++ .../java/org/example/generator/Generator.java | 12 +++++-- .../org/example/classes/BinaryTreeNode.java | 4 +++ .../java/org/example/classes/Cart.java | 4 ++- .../java/org/example/classes/Example.java | 3 ++ .../org/example/classes/NonGeneratable.java | 14 ++++++++ .../java/org/example/classes/Product.java | 4 ++- .../java/org/example/classes/Rectangle.java | 0 .../java/org/example/classes/Shape.java | 3 ++ .../java/org/example/classes/Triangle.java | 0 .../org/example/generator/GeneratorTest.java | 35 +++++++++++++++++++ types.md | 7 ++++ 15 files changed, 100 insertions(+), 22 deletions(-) delete mode 100644 src/main/java/org/example/GenerateExample.java create mode 100644 src/main/java/org/example/generator/Generatable.java create mode 100644 src/main/java/org/example/generator/GenerationException.java rename src/{main => test}/java/org/example/classes/BinaryTreeNode.java (90%) rename src/{main => test}/java/org/example/classes/Cart.java (89%) rename src/{main => test}/java/org/example/classes/Example.java (78%) create mode 100644 src/test/java/org/example/classes/NonGeneratable.java rename src/{main => test}/java/org/example/classes/Product.java (93%) rename src/{main => test}/java/org/example/classes/Rectangle.java (100%) rename src/{main => test}/java/org/example/classes/Shape.java (65%) rename src/{main => test}/java/org/example/classes/Triangle.java (100%) create mode 100644 src/test/java/org/example/generator/GeneratorTest.java create mode 100644 types.md diff --git a/build.gradle.kts b/build.gradle.kts index b4738ae..f3ffd4c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,6 +12,7 @@ repositories { dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.assertj:assertj-core:3.27.6") testRuntimeOnly("org.junit.platform:junit-platform-launcher") } diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java deleted file mode 100644 index 47679a9..0000000 --- a/src/main/java/org/example/GenerateExample.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.example; - - -import org.example.classes.Example; -import org.example.generator.Generator; - -public class GenerateExample { - public static void main(String[] args) { - var gen = new Generator(); - try { - Object generated = gen.generateValueOfType(Example.class); - System.out.println(generated); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } -} \ No newline at end of file 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..b7f934a --- /dev/null +++ b/src/main/java/org/example/generator/Generatable.java @@ -0,0 +1,11 @@ +package org.example.generator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Generatable { +} diff --git a/src/main/java/org/example/generator/GenerationException.java b/src/main/java/org/example/generator/GenerationException.java new file mode 100644 index 0000000..a97b124 --- /dev/null +++ b/src/main/java/org/example/generator/GenerationException.java @@ -0,0 +1,7 @@ +package org.example.generator; + +public class GenerationException extends Exception { + public GenerationException(String message) { + super(message); + } +} diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 9d86bfb..fa17fc8 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -6,13 +6,19 @@ public class Generator { - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { + // todo think about generic + public Object generateValueOfType( + Class clazz + ) throws InvocationTargetException, InstantiationException, IllegalAccessException, GenerationException { + + if (!clazz.isAnnotationPresent(Generatable.class)) { + throw new GenerationException("Class is not annotated with @Generatable"); + } + Constructor[] constructors = clazz.getDeclaredConstructors(); int randomConstructorIndex = new Random().nextInt(constructors.length); Constructor randomConstructor = constructors[randomConstructorIndex]; return randomConstructor.newInstance(111); } - - } diff --git a/src/main/java/org/example/classes/BinaryTreeNode.java b/src/test/java/org/example/classes/BinaryTreeNode.java similarity index 90% rename from src/main/java/org/example/classes/BinaryTreeNode.java rename to src/test/java/org/example/classes/BinaryTreeNode.java index 046ff56..8dfa863 100644 --- a/src/main/java/org/example/classes/BinaryTreeNode.java +++ b/src/test/java/org/example/classes/BinaryTreeNode.java @@ -1,5 +1,9 @@ package org.example.classes; +import org.example.generator.Generatable; + +// todo recursion +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; diff --git a/src/main/java/org/example/classes/Cart.java b/src/test/java/org/example/classes/Cart.java similarity index 89% rename from src/main/java/org/example/classes/Cart.java rename to src/test/java/org/example/classes/Cart.java index 965237d..0feefd3 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/test/java/org/example/classes/Cart.java @@ -1,7 +1,9 @@ package org.example.classes; import java.util.List; +import org.example.generator.Generatable; +@Generatable public class Cart { private List items; @@ -18,4 +20,4 @@ public void setItems(List items) { } // Конструктор, методы добавления и удаления товаров, геттеры и другие методы -} \ No newline at end of file +} diff --git a/src/main/java/org/example/classes/Example.java b/src/test/java/org/example/classes/Example.java similarity index 78% rename from src/main/java/org/example/classes/Example.java rename to src/test/java/org/example/classes/Example.java index eac9463..e0c8d1c 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/test/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/test/java/org/example/classes/NonGeneratable.java b/src/test/java/org/example/classes/NonGeneratable.java new file mode 100644 index 0000000..4802552 --- /dev/null +++ b/src/test/java/org/example/classes/NonGeneratable.java @@ -0,0 +1,14 @@ +package org.example.classes; + +public class NonGeneratable { + int i; + + public NonGeneratable(int i) { + this.i = i; + } + + @Override + public String toString() { + return "NonGeneratable(" + i + ")"; + } +} diff --git a/src/main/java/org/example/classes/Product.java b/src/test/java/org/example/classes/Product.java similarity index 93% rename from src/main/java/org/example/classes/Product.java rename to src/test/java/org/example/classes/Product.java index e7dcc89..f439adf 100644 --- a/src/main/java/org/example/classes/Product.java +++ b/src/test/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; @@ -44,5 +47,4 @@ public boolean equals(Object obj) { public String toString() { return super.toString(); } - } diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/test/java/org/example/classes/Rectangle.java similarity index 100% rename from src/main/java/org/example/classes/Rectangle.java rename to src/test/java/org/example/classes/Rectangle.java diff --git a/src/main/java/org/example/classes/Shape.java b/src/test/java/org/example/classes/Shape.java similarity index 65% rename from src/main/java/org/example/classes/Shape.java rename to src/test/java/org/example/classes/Shape.java index c20a851..7a6f41f 100644 --- a/src/main/java/org/example/classes/Shape.java +++ b/src/test/java/org/example/classes/Shape.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public interface Shape { double getArea(); double getPerimeter(); diff --git a/src/main/java/org/example/classes/Triangle.java b/src/test/java/org/example/classes/Triangle.java similarity index 100% rename from src/main/java/org/example/classes/Triangle.java rename to src/test/java/org/example/classes/Triangle.java 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..a7082bd --- /dev/null +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -0,0 +1,35 @@ +package org.example.generator; + +import org.example.classes.Example; +import org.example.classes.NonGeneratable; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class GeneratorTest { + + private final Generator generator = new Generator(); + + @Test + void shouldThrowExceptionOnNonGeneratableClass() { + var ex = assertThrows( + GenerationException.class, + () -> generator.generateValueOfType(NonGeneratable.class) + ); + assertThat(ex.getMessage()).isEqualTo("Class is not annotated with @Generatable"); + } + + @Test + void shouldGenerateExampleClass() { + var example = generate(Example.class); + assertThat(example).isInstanceOf(Example.class); + } + + private Object generate(Class clazz) { + try { + return generator.generateValueOfType(clazz); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/types.md b/types.md new file mode 100644 index 0000000..3946258 --- /dev/null +++ b/types.md @@ -0,0 +1,7 @@ +- [Example.java](src/test/java/org/example/classes/Example.java): базовый пример, содержит один примитив в конструкторе +- [Product.java](src/test/java/org/example/classes/Product.java): конструктор принимает на вход не все поля, примитивы +- [Cart.java](src/test/java/org/example/classes/Cart.java): конструктор принимает на вход класс из classpath +- [BinaryTreeNode.java](src/test/java/org/example/classes/BinaryTreeNode.java): рекурсия +- [Shape.java](src/test/java/org/example/classes/Shape.java): интерфейс (предположительно выбираем любую реализация) + - [Rectangle.java](src/test/java/org/example/classes/Rectangle.java): реализация один + - [Triangle.java](src/test/java/org/example/classes/Triangle.java): реализация два From cc5928efd7a1e7be44123318c0a11f32a0f0d675 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Tue, 21 Oct 2025 23:06:30 +0300 Subject: [PATCH 03/14] add primitives support --- build.gradle.kts | 6 ++ .../java/org/example/generator/Generator.java | 65 +++++++++++++++++-- .../type/TypeGeneratorsProvider.java | 9 +++ .../impl/PrimitiveGeneratorsProvider.java | 45 +++++++++++++ .../type/impl/StringGeneratorsProvider.java | 30 +++++++++ .../org/example/generator/GeneratorTest.java | 39 ++++++++++- .../type/TypeGeneratorsProviderTest.java | 55 ++++++++++++++++ .../impl/PrimitiveGeneratorsProviderTest.java | 16 +++++ .../impl/StringGeneratorsProviderTest.java | 26 ++++++++ types.md | 16 +++-- 10 files changed, 294 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/example/generator/type/TypeGeneratorsProvider.java create mode 100644 src/main/java/org/example/generator/type/impl/PrimitiveGeneratorsProvider.java create mode 100644 src/main/java/org/example/generator/type/impl/StringGeneratorsProvider.java create mode 100644 src/test/java/org/example/generator/type/TypeGeneratorsProviderTest.java create mode 100644 src/test/java/org/example/generator/type/impl/PrimitiveGeneratorsProviderTest.java create mode 100644 src/test/java/org/example/generator/type/impl/StringGeneratorsProviderTest.java diff --git a/build.gradle.kts b/build.gradle.kts index f3ffd4c..0b2c701 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -5,6 +5,12 @@ plugins { group = "org.example" version = "1.0-SNAPSHOT" +java { + toolchain { + languageVersion.set(JavaLanguageVersion.of(21)) + } +} + repositories { mavenCentral() } diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index fa17fc8..d1ace6b 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -2,11 +2,39 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.Random; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Supplier; +import org.example.generator.type.TypeGeneratorsProvider; public class Generator { - // todo think about generic + private final Map, Supplier> generators; + + public Generator(Collection providers) { + Map, Supplier> result = new HashMap<>(); + + for (TypeGeneratorsProvider provider : providers) { + Map, Supplier> generatorsFromProvider = provider.getGenerators(); + + for (Map.Entry, Supplier> entry : generatorsFromProvider.entrySet()) { + Class type = entry.getKey(); + Supplier supplier = entry.getValue(); + + if (result.containsKey(type)) { + throw new IllegalArgumentException( + "Multiple providers supply generator for type: " + type.getName() + ); + } + + result.put(type, supplier); + } + } + + this.generators = Map.copyOf(result); + } + public Object generateValueOfType( Class clazz ) throws InvocationTargetException, InstantiationException, IllegalAccessException, GenerationException { @@ -16,9 +44,36 @@ public Object generateValueOfType( } Constructor[] constructors = clazz.getDeclaredConstructors(); + for (int i = 0; i < constructors.length; i++) { + Constructor constructor = constructors[i]; + try { + return tryConstructor(constructor); + } catch (Exception e) { + if (i == constructors.length - 1) { + throw e; + } + } + } + + throw new GenerationException("No suitable constructor found for class: " + clazz.getName()); + } + + private Object tryConstructor( + Constructor constructor + ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { + Object[] paramValues = new Object[constructor.getParameterCount()]; + + for (int i = 0; i < constructor.getParameterCount(); i++) { + Class paramType = constructor.getParameterTypes()[i]; + Supplier supplier = generators.get(paramType); + + if (supplier == null) { + throw new GenerationException("Unknown type: " + paramType.getName()); + } + + paramValues[i] = supplier.get(); + } - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); + return constructor.newInstance(paramValues); } } diff --git a/src/main/java/org/example/generator/type/TypeGeneratorsProvider.java b/src/main/java/org/example/generator/type/TypeGeneratorsProvider.java new file mode 100644 index 0000000..2d46063 --- /dev/null +++ b/src/main/java/org/example/generator/type/TypeGeneratorsProvider.java @@ -0,0 +1,9 @@ +package org.example.generator.type; + +import java.util.Map; +import java.util.function.Supplier; + +@FunctionalInterface +public interface TypeGeneratorsProvider { + Map, Supplier> getGenerators(); +} diff --git a/src/main/java/org/example/generator/type/impl/PrimitiveGeneratorsProvider.java b/src/main/java/org/example/generator/type/impl/PrimitiveGeneratorsProvider.java new file mode 100644 index 0000000..06a57ca --- /dev/null +++ b/src/main/java/org/example/generator/type/impl/PrimitiveGeneratorsProvider.java @@ -0,0 +1,45 @@ +package org.example.generator.type.impl; + +import java.util.Map; +import java.util.Random; +import java.util.function.Supplier; +import org.example.generator.type.TypeGeneratorsProvider; + +public class PrimitiveGeneratorsProvider implements TypeGeneratorsProvider { + + private final Random random; + + public PrimitiveGeneratorsProvider(Random random) { + this.random = random; + } + + @Override + public Map, Supplier> getGenerators() { + + return Map.ofEntries( + Map.entry(boolean.class, random::nextBoolean), + Map.entry(Boolean.class, random::nextBoolean), + + Map.entry(byte.class, () -> (byte) random.nextInt(Byte.MAX_VALUE)), + Map.entry(Byte.class, () -> (byte) random.nextInt(Byte.MAX_VALUE)), + + Map.entry(short.class, () -> (short) random.nextInt(Short.MAX_VALUE)), + Map.entry(Short.class, () -> (short) random.nextInt(Short.MAX_VALUE)), + + Map.entry(int.class, random::nextInt), + Map.entry(Integer.class, random::nextInt), + + Map.entry(long.class, random::nextLong), + Map.entry(Long.class, random::nextLong), + + Map.entry(float.class, random::nextFloat), + Map.entry(Float.class, random::nextFloat), + + Map.entry(double.class, random::nextDouble), + Map.entry(Double.class, random::nextDouble), + + Map.entry(char.class, () -> (char) random.nextInt(Character.MAX_VALUE)), + Map.entry(Character.class, () -> (char) random.nextInt(Character.MAX_VALUE)) + ); + } +} diff --git a/src/main/java/org/example/generator/type/impl/StringGeneratorsProvider.java b/src/main/java/org/example/generator/type/impl/StringGeneratorsProvider.java new file mode 100644 index 0000000..754b8eb --- /dev/null +++ b/src/main/java/org/example/generator/type/impl/StringGeneratorsProvider.java @@ -0,0 +1,30 @@ +package org.example.generator.type.impl; + +import java.util.Map; +import java.util.Random; +import java.util.function.Supplier; +import org.example.generator.type.TypeGeneratorsProvider; + +public class StringGeneratorsProvider implements TypeGeneratorsProvider { + + private final Supplier stringSupplier; + + public StringGeneratorsProvider(Random random, int maxLength) { + + this.stringSupplier = () -> { + int length = random.nextInt(maxLength); + StringBuilder sb = new StringBuilder(length); + for (int i = 0; i < length; i++) { + sb.append((char) (random.nextInt(26) + 'a')); + } + + return sb.toString(); + }; + } + + @Override + public Map, Supplier> getGenerators() { + + return Map.of(String.class, stringSupplier); + } +} diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index a7082bd..19bf768 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -1,14 +1,45 @@ package org.example.generator; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Random; import org.example.classes.Example; import org.example.classes.NonGeneratable; +import org.example.classes.Product; +import org.example.generator.type.TypeGeneratorsProvider; +import org.example.generator.type.impl.PrimitiveGeneratorsProvider; +import org.example.generator.type.impl.StringGeneratorsProvider; import org.junit.jupiter.api.Test; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; class GeneratorTest { - private final Generator generator = new Generator(); + private final Random random = new Random(); + + private final Collection providers = List.of( + new PrimitiveGeneratorsProvider(random), + new StringGeneratorsProvider(random, 15) + ); + + private final Generator generator = new Generator(providers); + + @Test + void shouldThrowOnDuplicateGenerator() { + TypeGeneratorsProvider provider1 = () -> Map.of(String.class, () -> "test-string"); + TypeGeneratorsProvider provider2 = () -> Map.of(String.class, () -> "test-string-2"); + + Collection duplicateProviders = List.of(provider1, provider2); + + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new Generator(duplicateProviders) + ); + + assertThat(exception.getMessage()) + .contains("Multiple providers supply generator for type: java.lang.String"); + } @Test void shouldThrowExceptionOnNonGeneratableClass() { @@ -25,6 +56,12 @@ void shouldGenerateExampleClass() { assertThat(example).isInstanceOf(Example.class); } + @Test + void shouldGenerateProductClass() { + var product = generate(Product.class); + assertThat(product).isInstanceOf(Product.class); + } + private Object generate(Class clazz) { try { return generator.generateValueOfType(clazz); diff --git a/src/test/java/org/example/generator/type/TypeGeneratorsProviderTest.java b/src/test/java/org/example/generator/type/TypeGeneratorsProviderTest.java new file mode 100644 index 0000000..acc5ca9 --- /dev/null +++ b/src/test/java/org/example/generator/type/TypeGeneratorsProviderTest.java @@ -0,0 +1,55 @@ +package org.example.generator.type; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public abstract class TypeGeneratorsProviderTest { + + protected abstract TypeGeneratorsProvider getProvider(); + + @Test + void suppliersShouldMatchClasses() { + var provider = getProvider(); + + var generators = provider.getGenerators(); + + for (var classToSupplier : generators.entrySet()) { + var clazz = classToSupplier.getKey(); + var supplier = classToSupplier.getValue(); + + var instance = supplier.get(); + + assertThat(instance).isInstanceOfAny(clazz, getPrimitiveType(clazz), getWrapperType(clazz)); + } + } + + private Class getPrimitiveType(Class wrapper) { + return switch (wrapper.getSimpleName()) { + case "java.lang.Integer" -> int.class; + case "java.lang.Short" -> short.class; + case "java.lang.Byte" -> byte.class; + case "java.lang.Long" -> long.class; + case "java.lang.Float" -> float.class; + case "java.lang.Double" -> double.class; + case "java.lang.Character" -> char.class; + case "java.lang.Boolean" -> boolean.class; + case "java.lang.Void" -> void.class; + default -> wrapper; + }; + } + + private Class getWrapperType(Class primitive) { + return switch (primitive.getSimpleName()) { + case "int" -> Integer.class; + case "short" -> Short.class; + case "byte" -> Byte.class; + case "long" -> Long.class; + case "float" -> Float.class; + case "double" -> Double.class; + case "char" -> Character.class; + case "boolean" -> Boolean.class; + case "void" -> Void.class; + default -> primitive; + }; + } +} diff --git a/src/test/java/org/example/generator/type/impl/PrimitiveGeneratorsProviderTest.java b/src/test/java/org/example/generator/type/impl/PrimitiveGeneratorsProviderTest.java new file mode 100644 index 0000000..578320c --- /dev/null +++ b/src/test/java/org/example/generator/type/impl/PrimitiveGeneratorsProviderTest.java @@ -0,0 +1,16 @@ +package org.example.generator.type.impl; + +import java.util.Random; +import org.example.generator.type.TypeGeneratorsProvider; +import org.example.generator.type.TypeGeneratorsProviderTest; + +class PrimitiveGeneratorsProviderTest extends TypeGeneratorsProviderTest { + + private final Random random = new Random(); + private final TypeGeneratorsProvider provider = new PrimitiveGeneratorsProvider(random); + + @Override + protected TypeGeneratorsProvider getProvider() { + return provider; + } +} diff --git a/src/test/java/org/example/generator/type/impl/StringGeneratorsProviderTest.java b/src/test/java/org/example/generator/type/impl/StringGeneratorsProviderTest.java new file mode 100644 index 0000000..199228f --- /dev/null +++ b/src/test/java/org/example/generator/type/impl/StringGeneratorsProviderTest.java @@ -0,0 +1,26 @@ +package org.example.generator.type.impl; + +import java.util.Random; +import org.example.generator.type.TypeGeneratorsProvider; +import org.example.generator.type.TypeGeneratorsProviderTest; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class StringGeneratorsProviderTest extends TypeGeneratorsProviderTest { + + private final Random random = new Random(); + private final TypeGeneratorsProvider provider = new StringGeneratorsProvider(random, 15); + + @Override + protected TypeGeneratorsProvider getProvider() { + return provider; + } + + @Test + void shouldGenerateStrings() { + var generators = provider.getGenerators(); + + assertThat(generators.size()).isEqualTo(1); + assertThat(generators.containsKey(String.class)).isTrue(); + } +} diff --git a/types.md b/types.md index 3946258..1d51475 100644 --- a/types.md +++ b/types.md @@ -1,7 +1,9 @@ -- [Example.java](src/test/java/org/example/classes/Example.java): базовый пример, содержит один примитив в конструкторе -- [Product.java](src/test/java/org/example/classes/Product.java): конструктор принимает на вход не все поля, примитивы -- [Cart.java](src/test/java/org/example/classes/Cart.java): конструктор принимает на вход класс из classpath -- [BinaryTreeNode.java](src/test/java/org/example/classes/BinaryTreeNode.java): рекурсия -- [Shape.java](src/test/java/org/example/classes/Shape.java): интерфейс (предположительно выбираем любую реализация) - - [Rectangle.java](src/test/java/org/example/classes/Rectangle.java): реализация один - - [Triangle.java](src/test/java/org/example/classes/Triangle.java): реализация два +- [X] [Example.java](src/test/java/org/example/classes/Example.java): базовый пример, содержит один примитив в + конструкторе +- [X] [Product.java](src/test/java/org/example/classes/Product.java): конструктор принимает на вход не все поля, + примитивы +- [ ] [Cart.java](src/test/java/org/example/classes/Cart.java): конструктор принимает на вход класс из classpath +- [ ] [BinaryTreeNode.java](src/test/java/org/example/classes/BinaryTreeNode.java): рекурсия +- [ ] [Shape.java](src/test/java/org/example/classes/Shape.java): интерфейс (предположительно выбираем любую реализация) + - [ ] [Rectangle.java](src/test/java/org/example/classes/Rectangle.java): реализация один + - [ ] [Triangle.java](src/test/java/org/example/classes/Triangle.java): реализация два From 073d3138058c4c2fa976465927dc3638630b7993 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Tue, 21 Oct 2025 23:13:48 +0300 Subject: [PATCH 04/14] update tests --- .../java/org/example/classes/Rectangle.java | 7 ++++-- .../java/org/example/classes/Triangle.java | 9 ++++--- .../org/example/generator/GeneratorTest.java | 24 ++++++++++++------- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/test/java/org/example/classes/Rectangle.java b/src/test/java/org/example/classes/Rectangle.java index 90b0886..5192246 100644 --- a/src/test/java/org/example/classes/Rectangle.java +++ b/src/test/java/org/example/classes/Rectangle.java @@ -1,8 +1,11 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Rectangle implements Shape { - private double length; - private double width; + private final double length; + private final double width; public Rectangle(double length, double width) { this.length = length; diff --git a/src/test/java/org/example/classes/Triangle.java b/src/test/java/org/example/classes/Triangle.java index 011e96f..c68ee83 100644 --- a/src/test/java/org/example/classes/Triangle.java +++ b/src/test/java/org/example/classes/Triangle.java @@ -1,9 +1,12 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Triangle implements Shape { - private double sideA; - private double sideB; - private double sideC; + private final double sideA; + private final double sideB; + private final double sideC; public Triangle(double sideA, double sideB, double sideC) { this.sideA = sideA; diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index 19bf768..37dfda2 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -7,10 +7,14 @@ import org.example.classes.Example; import org.example.classes.NonGeneratable; import org.example.classes.Product; +import org.example.classes.Rectangle; +import org.example.classes.Triangle; import org.example.generator.type.TypeGeneratorsProvider; import org.example.generator.type.impl.PrimitiveGeneratorsProvider; import org.example.generator.type.impl.StringGeneratorsProvider; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -50,16 +54,20 @@ void shouldThrowExceptionOnNonGeneratableClass() { assertThat(ex.getMessage()).isEqualTo("Class is not annotated with @Generatable"); } - @Test - void shouldGenerateExampleClass() { - var example = generate(Example.class); - assertThat(example).isInstanceOf(Example.class); + @ParameterizedTest + @MethodSource("source") + void shouldGenerateSupportedClasses(Class clazz) { + var instance = generate(clazz); + assertThat(instance).isInstanceOf(clazz); } - @Test - void shouldGenerateProductClass() { - var product = generate(Product.class); - assertThat(product).isInstanceOf(Product.class); + static List> source() { + return List.of( + Example.class, + Product.class, + Rectangle.class, + Triangle.class + ); } private Object generate(Class clazz) { From 12eb2830bd383c774b2dd5ba7a41eb3763517776 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 00:44:27 +0300 Subject: [PATCH 05/14] add generation of simple types and enums --- .../java/org/example/generator/Generator.java | 60 ++++++++++++++++- .../org/example/generator/GeneratorTest.java | 65 ++++++++++++++++++- 2 files changed, 119 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index d1ace6b..80af4ef 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -5,6 +5,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.Random; import java.util.function.Supplier; import org.example.generator.type.TypeGeneratorsProvider; @@ -12,7 +13,11 @@ public class Generator { private final Map, Supplier> generators; - public Generator(Collection providers) { + private final int maxDepth; + + private final Random random = new Random(); + + public Generator(Collection providers, int maxDepth) { Map, Supplier> result = new HashMap<>(); for (TypeGeneratorsProvider provider : providers) { @@ -33,14 +38,41 @@ public Generator(Collection providers) { } this.generators = Map.copyOf(result); + + if (maxDepth <= 0) { + throw new IllegalArgumentException("maxDepth expected to be more than 0, but got " + maxDepth); + } + this.maxDepth = maxDepth; } public Object generateValueOfType( Class clazz ) throws InvocationTargetException, InstantiationException, IllegalAccessException, GenerationException { + return generateValueOfType(clazz, 0); + } + + private Object generateValueOfType( + Class clazz, + int depth + ) throws InvocationTargetException, InstantiationException, IllegalAccessException, GenerationException { + + if (!canBeGenerated(clazz)) { + throw new GenerationException( + "Class is not annotated with @" + Generatable.class.getSimpleName() + " and not a simple type" + ); + } + + // todo add tests + if (depth > maxDepth) { + throw new GenerationException("maxDepth exceeded"); + } - if (!clazz.isAnnotationPresent(Generatable.class)) { - throw new GenerationException("Class is not annotated with @Generatable"); + if (generators.containsKey(clazz)) { + return generators.get(clazz).get(); + } + + if (clazz.isEnum()) { + return generateEnum(clazz); } Constructor[] constructors = clazz.getDeclaredConstructors(); @@ -58,6 +90,28 @@ public Object generateValueOfType( throw new GenerationException("No suitable constructor found for class: " + clazz.getName()); } + private boolean canBeGenerated(Class clazz) { + return generators.containsKey(clazz) || clazz.isEnum() || + clazz.isAnnotationPresent(Generatable.class); + } + + private Object generateEnum(Class enumClass) throws GenerationException { + Object[] values = enumClass.getEnumConstants(); + + if (values == null) { + // should not happen + throw new IllegalStateException("generateEnum received not enum as a parameter"); + } + + if (0 == values.length) { + throw new GenerationException("enum '" + enumClass.getName() + + "' cannot generated, because values is empty" + ); + } + + return values[random.nextInt(values.length)]; + } + private Object tryConstructor( Constructor constructor ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index 37dfda2..faf8f06 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -15,6 +15,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -27,7 +28,21 @@ class GeneratorTest { new StringGeneratorsProvider(random, 15) ); - private final Generator generator = new Generator(providers); + private final Generator generator = new Generator(providers, Integer.MAX_VALUE); + + private enum TestEnum { + ONE("one"), + TWO("two"), + ; + + final String name; + + TestEnum(String name) { + this.name = name; + } + } + + private enum EmptyEnum {} @Test void shouldThrowOnDuplicateGenerator() { @@ -38,20 +53,60 @@ void shouldThrowOnDuplicateGenerator() { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> new Generator(duplicateProviders) + () -> new Generator(duplicateProviders, 1) ); assertThat(exception.getMessage()) .contains("Multiple providers supply generator for type: java.lang.String"); } + @ParameterizedTest + @ValueSource(ints = {-1, 0}) + void shouldThrowOnNegativeOrZeroMaxDepth(int maxDepth) { + IllegalArgumentException exception = assertThrows( + IllegalArgumentException.class, + () -> new Generator(List.of(), maxDepth) + ); + + assertThat(exception.getMessage()) + .isEqualTo("maxDepth expected to be more than 0, but got " + maxDepth); + } + @Test void shouldThrowExceptionOnNonGeneratableClass() { var ex = assertThrows( GenerationException.class, () -> generator.generateValueOfType(NonGeneratable.class) ); - assertThat(ex.getMessage()).isEqualTo("Class is not annotated with @Generatable"); + assertThat(ex.getMessage()).isEqualTo( + "Class is not annotated with @Generatable and not a simple type" + ); + } + + @Test + void shouldGenerateSimpleClassesFromGenerators() { + TypeGeneratorsProvider provider = () -> Map.of(String.class, () -> "test-string"); + Generator generator = new Generator(List.of(provider), 1); + + assertThat(generate(generator, String.class)).isEqualTo("test-string"); + } + + @Test + void shouldGenerateEnum() { + Generator generator = new Generator(List.of(), 1); + + assertThat(generate(generator, TestEnum.class)).isInstanceOf(TestEnum.class); + } + + @Test + void shouldThrowOnEmptyEnum() { + var ex = assertThrows( + GenerationException.class, + () -> generator.generateValueOfType(EmptyEnum.class) + ); + assertThat(ex.getMessage()).isEqualTo("enum '" + this.getClass().getName() + "$" + + EmptyEnum.class.getSimpleName() + "' cannot generated, because values is empty" + ); } @ParameterizedTest @@ -71,6 +126,10 @@ static List> source() { } private Object generate(Class clazz) { + return generate(generator, clazz); + } + + private Object generate(Generator generator, Class clazz) { try { return generator.generateValueOfType(clazz); } catch (Exception e) { From 79b4929900434f4915bcb6383c1727fb302df26e Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 20:27:40 +0300 Subject: [PATCH 06/14] add generation of array --- .../java/org/example/generator/Generator.java | 31 ++++++++++++++++++- .../org/example/generator/GeneratorTest.java | 23 ++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 80af4ef..16be94e 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,5 +1,6 @@ package org.example.generator; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Collection; @@ -75,6 +76,10 @@ private Object generateValueOfType( return generateEnum(clazz); } + if (clazz.isArray()) { + return generateArray(clazz, depth); + } + Constructor[] constructors = clazz.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { Constructor constructor = constructors[i]; @@ -91,10 +96,34 @@ private Object generateValueOfType( } private boolean canBeGenerated(Class clazz) { - return generators.containsKey(clazz) || clazz.isEnum() || + return generators.containsKey(clazz) || + clazz.isEnum() || + clazz.isArray() || clazz.isAnnotationPresent(Generatable.class); } + // todo вынести длину в параметр + private Object generateArray( + Class arrayClass, + int depth + ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { + Class arrayElementClass = arrayClass.getComponentType(); + + if (arrayElementClass == null) { + throw new IllegalStateException("generateArray received not array as a parameter"); + } + + int length = random.nextInt(10); + Object result = Array.newInstance(arrayElementClass, length); + + for (int i = 0; i < length; ++i) { + Object element = generateValueOfType(arrayElementClass, depth + 1); + Array.set(result, i, element); + } + + return result; + } + private Object generateEnum(Class enumClass) throws GenerationException { Object[] values = enumClass.getEnumConstants(); diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index faf8f06..ea3989a 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -109,6 +109,29 @@ void shouldThrowOnEmptyEnum() { ); } + @Test + void shouldGenerateArray() { + Class intArrayClass = int[].class; + assertThat(generate(intArrayClass)).isInstanceOf(intArrayClass); + } + + @Test + void shouldGenerate2DArray() { + Class intArrayClass = int[][].class; + assertThat(generate(intArrayClass)).isInstanceOf(intArrayClass); + } + + @Test + void shouldThrowIfMaxDepthReached() { + TypeGeneratorsProvider provider = () -> Map.of(String.class, () -> "test-string"); + var generator = new Generator(List.of(provider), 1); + var ex = assertThrows( + GenerationException.class, + () -> generator.generateValueOfType(String[][].class) + ); + assertThat(ex.getMessage()).isEqualTo("maxDepth exceeded"); + } + @ParameterizedTest @MethodSource("source") void shouldGenerateSupportedClasses(Class clazz) { From 36d1dbb4bcd1b72b94301686d24256d8a403fd81 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 20:54:13 +0300 Subject: [PATCH 07/14] add generation collection and map --- .../java/org/example/generator/Generator.java | 34 ++++++++++++-- .../org/example/generator/GeneratorTest.java | 47 +++++++++++++++++-- 2 files changed, 72 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 16be94e..650598f 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -3,10 +3,7 @@ import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; +import java.util.*; import java.util.function.Supplier; import org.example.generator.type.TypeGeneratorsProvider; @@ -63,7 +60,7 @@ private Object generateValueOfType( ); } - // todo add tests + // todo replace with null! if (depth > maxDepth) { throw new GenerationException("maxDepth exceeded"); } @@ -80,6 +77,14 @@ private Object generateValueOfType( return generateArray(clazz, depth); } + if (Collection.class.isAssignableFrom(clazz)) { + return generateCollectionFromClass(clazz); + } + + if (Map.class.isAssignableFrom(clazz)) { + return generateMapFromClass(clazz); + } + Constructor[] constructors = clazz.getDeclaredConstructors(); for (int i = 0; i < constructors.length; i++) { Constructor constructor = constructors[i]; @@ -99,6 +104,8 @@ private boolean canBeGenerated(Class clazz) { return generators.containsKey(clazz) || clazz.isEnum() || clazz.isArray() || + Collection.class.isAssignableFrom(clazz) || + Map.class.isAssignableFrom(clazz) || clazz.isAnnotationPresent(Generatable.class); } @@ -141,6 +148,23 @@ private Object generateEnum(Class enumClass) throws GenerationException { return values[random.nextInt(values.length)]; } + private Collection generateCollectionFromClass(Class collectionClass) { + if (Set.class.isAssignableFrom(collectionClass)) { + return new HashSet<>(); + } + if (Queue.class.isAssignableFrom(collectionClass)) { + return new LinkedList<>(); + } + return new ArrayList<>(); + } + + private Map generateMapFromClass(Class collectionClass) { + if (SortedMap.class.isAssignableFrom(collectionClass)) { + return new TreeMap<>(); + } + return new HashMap<>(); + } + private Object tryConstructor( Constructor constructor ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index ea3989a..ea8c3b9 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -1,9 +1,6 @@ package org.example.generator; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Random; +import java.util.*; import org.example.classes.Example; import org.example.classes.NonGeneratable; import org.example.classes.Product; @@ -132,6 +129,48 @@ void shouldThrowIfMaxDepthReached() { assertThat(ex.getMessage()).isEqualTo("maxDepth exceeded"); } + @Test + void shouldGenerateList() { + var result = generate(List.class); + assertThat(result).isInstanceOf(ArrayList.class); + assertThat(((List) result).size()).isEqualTo(0); + } + + @Test + void shouldGenerateSet() { + var result = generate(Set.class); + assertThat(result).isInstanceOf(HashSet.class); + assertThat(((Set) result).size()).isEqualTo(0); + } + + @Test + void shouldGenerateQueue() { + var result = generate(Queue.class); + assertThat(result).isInstanceOf(LinkedList.class); + assertThat(((Queue) result).size()).isEqualTo(0); + } + + @Test + void shouldGenerateCollection() { + var result = generate(Collection.class); + assertThat(result).isInstanceOf(ArrayList.class); + assertThat(((Collection) result).size()).isEqualTo(0); + } + + @Test + void shouldGenerateMap() { + var result = generate(Map.class); + assertThat(result).isInstanceOf(HashMap.class); + assertThat(((Map) result).size()).isEqualTo(0); + } + + @Test + void shouldGenerateSortedMap() { + var result = generate(SortedMap.class); + assertThat(result).isInstanceOf(TreeMap.class); + assertThat(((Map) result).size()).isEqualTo(0); + } + @ParameterizedTest @MethodSource("source") void shouldGenerateSupportedClasses(Class clazz) { From f369b96e253d36aa93b6dfafa7f54732c9a1a644 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 20:56:12 +0300 Subject: [PATCH 08/14] extract test enums to files --- src/test/java/org/example/classes/EmptyEnum.java | 3 +++ src/test/java/org/example/classes/TestEnum.java | 13 +++++++++++++ .../org/example/generator/GeneratorTest.java | 16 ++-------------- 3 files changed, 18 insertions(+), 14 deletions(-) create mode 100644 src/test/java/org/example/classes/EmptyEnum.java create mode 100644 src/test/java/org/example/classes/TestEnum.java diff --git a/src/test/java/org/example/classes/EmptyEnum.java b/src/test/java/org/example/classes/EmptyEnum.java new file mode 100644 index 0000000..890cc3e --- /dev/null +++ b/src/test/java/org/example/classes/EmptyEnum.java @@ -0,0 +1,3 @@ +package org.example.classes; + +public enum EmptyEnum {} diff --git a/src/test/java/org/example/classes/TestEnum.java b/src/test/java/org/example/classes/TestEnum.java new file mode 100644 index 0000000..6749fc4 --- /dev/null +++ b/src/test/java/org/example/classes/TestEnum.java @@ -0,0 +1,13 @@ +package org.example.classes; + +public enum TestEnum { + ONE("one"), + TWO("two"), + ; + + final String name; + + TestEnum(String name) { + this.name = name; + } +} diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index ea8c3b9..65d58bc 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -1,10 +1,12 @@ package org.example.generator; import java.util.*; +import org.example.classes.EmptyEnum; import org.example.classes.Example; import org.example.classes.NonGeneratable; import org.example.classes.Product; import org.example.classes.Rectangle; +import org.example.classes.TestEnum; import org.example.classes.Triangle; import org.example.generator.type.TypeGeneratorsProvider; import org.example.generator.type.impl.PrimitiveGeneratorsProvider; @@ -27,20 +29,6 @@ class GeneratorTest { private final Generator generator = new Generator(providers, Integer.MAX_VALUE); - private enum TestEnum { - ONE("one"), - TWO("two"), - ; - - final String name; - - TestEnum(String name) { - this.name = name; - } - } - - private enum EmptyEnum {} - @Test void shouldThrowOnDuplicateGenerator() { TypeGeneratorsProvider provider1 = () -> Map.of(String.class, () -> "test-string"); From 409af5721e408f9f89a62f9e28b6b20fffb12953 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 20:59:39 +0300 Subject: [PATCH 09/14] fix maxDepth behavior --- src/main/java/org/example/generator/Generator.java | 6 +++--- .../java/org/example/generator/GeneratorTest.java | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 650598f..03542dd 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -60,9 +60,9 @@ private Object generateValueOfType( ); } - // todo replace with null! + // todo primitives cannot be null if (depth > maxDepth) { - throw new GenerationException("maxDepth exceeded"); + return null; } if (generators.containsKey(clazz)) { @@ -120,7 +120,7 @@ private Object generateArray( throw new IllegalStateException("generateArray received not array as a parameter"); } - int length = random.nextInt(10); + int length = random.nextInt(1, 10); Object result = Array.newInstance(arrayElementClass, length); for (int i = 0; i < length; ++i) { diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index 65d58bc..997d7ed 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -107,14 +107,15 @@ void shouldGenerate2DArray() { } @Test - void shouldThrowIfMaxDepthReached() { + void shouldPlaceNullIfMaxDepthReached() { TypeGeneratorsProvider provider = () -> Map.of(String.class, () -> "test-string"); var generator = new Generator(List.of(provider), 1); - var ex = assertThrows( - GenerationException.class, - () -> generator.generateValueOfType(String[][].class) - ); - assertThat(ex.getMessage()).isEqualTo("maxDepth exceeded"); + var result = generate(generator, String[][].class); + + assertThat(result).isInstanceOf(String[][].class); + + var resultTyped = (String[][]) result; + assertThat(resultTyped[0][0]).isNull(); } @Test From c2c3b0f5539885e4928178fc73b95f4f9061142e Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 21:34:07 +0300 Subject: [PATCH 10/14] add generation of internal collections and maps --- .../java/org/example/generator/Generator.java | 104 +++++++++++++++--- .../org/example/classes/BinaryTreeNode.java | 1 - .../org/example/classes/InternalMapTest.java | 17 +++ .../org/example/generator/GeneratorTest.java | 41 +++++-- 4 files changed, 135 insertions(+), 28 deletions(-) create mode 100644 src/test/java/org/example/classes/InternalMapTest.java diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 03542dd..234343e 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -2,7 +2,11 @@ import java.lang.reflect.Array; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.util.*; import java.util.function.Supplier; import org.example.generator.type.TypeGeneratorsProvider; @@ -89,7 +93,7 @@ private Object generateValueOfType( for (int i = 0; i < constructors.length; i++) { Constructor constructor = constructors[i]; try { - return tryConstructor(constructor); + return tryConstructor(constructor, depth); } catch (Exception e) { if (i == constructors.length - 1) { throw e; @@ -148,39 +152,105 @@ private Object generateEnum(Class enumClass) throws GenerationException { return values[random.nextInt(values.length)]; } - private Collection generateCollectionFromClass(Class collectionClass) { - if (Set.class.isAssignableFrom(collectionClass)) { - return new HashSet<>(); - } - if (Queue.class.isAssignableFrom(collectionClass)) { - return new LinkedList<>(); - } - return new ArrayList<>(); + private Collection generateCollectionFromClass(Class collectionClass) { + return switch (collectionClass) { + case Class c when Set.class.isAssignableFrom(c) -> new HashSet<>(); + case Class c when Queue.class.isAssignableFrom(c) -> new LinkedList<>(); + default -> new ArrayList<>(); + }; } - private Map generateMapFromClass(Class collectionClass) { - if (SortedMap.class.isAssignableFrom(collectionClass)) { + private Map generateMapFromClass(Class mapClass) { + if (SortedMap.class.isAssignableFrom(mapClass)) { return new TreeMap<>(); } return new HashMap<>(); } private Object tryConstructor( - Constructor constructor + Constructor constructor, + int depth ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { Object[] paramValues = new Object[constructor.getParameterCount()]; for (int i = 0; i < constructor.getParameterCount(); i++) { Class paramType = constructor.getParameterTypes()[i]; - Supplier supplier = generators.get(paramType); + paramValues[i] = generateValueOfType(paramType, depth + 1); + } - if (supplier == null) { - throw new GenerationException("Unknown type: " + paramType.getName()); + var instance = constructor.newInstance(paramValues); + + var clazz = instance.getClass(); + + for (Field field : clazz.getDeclaredFields()) { + int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) { + continue; } - paramValues[i] = supplier.get(); + field.setAccessible(true); + Class fieldClass = field.getType(); + + Object fieldValue = switch (fieldClass) { + case Class c when Collection.class.isAssignableFrom(c) -> generateCollectionFromField(field, depth); + case Class c when Map.class.isAssignableFrom(c) -> generateMapFromField(field, depth); + default -> generateValueOfType(fieldClass, depth + 1); + }; + + field.set(instance, fieldValue); + } + + return instance; + } + + private Collection generateCollectionFromField( + Field collectionField, + int depth + ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { + Type genericType = collectionField.getGenericType(); + Collection collection = generateCollectionFromClass(collectionField.getType()); + + if (genericType instanceof ParameterizedType parameterizedType) { + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + // if not generic, then fill + if (typeArgs.length == 1 && typeArgs[0] instanceof Class elementType) { + int length = random.nextInt(1, 10); + + for (int i = 0; i < length; ++i) { + Object element = generateValueOfType(elementType, depth + 1); + collection.add(element); + } + } + } + + return collection; + } + + private Map generateMapFromField( + Field mapField, + int depth + ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { + Type genericType = mapField.getGenericType(); + Map map = generateMapFromClass(mapField.getType()); + + if (genericType instanceof ParameterizedType parameterizedType) { + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + if (typeArgs.length == 2 && + typeArgs[0] instanceof Class keyType && + typeArgs[1] instanceof Class valueType) { + + int size = random.nextInt(1, 10); + + for (int i = 0; i < size; i++) { + Object key = generateValueOfType(keyType, depth + 1); + Object value = generateValueOfType(valueType, depth + 1); + map.put(key, value); + } + } } - return constructor.newInstance(paramValues); + return map; } } diff --git a/src/test/java/org/example/classes/BinaryTreeNode.java b/src/test/java/org/example/classes/BinaryTreeNode.java index 8dfa863..52a9157 100644 --- a/src/test/java/org/example/classes/BinaryTreeNode.java +++ b/src/test/java/org/example/classes/BinaryTreeNode.java @@ -2,7 +2,6 @@ import org.example.generator.Generatable; -// todo recursion @Generatable public class BinaryTreeNode { private Integer data; diff --git a/src/test/java/org/example/classes/InternalMapTest.java b/src/test/java/org/example/classes/InternalMapTest.java new file mode 100644 index 0000000..c80da83 --- /dev/null +++ b/src/test/java/org/example/classes/InternalMapTest.java @@ -0,0 +1,17 @@ +package org.example.classes; + +import java.util.Map; +import org.example.generator.Generatable; + +@Generatable +public class InternalMapTest { + private Map items; + + public Map getItems() { + return items; + } + + public void setItems(Map items) { + this.items = items; + } +} diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index 997d7ed..3d2d250 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -1,13 +1,7 @@ package org.example.generator; import java.util.*; -import org.example.classes.EmptyEnum; -import org.example.classes.Example; -import org.example.classes.NonGeneratable; -import org.example.classes.Product; -import org.example.classes.Rectangle; -import org.example.classes.TestEnum; -import org.example.classes.Triangle; +import org.example.classes.*; import org.example.generator.type.TypeGeneratorsProvider; import org.example.generator.type.impl.PrimitiveGeneratorsProvider; import org.example.generator.type.impl.StringGeneratorsProvider; @@ -27,7 +21,7 @@ class GeneratorTest { new StringGeneratorsProvider(random, 15) ); - private final Generator generator = new Generator(providers, Integer.MAX_VALUE); + private final Generator generator = new Generator(providers, 10); @Test void shouldThrowOnDuplicateGenerator() { @@ -89,8 +83,8 @@ void shouldThrowOnEmptyEnum() { GenerationException.class, () -> generator.generateValueOfType(EmptyEnum.class) ); - assertThat(ex.getMessage()).isEqualTo("enum '" + this.getClass().getName() + "$" + - EmptyEnum.class.getSimpleName() + "' cannot generated, because values is empty" + assertThat(ex.getMessage()).isEqualTo("enum '" + EmptyEnum.class.getName() + + "' cannot generated, because values is empty" ); } @@ -160,6 +154,31 @@ void shouldGenerateSortedMap() { assertThat(((Map) result).size()).isEqualTo(0); } + @Test + void shouldFillInternalCollection() { + var instance = generate(Cart.class); + assertThat(instance).isInstanceOf(Cart.class); + + var cart = (Cart) instance; + var items = cart.getItems(); + assertThat(items.size()).isNotEqualTo(0); + assertThat(items.getFirst()).isInstanceOf(Product.class); + } + + @Test + void shouldFillInternalMap() { + var instance = generate(InternalMapTest.class); + assertThat(instance).isInstanceOf(InternalMapTest.class); + + var cart = (InternalMapTest) instance; + var items = cart.getItems(); + assertThat(items.size()).isNotEqualTo(0); + + Map.Entry item = items.entrySet().iterator().next(); + assertThat(item.getKey()).isInstanceOf(Product.class); + assertThat(item.getValue()).isInstanceOf(String.class); + } + @ParameterizedTest @MethodSource("source") void shouldGenerateSupportedClasses(Class clazz) { @@ -171,6 +190,8 @@ static List> source() { return List.of( Example.class, Product.class, + Cart.class, + BinaryTreeNode.class, Rectangle.class, Triangle.class ); From 41282ccc1cdddfe512d301cc735accf8e57fae00 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 22:21:32 +0300 Subject: [PATCH 11/14] add package for scanInApi --- src/main/java/org/example/generator/Generator.java | 9 ++++++++- .../java/org/example/generator/GeneratorTest.java | 13 +++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 234343e..c4842f9 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -16,10 +16,15 @@ public class Generator { private final Map, Supplier> generators; private final int maxDepth; + private final String packageToScan; private final Random random = new Random(); - public Generator(Collection providers, int maxDepth) { + public Generator( + Collection providers, + int maxDepth, + Object packageMarker + ) { Map, Supplier> result = new HashMap<>(); for (TypeGeneratorsProvider provider : providers) { @@ -45,6 +50,8 @@ public Generator(Collection providers, int maxDepth) { throw new IllegalArgumentException("maxDepth expected to be more than 0, but got " + maxDepth); } this.maxDepth = maxDepth; + + this.packageToScan = packageMarker.getClass().getPackageName(); } public Object generateValueOfType( diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index 3d2d250..ec86c58 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -20,8 +20,9 @@ class GeneratorTest { new PrimitiveGeneratorsProvider(random), new StringGeneratorsProvider(random, 15) ); + private final Object marker = TestEnum.ONE; - private final Generator generator = new Generator(providers, 10); + private final Generator generator = new Generator(providers, 10, marker); @Test void shouldThrowOnDuplicateGenerator() { @@ -32,7 +33,7 @@ void shouldThrowOnDuplicateGenerator() { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> new Generator(duplicateProviders, 1) + () -> new Generator(duplicateProviders, 1, marker) ); assertThat(exception.getMessage()) @@ -44,7 +45,7 @@ void shouldThrowOnDuplicateGenerator() { void shouldThrowOnNegativeOrZeroMaxDepth(int maxDepth) { IllegalArgumentException exception = assertThrows( IllegalArgumentException.class, - () -> new Generator(List.of(), maxDepth) + () -> new Generator(List.of(), maxDepth, marker) ); assertThat(exception.getMessage()) @@ -65,14 +66,14 @@ void shouldThrowExceptionOnNonGeneratableClass() { @Test void shouldGenerateSimpleClassesFromGenerators() { TypeGeneratorsProvider provider = () -> Map.of(String.class, () -> "test-string"); - Generator generator = new Generator(List.of(provider), 1); + Generator generator = new Generator(List.of(provider), 1, marker); assertThat(generate(generator, String.class)).isEqualTo("test-string"); } @Test void shouldGenerateEnum() { - Generator generator = new Generator(List.of(), 1); + Generator generator = new Generator(List.of(), 1, marker); assertThat(generate(generator, TestEnum.class)).isInstanceOf(TestEnum.class); } @@ -103,7 +104,7 @@ void shouldGenerate2DArray() { @Test void shouldPlaceNullIfMaxDepthReached() { TypeGeneratorsProvider provider = () -> Map.of(String.class, () -> "test-string"); - var generator = new Generator(List.of(provider), 1); + var generator = new Generator(List.of(provider), 1, marker); var result = generate(generator, String[][].class); assertThat(result).isInstanceOf(String[][].class); From 253dd97bb5f923c45cb83898eac0dd7553e195b0 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 23:03:55 +0300 Subject: [PATCH 12/14] add PackageUtils --- build.gradle.kts | 1 + .../org/example/generator/PackageUtils.java | 80 +++++++++++++++++++ .../example/generator/PackageUtilsTest.java | 63 +++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 src/main/java/org/example/generator/PackageUtils.java create mode 100644 src/test/java/org/example/generator/PackageUtilsTest.java diff --git a/build.gradle.kts b/build.gradle.kts index 0b2c701..bc98873 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -20,6 +20,7 @@ dependencies { testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.assertj:assertj-core:3.27.6") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + testImplementation("net.bytebuddy:byte-buddy:1.17.8") } tasks.test { diff --git a/src/main/java/org/example/generator/PackageUtils.java b/src/main/java/org/example/generator/PackageUtils.java new file mode 100644 index 0000000..cc0d59c --- /dev/null +++ b/src/main/java/org/example/generator/PackageUtils.java @@ -0,0 +1,80 @@ +package org.example.generator; + +import java.io.File; +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +public class PackageUtils { + + private PackageUtils() { + throw new IllegalStateException("Utility class"); + } + + public static Set> getClassesInPackage(String packageName, ClassLoader classLoader) + throws IOException, URISyntaxException { + Set> classes = new HashSet<>(); + String path = packageName.replace('.', '/'); + Enumeration resources = classLoader.getResources(path); + + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + if ("file".equals(resource.getProtocol())) { + File directory = new File(resource.toURI()); + scanDirectory(directory, packageName, classes, classLoader); + } else if ("jar".equals(resource.getProtocol())) { + JarFile jar = ((JarURLConnection) resource.openConnection()).getJarFile(); + scanJar(jar, packageName, classes, classLoader); + } + } + + return classes; + } + + private static void scanDirectory(File dir, String packageName, Set> classes, ClassLoader classLoader) { + File[] files = dir.listFiles(); + if (files == null) return; + + for (File file : files) { + if (file.isDirectory()) { + String subPackage = packageName.isEmpty() ? file.getName() : packageName + "." + file.getName(); + scanDirectory(file, subPackage, classes, classLoader); + } else if (file.getName().endsWith(".class")) { + String className = file.getName().substring(0, file.getName().length() - 6); + if (!packageName.isEmpty()) { + className = packageName + "." + className; + } + try { + classes.add(classLoader.loadClass(className)); + } catch (ClassNotFoundException e) { + // ignore + } + } + } + } + + private static void scanJar(JarFile jar, String packageName, Set> classes, ClassLoader classLoader) { + String packagePath = packageName.replace('.', '/'); + Enumeration entries = jar.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + + if (name.startsWith(packagePath) && name.endsWith(".class") && !entry.isDirectory()) { + String className = name.substring(0, name.length() - 6).replace('/', '.'); + try { + classes.add(classLoader.loadClass(className)); + } catch (ClassNotFoundException e) { + // ignore + } + } + } + } +} diff --git a/src/test/java/org/example/generator/PackageUtilsTest.java b/src/test/java/org/example/generator/PackageUtilsTest.java new file mode 100644 index 0000000..8341065 --- /dev/null +++ b/src/test/java/org/example/generator/PackageUtilsTest.java @@ -0,0 +1,63 @@ +package org.example.generator; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.DynamicType; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +class PackageUtilsTest { + + @TempDir + private Path tempDir; + + private URL tempUrl; + + private final ByteBuddy byteBuddy = new ByteBuddy(); + private final String packageName = "org.example.kek"; + + @BeforeEach + void setUp() throws IOException { + + saveClassUsingByteBuddy(packageName + ".Service1"); + saveClassUsingByteBuddy(packageName + ".Service2"); + saveClassUsingByteBuddy(packageName + ".utils.Helper"); + + tempUrl = tempDir.toUri().toURL(); + } + + private void saveClassUsingByteBuddy(String name) throws IOException { + try (DynamicType object = byteBuddy.subclass(Object.class).name(name).make()) { + object.saveIn(tempDir.toFile()); + } + } + + @Test + void shouldFindAllImpl() throws IOException, URISyntaxException { + try (var classLoader = new URLClassLoader( + new URL[]{tempUrl}, + Thread.currentThread().getContextClassLoader() + ) + ) { + Set> result = PackageUtils.getClassesInPackage(packageName, classLoader); + + assertThat(result.size()).isEqualTo(3); + List.of( + packageName + ".Service1", + packageName + ".Service2", + packageName + ".utils.Helper" + ).forEach(name -> assertThat( + result.stream().anyMatch(c -> c.getName().equals(name)) + ).isTrue() + ); + } + } +} From fad8ac6bf691b74afa26c3f51b248804003f0679 Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 23:15:12 +0300 Subject: [PATCH 13/14] add interface generation --- .../java/org/example/generator/Generator.java | 65 +++++++++++++++---- .../example/classes/InterfaceWithNoImpl.java | 7 ++ .../org/example/generator/GeneratorTest.java | 13 +++- 3 files changed, 73 insertions(+), 12 deletions(-) create mode 100644 src/test/java/org/example/classes/InterfaceWithNoImpl.java diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index c4842f9..800a7b4 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,5 +1,6 @@ package org.example.generator; +import java.io.IOException; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; @@ -7,6 +8,7 @@ import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; +import java.net.URISyntaxException; import java.util.*; import java.util.function.Supplier; import org.example.generator.type.TypeGeneratorsProvider; @@ -17,6 +19,7 @@ public class Generator { private final int maxDepth; private final String packageToScan; + private final Set> classesInPackageToScan; private final Random random = new Random(); @@ -52,6 +55,15 @@ public Generator( this.maxDepth = maxDepth; this.packageToScan = packageMarker.getClass().getPackageName(); + // todo add test + try { + this.classesInPackageToScan = PackageUtils.getClassesInPackage( + packageToScan, + Thread.currentThread().getContextClassLoader() + ); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } } public Object generateValueOfType( @@ -96,19 +108,14 @@ private Object generateValueOfType( return generateMapFromClass(clazz); } - Constructor[] constructors = clazz.getDeclaredConstructors(); - for (int i = 0; i < constructors.length; i++) { - Constructor constructor = constructors[i]; - try { - return tryConstructor(constructor, depth); - } catch (Exception e) { - if (i == constructors.length - 1) { - throw e; - } - } + if (clazz.isInterface()) { + Class implementationClass = findImplementationClass(clazz).orElseThrow( + () -> new GenerationException("No implementation found for interface " + clazz.getName()) + ); + return generateValueOfType(implementationClass, depth); // not incrementing depth on purpose } - throw new GenerationException("No suitable constructor found for class: " + clazz.getName()); + return generateCommonClass(clazz, depth); } private boolean canBeGenerated(Class clazz) { @@ -174,6 +181,42 @@ private Map generateMapFromClass(Class mapClass) { return new HashMap<>(); } + private Optional> findImplementationClass(Class interfaceClass) { + if (!interfaceClass.getPackageName().startsWith(packageToScan)) { + return Optional.empty(); + } + + List> implementations = classesInPackageToScan.stream().filter(c -> + interfaceClass.isAssignableFrom(c) && + c.isAnnotationPresent(Generatable.class) && + !c.isInterface() && + !Modifier.isAbstract(c.getModifiers()) + ).toList(); + if (implementations.isEmpty()) { + return Optional.empty(); + } + return Optional.of(implementations.get(random.nextInt(implementations.size()))); + } + + private Object generateCommonClass( + Class clazz, + int depth + ) throws GenerationException, InvocationTargetException, InstantiationException, IllegalAccessException { + Constructor[] constructors = clazz.getDeclaredConstructors(); + for (int i = 0; i < constructors.length; i++) { + Constructor constructor = constructors[i]; + try { + return tryConstructor(constructor, depth); + } catch (Exception e) { + if (i == constructors.length - 1) { + throw e; + } + } + } + + throw new GenerationException("No suitable constructor found for class: " + clazz.getName()); + } + private Object tryConstructor( Constructor constructor, int depth diff --git a/src/test/java/org/example/classes/InterfaceWithNoImpl.java b/src/test/java/org/example/classes/InterfaceWithNoImpl.java new file mode 100644 index 0000000..dd4fd53 --- /dev/null +++ b/src/test/java/org/example/classes/InterfaceWithNoImpl.java @@ -0,0 +1,7 @@ +package org.example.classes; + +import org.example.generator.Generatable; + +@Generatable +public interface InterfaceWithNoImpl { +} diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java index ec86c58..6c1bb16 100644 --- a/src/test/java/org/example/generator/GeneratorTest.java +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -180,6 +180,16 @@ void shouldFillInternalMap() { assertThat(item.getValue()).isInstanceOf(String.class); } + @Test + void shouldThrowOnInterfaceWithNotImpl() { + var clazz = InterfaceWithNoImpl.class; + var ex = assertThrows( + GenerationException.class, + () -> generator.generateValueOfType(clazz) + ); + assertThat(ex.getMessage()).isEqualTo("No implementation found for interface " + clazz.getName()); + } + @ParameterizedTest @MethodSource("source") void shouldGenerateSupportedClasses(Class clazz) { @@ -194,7 +204,8 @@ static List> source() { Cart.class, BinaryTreeNode.class, Rectangle.class, - Triangle.class + Triangle.class, + Shape.class ); } From c6952f86e038196094314c380fd5873a7eecfc6b Mon Sep 17 00:00:00 2001 From: "ar.r.lysenko" Date: Sat, 25 Oct 2025 23:16:57 +0300 Subject: [PATCH 14/14] update types --- src/main/java/org/example/generator/PackageUtils.java | 2 +- types.md | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/example/generator/PackageUtils.java b/src/main/java/org/example/generator/PackageUtils.java index cc0d59c..cf38b77 100644 --- a/src/main/java/org/example/generator/PackageUtils.java +++ b/src/main/java/org/example/generator/PackageUtils.java @@ -11,7 +11,7 @@ import java.util.jar.JarEntry; import java.util.jar.JarFile; -public class PackageUtils { +public final class PackageUtils { private PackageUtils() { throw new IllegalStateException("Utility class"); diff --git a/types.md b/types.md index 1d51475..cb2c9bc 100644 --- a/types.md +++ b/types.md @@ -2,8 +2,8 @@ конструкторе - [X] [Product.java](src/test/java/org/example/classes/Product.java): конструктор принимает на вход не все поля, примитивы -- [ ] [Cart.java](src/test/java/org/example/classes/Cart.java): конструктор принимает на вход класс из classpath -- [ ] [BinaryTreeNode.java](src/test/java/org/example/classes/BinaryTreeNode.java): рекурсия -- [ ] [Shape.java](src/test/java/org/example/classes/Shape.java): интерфейс (предположительно выбираем любую реализация) - - [ ] [Rectangle.java](src/test/java/org/example/classes/Rectangle.java): реализация один - - [ ] [Triangle.java](src/test/java/org/example/classes/Triangle.java): реализация два +- [X] [Cart.java](src/test/java/org/example/classes/Cart.java): конструктор принимает на вход класс из classpath +- [X] [BinaryTreeNode.java](src/test/java/org/example/classes/BinaryTreeNode.java): рекурсия +- [X] [Shape.java](src/test/java/org/example/classes/Shape.java): интерфейс (предположительно выбираем любую реализация) + - [X] [Rectangle.java](src/test/java/org/example/classes/Rectangle.java): реализация один + - [X] [Triangle.java](src/test/java/org/example/classes/Triangle.java): реализация два