From 14cc9eca78d1d6701c1ae41b95e11e9a7ae6c340 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:47:40 +0000 Subject: [PATCH 1/3] 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 cb36972dd9bddc3dc1b74984c1d13067d269ebbb Mon Sep 17 00:00:00 2001 From: "a.k.lysenko" Date: Fri, 10 Oct 2025 16:57:38 +0300 Subject: [PATCH 2/3] generator almost works: should add support for non-primitives types as fields and write tests --- build.gradle.kts | 9 ++ .../java/org/example/GenerateExample.java | 7 +- .../org/example/annotation/Generatable.java | 11 +++ .../org/example/classes/AbstractExample.java | 12 +++ .../org/example/classes/BinaryTreeNode.java | 12 +++ src/main/java/org/example/classes/Cart.java | 12 ++- .../java/org/example/classes/Example.java | 13 ++- .../java/org/example/classes/Product.java | 9 +- .../java/org/example/classes/Rectangle.java | 8 ++ src/main/java/org/example/classes/Shape.java | 3 + .../java/org/example/classes/Triangle.java | 9 ++ .../java/org/example/generator/Generator.java | 79 +++++++++++++++++ .../generator/util/ReflectionUtil.java | 84 +++++++++++++++++++ 13 files changed, 261 insertions(+), 7 deletions(-) create mode 100644 src/main/java/org/example/annotation/Generatable.java create mode 100644 src/main/java/org/example/classes/AbstractExample.java create mode 100644 src/main/java/org/example/generator/util/ReflectionUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index b4738ae..a622996 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,6 +9,15 @@ repositories { mavenCentral() } +java { + sourceCompatibility = JavaVersion.VERSION_21 +} + +dependencies { + implementation("org.slf4j:slf4j-api:2.0.17") + implementation("ch.qos.logback:logback-classic:1.5.18") +} + dependencies { testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..c56f5ee 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,14 +1,19 @@ package org.example; +import org.example.classes.BinaryTreeNode; +import org.example.classes.Cart; import org.example.classes.Example; +import org.example.classes.Product; +import org.example.classes.Shape; +import org.example.classes.Triangle; 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); + Object generated = gen.generateByType(Cart.class, "org.example"); System.out.println(generated); } catch (Throwable e) { throw new RuntimeException(e); diff --git a/src/main/java/org/example/annotation/Generatable.java b/src/main/java/org/example/annotation/Generatable.java new file mode 100644 index 0000000..81feeeb --- /dev/null +++ b/src/main/java/org/example/annotation/Generatable.java @@ -0,0 +1,11 @@ +package org.example.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Generatable { +} diff --git a/src/main/java/org/example/classes/AbstractExample.java b/src/main/java/org/example/classes/AbstractExample.java new file mode 100644 index 0000000..960ae33 --- /dev/null +++ b/src/main/java/org/example/classes/AbstractExample.java @@ -0,0 +1,12 @@ +package org.example.classes; + +import org.example.annotation.Generatable; + +@Generatable +public class AbstractExample { + protected int x; + + public AbstractExample(int x) { + this.x = x; + } +} diff --git a/src/main/java/org/example/classes/BinaryTreeNode.java b/src/main/java/org/example/classes/BinaryTreeNode.java index 046ff56..431197d 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.annotation.Generatable; + +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; @@ -30,4 +33,13 @@ public void setLeft(BinaryTreeNode left) { public void setRight(BinaryTreeNode right) { this.right = right; } + + @Override + public String toString() { + return "BinaryTreeNode{" + + "data=" + data + + ", left=" + left + + ", right=" + right + + '}'; + } } diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..a82884e 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.annotation.Generatable; + import java.util.List; +@Generatable public class Cart { private List items; @@ -17,5 +20,12 @@ public void setItems(List items) { this.items = items; } - // Конструктор, методы добавления и удаления товаров, геттеры и другие методы +// Конструктор, методы добавления и удаления товаров, геттеры и другие методы + + @Override + public String toString() { + return "Cart{" + + "items=" + items + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/org/example/classes/Example.java b/src/main/java/org/example/classes/Example.java index eac9463..86b8418 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/main/java/org/example/classes/Example.java @@ -1,14 +1,21 @@ package org.example.classes; -public class Example { +import org.example.annotation.Generatable; + +//@Generatable +public class Example extends AbstractExample { int i; - public Example(int i) { + public Example(int i, int x) { + super(x); this.i = i; } @Override public String toString() { - return "Example(" + i + ")"; + return "Example{" + + "i=" + i + + ", x=" + x + + '}'; } } diff --git a/src/main/java/org/example/classes/Product.java b/src/main/java/org/example/classes/Product.java index e7dcc89..6a066d3 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.annotation.Generatable; + +@Generatable public class Product { private String name; private double price; @@ -42,7 +45,9 @@ public boolean equals(Object obj) { @Override public String toString() { - return super.toString(); + return "Product{" + + "name='" + name + '\'' + + ", price=" + price + + '}'; } - } diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..e0d6bdf 100644 --- a/src/main/java/org/example/classes/Rectangle.java +++ b/src/main/java/org/example/classes/Rectangle.java @@ -18,4 +18,12 @@ public double getArea() { public double getPerimeter() { return 2 * (length + width); } + + @Override + public String toString() { + return "Rectangle{" + + "length=" + length + + ", width=" + width + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/org/example/classes/Shape.java b/src/main/java/org/example/classes/Shape.java index c20a851..ee60ced 100644 --- a/src/main/java/org/example/classes/Shape.java +++ b/src/main/java/org/example/classes/Shape.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.annotation.Generatable; + +@Generatable public interface Shape { double getArea(); double getPerimeter(); diff --git a/src/main/java/org/example/classes/Triangle.java b/src/main/java/org/example/classes/Triangle.java index 011e96f..cae61f2 100644 --- a/src/main/java/org/example/classes/Triangle.java +++ b/src/main/java/org/example/classes/Triangle.java @@ -21,4 +21,13 @@ public double getArea() { public double getPerimeter() { return sideA + sideB + sideC; } + + @Override + public String toString() { + return "Triangle{" + + "sideA=" + sideA + + ", sideB=" + sideB + + ", sideC=" + sideC + + '}'; + } } \ No newline at end of file diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 9d86bfb..65661b9 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,11 +1,50 @@ package org.example.generator; +import org.example.annotation.Generatable; + import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; +import java.net.URISyntaxException; +import java.util.Map; import java.util.Random; +import java.util.Set; +import java.util.concurrent.ThreadLocalRandom; +import java.util.function.Function; +import java.util.function.Supplier; + +import static org.example.generator.util.ReflectionUtil.isClassOrParentAnnotated; public class Generator { + private static final Map, Supplier> PRIMITIVE_TYPES_GENERATORS = Map.ofEntries( + Map.entry(int.class, () -> new Random().nextInt(100)), + Map.entry(Integer.class, () -> new Random().nextInt(100)), + Map.entry(long.class, () -> new Random().nextLong(100)), + Map.entry(Long.class, () -> new Random().nextLong(100)), + Map.entry(double.class, () -> new Random().nextDouble(100)), + Map.entry(Double.class, () -> new Random().nextDouble(100)), + Map.entry(float.class, () -> new Random().nextFloat(100)), + Map.entry(Float.class, () -> new Random().nextFloat(100)), + Map.entry(boolean.class, () -> new Random().nextBoolean()), + Map.entry(Boolean.class, () -> new Random().nextBoolean()), + Map.entry(char.class, () -> (char) (new Random().nextInt(26) + 'a')), + Map.entry(Character.class, () -> (char) (new Random().nextInt(26) + 'a')), + Map.entry(String.class, () -> { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int length = 10; + char[] chars = new char[length]; + + for (int i = 0; i < length; i++) { + boolean lower = random.nextBoolean(); + char left = lower ? 'a' : 'A'; + char right = lower ? 'z' : 'Z'; + chars[i] = (char) random.nextInt(left, right + 1); + } + + return new String(chars); + }) + ); + public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { Constructor[] constructors = clazz.getDeclaredConstructors(); @@ -14,5 +53,45 @@ public Object generateValueOfType(Class clazz) throws InvocationTargetExcepti return randomConstructor.newInstance(111); } + public Object generateByType(Class clazz, String packageName) throws IllegalArgumentException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + if (!isClassOrParentAnnotated(clazz, Generatable.class, packageName)) { + throw new IllegalArgumentException(clazz.getCanonicalName() + " is not annotated with @" + Generatable.class.getCanonicalName()); + } + return createInstance(clazz); + } + + private static Object createInstance(Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + var constructors = clazz.getDeclaredConstructors(); + var randomConstructorIndex = constructors.length == 0 ? 0 : new Random().nextInt(constructors.length); + var constructor = constructors[randomConstructorIndex]; + constructor.setAccessible(true); + + var paramTypes = constructor.getParameterTypes(); + var params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { + if (PRIMITIVE_TYPES_GENERATORS.containsKey(paramTypes[i])) { + params[i] = PRIMITIVE_TYPES_GENERATORS.get(paramTypes[i]).get(); + } else { + //todo а что если параметр - класс, помеченный аннотацией? надо рекурсивно создавать объекты до какой-то глубины + params[i] = null; + } + } + + return constructor.newInstance(params); + } } + +/*todo описание алгоритма работы генератора: +* 1) Проверить чем является переданный класс - классом или интерфейсом +* + Если интерфейс, то: +* а) Если ни одной реализации интерфейса нет, то выбросить исключение +* б) Найти любую реализацию интерфейса и выбрать ее для создания объекта +* + Если класс, то: +* a) Дойти до максимального родителя (Object) и посмотреть есть ли у него аннотация Generatable +* - Если нет, то выбросить исключение +* 2) Теперь мы выбрали класс, на основе которого будет создаваться объект +* +* +* + */ diff --git a/src/main/java/org/example/generator/util/ReflectionUtil.java b/src/main/java/org/example/generator/util/ReflectionUtil.java new file mode 100644 index 0000000..19276fa --- /dev/null +++ b/src/main/java/org/example/generator/util/ReflectionUtil.java @@ -0,0 +1,84 @@ +package org.example.generator.util; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Random; + +public class ReflectionUtil { + + private static final Logger log = LoggerFactory.getLogger(ReflectionUtil.class); + + public static boolean isClassOrParentAnnotated( + final Class clazz, + final Class annotation, + final String packageName + ) { + var clazzCopy = clazz; + var packageNameForClasses = new StringBuilder().append(packageName).append(".classes"); + + var isInterfaceAnnotated = false; + + if (clazzCopy.isInterface()) { + isInterfaceAnnotated = clazzCopy.isAnnotationPresent(annotation); + var implementations = findAllInterfaceImplementations(clazzCopy, packageNameForClasses.toString()); + clazzCopy = implementations.get(new Random().nextInt(implementations.size())); + log.debug("Was given an interface = {}, chosen implementation = {}", clazz.getCanonicalName(), clazzCopy.getCanonicalName()); + } + + while (!clazzCopy.equals(Object.class)) { + var implementedInterfaces = Arrays.asList(clazzCopy.getInterfaces()); + if (clazzCopy.isAnnotationPresent(annotation) || + isInterfaceAnnotated || + implementedInterfaces.stream().anyMatch(i -> i.isAnnotationPresent(annotation)) + ) { + return true; + } + clazzCopy = clazzCopy.getSuperclass(); + } + return false; + } + + public static List> findAllInterfaceImplementations(Class interfaceClass, String packageName) { + var path = packageName.replace('.', '/'); + var classLoader = Thread.currentThread().getContextClassLoader(); + var resource = classLoader.getResource(path); + if (resource == null) { + return Collections.emptyList(); + } + + File dir; + try { + dir = new File(resource.toURI()); + } catch (URISyntaxException e) { + log.error("Invalid URI syntax: {}", e.getMessage()); + return Collections.emptyList(); + } + + var implementations = new ArrayList>(); + + for (var file : Objects.requireNonNull(dir.listFiles())) { + if (file.getName().endsWith(".class")) { + var className = packageName + "." + file.getName().replace(".class", ""); + try { + var clazz = Class.forName(className); + if (interfaceClass.isAssignableFrom(clazz) && !clazz.isInterface()) { + implementations.add(clazz); + } + } catch (ClassNotFoundException e) { + log.error("Class not found: {}", e.getMessage()); + } + } + } + + return implementations; + } +} From 760e4bbaaf90dfd3474d41b7fe1505283325504f Mon Sep 17 00:00:00 2001 From: "a.k.lysenko" Date: Mon, 13 Oct 2025 19:32:39 +0300 Subject: [PATCH 3/3] add support for non-primitive types with max_depth and add collections support --- .../java/org/example/GenerateExample.java | 46 +++++- .../org/example/classes/AbstractExample.java | 2 +- src/main/java/org/example/classes/Cart.java | 6 +- .../java/org/example/classes/Example.java | 7 +- .../java/org/example/generator/Generator.java | 142 ++++++++++++++---- .../generator/util/ReflectionUtil.java | 20 +-- 6 files changed, 175 insertions(+), 48 deletions(-) diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index c56f5ee..5110096 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -8,15 +8,57 @@ import org.example.classes.Shape; import org.example.classes.Triangle; import org.example.generator.Generator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; public class GenerateExample { + private static final String PACKAGE_NAME = "org.example"; + private static final Logger log = LoggerFactory.getLogger(GenerateExample.class); + public static void main(String[] args) { var gen = new Generator(); try { - Object generated = gen.generateByType(Cart.class, "org.example"); - System.out.println(generated); + generateWithAnnotatedAbstractClass(gen); + generateRandomInterfaceImplementation(gen); + generateRecursiveDataStructure(gen); + generateSimpleClass(gen); + generateWithCollections(gen); + generateTriangle(gen); } catch (Throwable e) { throw new RuntimeException(e); } } + + private static void generateWithAnnotatedAbstractClass(Generator generator) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + Object generated = generator.generateByType(Example.class, PACKAGE_NAME); + log.info("Generated: {}", generated); + } + + private static void generateRandomInterfaceImplementation(Generator generator) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + Object generated = generator.generateByType(Shape.class, PACKAGE_NAME); + log.info("Generated: {}", generated); + } + + private static void generateRecursiveDataStructure(Generator generator) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + Object generated = generator.generateByType(BinaryTreeNode.class, PACKAGE_NAME); + log.info("Generated: {}", generated); + } + + private static void generateSimpleClass(Generator generator) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + Object generated = generator.generateByType(Product.class, PACKAGE_NAME); + log.info("Generated: {}", generated); + } + + private static void generateWithCollections(Generator generator) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + Object generated = generator.generateByType(Cart.class, PACKAGE_NAME); + log.info("Generated: {}", generated); + } + + private static void generateTriangle(Generator generator) throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { + Triangle generated = (Triangle) generator.generateByType(Triangle.class, PACKAGE_NAME); + log.info("Generated: {}", generated); + log.info("Triangle perimeter is = {}", generated.getPerimeter()); + } } \ No newline at end of file diff --git a/src/main/java/org/example/classes/AbstractExample.java b/src/main/java/org/example/classes/AbstractExample.java index 960ae33..9113726 100644 --- a/src/main/java/org/example/classes/AbstractExample.java +++ b/src/main/java/org/example/classes/AbstractExample.java @@ -3,7 +3,7 @@ import org.example.annotation.Generatable; @Generatable -public class AbstractExample { +public abstract class AbstractExample { protected int x; public AbstractExample(int x) { diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index a82884e..3896913 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/main/java/org/example/classes/Cart.java @@ -3,13 +3,16 @@ import org.example.annotation.Generatable; import java.util.List; +import java.util.Map; @Generatable public class Cart { private List items; + private Map products; - public Cart(List items) { + public Cart(List items,Map products) { this.items = items; + this.products = products; } public List getItems() { @@ -26,6 +29,7 @@ public void setItems(List items) { public String toString() { return "Cart{" + "items=" + items + + ", products=" + products + '}'; } } \ No newline at end of file diff --git a/src/main/java/org/example/classes/Example.java b/src/main/java/org/example/classes/Example.java index 86b8418..0f0e998 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/main/java/org/example/classes/Example.java @@ -1,8 +1,5 @@ package org.example.classes; -import org.example.annotation.Generatable; - -//@Generatable public class Example extends AbstractExample { int i; @@ -11,6 +8,10 @@ public Example(int i, int x) { this.i = i; } + public Example(int x) { + super(x); + } + @Override public String toString() { return "Example{" + diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 65661b9..b8ad5f6 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -3,19 +3,24 @@ import org.example.annotation.Generatable; import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.net.URISyntaxException; +import java.lang.reflect.ParameterizedType; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.ThreadLocalRandom; -import java.util.function.Function; import java.util.function.Supplier; -import static org.example.generator.util.ReflectionUtil.isClassOrParentAnnotated; +import static org.example.generator.util.ReflectionUtil.getAnnotatedClass; public class Generator { + private static final int MAX_DEPTH = 2; + private static final Map, Supplier> PRIMITIVE_TYPES_GENERATORS = Map.ofEntries( Map.entry(int.class, () -> new Random().nextInt(100)), Map.entry(Integer.class, () -> new Random().nextInt(100)), @@ -29,39 +34,38 @@ public class Generator { Map.entry(Boolean.class, () -> new Random().nextBoolean()), Map.entry(char.class, () -> (char) (new Random().nextInt(26) + 'a')), Map.entry(Character.class, () -> (char) (new Random().nextInt(26) + 'a')), - Map.entry(String.class, () -> { - ThreadLocalRandom random = ThreadLocalRandom.current(); - int length = 10; - char[] chars = new char[length]; - - for (int i = 0; i < length; i++) { - boolean lower = random.nextBoolean(); - char left = lower ? 'a' : 'A'; - char right = lower ? 'z' : 'Z'; - chars[i] = (char) random.nextInt(left, right + 1); - } - - return new String(chars); - }) + Map.entry(String.class, getRandomString()) ); - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { - Constructor[] constructors = clazz.getDeclaredConstructors(); - - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); - } + private static final Set> IMMUTABLE_KEY_TYPES = PRIMITIVE_TYPES_GENERATORS.keySet(); public Object generateByType(Class clazz, String packageName) throws IllegalArgumentException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException { - if (!isClassOrParentAnnotated(clazz, Generatable.class, packageName)) { + var annotatedClass = getAnnotatedClass(clazz, Generatable.class, packageName); + + if (annotatedClass.isEmpty()) { throw new IllegalArgumentException(clazz.getCanonicalName() + " is not annotated with @" + Generatable.class.getCanonicalName()); } - return createInstance(clazz); + return createInstance(annotatedClass.get(), 0); } - private static Object createInstance(Class clazz) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + private static Object createInstance(Class clazz, int depth) throws InvocationTargetException, InstantiationException, IllegalAccessException { + if (PRIMITIVE_TYPES_GENERATORS.containsKey(clazz)) { + return PRIMITIVE_TYPES_GENERATORS.get(clazz).get(); + } + + if (depth > MAX_DEPTH) { + return null; + } + + if (Collection.class.isAssignableFrom(clazz)) { + return new ArrayList<>(); + } + + if (Map.class.isAssignableFrom(clazz)) { + return new HashMap<>(); + } + var constructors = clazz.getDeclaredConstructors(); var randomConstructorIndex = constructors.length == 0 ? 0 : new Random().nextInt(constructors.length); var constructor = constructors[randomConstructorIndex]; @@ -69,16 +73,90 @@ private static Object createInstance(Class clazz) throws NoSuchMethodExceptio var paramTypes = constructor.getParameterTypes(); var params = new Object[paramTypes.length]; + for (int i = 0; i < paramTypes.length; i++) { - if (PRIMITIVE_TYPES_GENERATORS.containsKey(paramTypes[i])) { - params[i] = PRIMITIVE_TYPES_GENERATORS.get(paramTypes[i]).get(); + params[i] = createInstance(paramTypes[i], depth + 1); + } + + var instance = constructor.newInstance(params); + + for (var field : clazz.getDeclaredFields()) { + field.setAccessible(true); + var fieldType = field.getType(); + Object value; + if (Collection.class.isAssignableFrom(fieldType)) { + value = generateCollection(field, depth + 1); + } else if (Map.class.isAssignableFrom(fieldType)) { + value = generateMap(field, depth + 1); + } else if (PRIMITIVE_TYPES_GENERATORS.containsKey(fieldType)) { + value = PRIMITIVE_TYPES_GENERATORS.get(fieldType).get(); } else { - //todo а что если параметр - класс, помеченный аннотацией? надо рекурсивно создавать объекты до какой-то глубины - params[i] = null; + value = createInstance(fieldType, depth + 1); + } + + field.set(instance, value); + } + + return instance; + } + + private static Object generateCollection(Field field, int depth) throws InvocationTargetException, InstantiationException, IllegalAccessException { + var collection = new ArrayList<>(); + + var genericType = field.getGenericType(); + if (genericType instanceof ParameterizedType parameterizedType) { + var typeArgs = parameterizedType.getActualTypeArguments(); + if (typeArgs.length == 1 && typeArgs[0] instanceof Class elementClass) { + int size = new Random().nextInt(3) + 1; + for (int i = 0; i < size; i++) { + collection.add(createInstance(elementClass, depth + 1)); + } } } - return constructor.newInstance(params); + return collection; + } + + private static Object generateMap(Field field, int depth) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + var map = new HashMap<>(); + + var genericType = field.getGenericType(); + if (genericType instanceof ParameterizedType parameterizedType) { + var typeArgs = parameterizedType.getActualTypeArguments(); + if (typeArgs.length == 2 && typeArgs[0] instanceof Class keyClass && typeArgs[1] instanceof Class valueClass) { + int size = new Random().nextInt(3) + 1; + for (int i = 0; i < size; i++) { + Object key; + if (keyClass.isEnum() || IMMUTABLE_KEY_TYPES.contains(keyClass)) { + key = createInstance(keyClass, depth + 1); + } else { + throw new IllegalArgumentException("key for Map is not immutable"); + } + Object value = createInstance(valueClass, depth + 1); + map.put(key, value); + } + } + } + + return map; + } + + private static Supplier getRandomString() { + return () -> { + ThreadLocalRandom random = ThreadLocalRandom.current(); + int length = 10; + char[] chars = new char[length]; + + for (int i = 0; i < length; i++) { + boolean lower = random.nextBoolean(); + char left = lower ? 'a' : 'A'; + char right = lower ? 'z' : 'Z'; + chars[i] = (char) random.nextInt(left, right + 1); + } + + return new String(chars); + }; } } diff --git a/src/main/java/org/example/generator/util/ReflectionUtil.java b/src/main/java/org/example/generator/util/ReflectionUtil.java index 19276fa..9fddf6f 100644 --- a/src/main/java/org/example/generator/util/ReflectionUtil.java +++ b/src/main/java/org/example/generator/util/ReflectionUtil.java @@ -11,13 +11,14 @@ import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Random; public class ReflectionUtil { private static final Logger log = LoggerFactory.getLogger(ReflectionUtil.class); - public static boolean isClassOrParentAnnotated( + public static Optional> getAnnotatedClass( final Class clazz, final Class annotation, final String packageName @@ -36,15 +37,16 @@ public static boolean isClassOrParentAnnotated( while (!clazzCopy.equals(Object.class)) { var implementedInterfaces = Arrays.asList(clazzCopy.getInterfaces()); + if (isInterfaceAnnotated) { + return Optional.of(clazzCopy); + } if (clazzCopy.isAnnotationPresent(annotation) || - isInterfaceAnnotated || - implementedInterfaces.stream().anyMatch(i -> i.isAnnotationPresent(annotation)) - ) { - return true; + implementedInterfaces.stream().anyMatch(i -> i.isAnnotationPresent(annotation))) { + return Optional.of(clazz); } clazzCopy = clazzCopy.getSuperclass(); } - return false; + return Optional.empty(); } public static List> findAllInterfaceImplementations(Class interfaceClass, String packageName) { @@ -55,9 +57,9 @@ public static List> findAllInterfaceImplementations(Class interfaceC return Collections.emptyList(); } - File dir; + File directory; try { - dir = new File(resource.toURI()); + directory = new File(resource.toURI()); } catch (URISyntaxException e) { log.error("Invalid URI syntax: {}", e.getMessage()); return Collections.emptyList(); @@ -65,7 +67,7 @@ public static List> findAllInterfaceImplementations(Class interfaceC var implementations = new ArrayList>(); - for (var file : Objects.requireNonNull(dir.listFiles())) { + for (var file : Objects.requireNonNull(directory.listFiles())) { if (file.getName().endsWith(".class")) { var className = packageName + "." + file.getName().replace(".class", ""); try {