From d2fee39191cd5c2b2bb60ca315c3fcf87a082716 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:28:41 +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 c9d3b8c51a5426489b0203e43b91e54eab87c633 Mon Sep 17 00:00:00 2001 From: ArtiKhog Date: Tue, 21 Oct 2025 10:59:26 +0300 Subject: [PATCH 2/3] it's working! --- .../java/org/example/GenerateExample.java | 18 +- .../org/example/classes/BinaryTreeNode.java | 19 ++ src/main/java/org/example/classes/Cart.java | 8 +- .../java/org/example/classes/Example.java | 3 + .../java/org/example/classes/Product.java | 5 +- .../java/org/example/classes/Rectangle.java | 8 + src/main/java/org/example/classes/Shape.java | 3 + .../java/org/example/classes/Triangle.java | 8 + .../org/example/generator/Generatable.java | 10 + .../java/org/example/generator/Generator.java | 182 +++++++++++++++++- .../org/example/generator/PackageUtils.java | 130 +++++++++++++ 11 files changed, 381 insertions(+), 13 deletions(-) create mode 100644 src/main/java/org/example/generator/Generatable.java create mode 100644 src/main/java/org/example/generator/PackageUtils.java diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..e4d155c 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,14 +1,26 @@ package org.example; -import org.example.classes.Example; +import org.example.classes.*; import org.example.generator.Generator; +import java.util.Random; + public class GenerateExample { public static void main(String[] args) { - var gen = new Generator(); + Random random = new Random(42); + var gen = new Generator(random, 3); + generateAndPrint(gen, Product.class); + generateAndPrint(gen, Rectangle.class); + generateAndPrint(gen, Triangle.class); + generateAndPrint(gen, Shape.class); + generateAndPrint(gen, Cart.class); + generateAndPrint(gen, BinaryTreeNode.class); + } + + private static void generateAndPrint(Generator generator, Class clazz) { try { - Object generated = gen.generateValueOfType(Example.class); + Object generated = generator.generateByType(clazz, "org.example.classes"); System.out.println(generated); } catch (Throwable e) { throw new RuntimeException(e); diff --git a/src/main/java/org/example/classes/BinaryTreeNode.java b/src/main/java/org/example/classes/BinaryTreeNode.java index 046ff56..d7e46e2 100644 --- a/src/main/java/org/example/classes/BinaryTreeNode.java +++ b/src/main/java/org/example/classes/BinaryTreeNode.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; @@ -30,4 +33,20 @@ public void setLeft(BinaryTreeNode left) { public void setRight(BinaryTreeNode right) { this.right = right; } + + @Override + public String toString() { + String leftString = "null"; + if (this.left != null) { + leftString = this.left.toString(); + } + String rightString = "null"; + if (this.right != null) { + rightString = this.right.toString(); + } + return "BinaryTreeNode(data: " + this.data + + ", left: " + leftString + + ", right: " + rightString + + ")"; + } } diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..ffea29e 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/main/java/org/example/classes/Cart.java @@ -1,7 +1,10 @@ package org.example.classes; +import org.example.generator.Generatable; + import java.util.List; +@Generatable public class Cart { private List items; @@ -17,5 +20,8 @@ public void setItems(List items) { this.items = items; } - // Конструктор, методы добавления и удаления товаров, геттеры и другие методы + @Override + public String toString() { + return "Cart(items: " + this.items.toString() + ")"; + } } \ 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..e0c8d1c 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/main/java/org/example/classes/Example.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Example { int i; diff --git a/src/main/java/org/example/classes/Product.java b/src/main/java/org/example/classes/Product.java index e7dcc89..c823861 100644 --- a/src/main/java/org/example/classes/Product.java +++ b/src/main/java/org/example/classes/Product.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Product { private String name; private double price; @@ -42,7 +45,7 @@ public boolean equals(Object obj) { @Override public String toString() { - return super.toString(); + return "Product(name: " + this.name + ", price: " + this.price + ")"; } } diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..a8ecc19 100644 --- a/src/main/java/org/example/classes/Rectangle.java +++ b/src/main/java/org/example/classes/Rectangle.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Rectangle implements Shape { private double length; private double width; @@ -18,4 +21,9 @@ public double getArea() { public double getPerimeter() { return 2 * (length + width); } + + @Override + public String toString() { + return "Rectangle(length: " + this.length + ", width: " + this.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..7a6f41f 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.generator.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..8f341b4 100644 --- a/src/main/java/org/example/classes/Triangle.java +++ b/src/main/java/org/example/classes/Triangle.java @@ -1,5 +1,8 @@ package org.example.classes; +import org.example.generator.Generatable; + +@Generatable public class Triangle implements Shape { private double sideA; private double sideB; @@ -21,4 +24,9 @@ public double getArea() { public double getPerimeter() { return sideA + sideB + sideC; } + + @Override + public String toString() { + return "Triangle(sideA: " + this.sideA + ", sideB: " + this.sideB + ", sideC: " + this.sideC + ")"; + } } \ 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..5182b51 --- /dev/null +++ b/src/main/java/org/example/generator/Generatable.java @@ -0,0 +1,10 @@ +package org.example.generator; + +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/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index 9d86bfb..c41f9c5 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,18 +1,184 @@ package org.example.generator; -import java.lang.reflect.Constructor; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; -import java.util.Random; +import java.lang.reflect.ParameterizedType; +import java.util.*; +import java.util.function.Supplier; + +import static org.example.generator.PackageUtils.getAnnotatedClass; public class Generator { + private final Random random; + private final int maxDepth; + + private final Map, Supplier> primitiveTypesGenerators; - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { - Constructor[] constructors = clazz.getDeclaredConstructors(); + private static final Set> IMMUTABLE_KEY_TYPES = Set.of( + int.class, Integer.class, long.class, Long.class, double.class, Double.class, + float.class, Float.class, boolean.class, Boolean.class, char.class, Character.class, + String.class + ); - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); + public Generator() { + this(new Random(), 4); } + public Generator(Random random, int maxDepth) { + this.random = random; + this.maxDepth = maxDepth; + this.primitiveTypesGenerators = initializePrimitiveGenerators(); + } + + public Object generateByType(Class clazz, + String packageName + ) + throws IllegalArgumentException, + InvocationTargetException, + InstantiationException, + IllegalAccessException + { + var annotatedClass = getAnnotatedClass(clazz, Generatable.class, packageName); + + if (annotatedClass.isEmpty()) { + throw new IllegalArgumentException(clazz.getCanonicalName() + " is not annotated with @" + Generatable.class.getCanonicalName()); + } + + return createInstance(annotatedClass.get(), 0); + } + + private Object createInstance(Class clazz, int depth) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + + if (primitiveTypesGenerators.containsKey(clazz)) { + return primitiveTypesGenerators.get(clazz); + } + + if (depth > maxDepth) { + 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 : 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++) { + 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 (primitiveTypesGenerators.containsKey(fieldType)) { + value = primitiveTypesGenerators.get(fieldType); + } else { + value = createInstance(fieldType, depth + 1); + } -} + field.set(instance, value); + } + + return instance; + } + + private Map, Supplier> initializePrimitiveGenerators() { + Map, Supplier> generators = new HashMap<>(); + + generators.put(int.class, () -> random.nextInt(100)); + generators.put(Integer.class, () -> random.nextInt(100)); + generators.put(long.class, () -> random.nextLong(100)); + generators.put(Long.class, () -> random.nextLong(100)); + generators.put(double.class, () -> random.nextDouble(100)); + generators.put(Double.class, () -> random.nextDouble(100)); + generators.put(float.class, () -> random.nextFloat(100)); + generators.put(Float.class, () -> random.nextFloat(100)); + generators.put(boolean.class, random::nextBoolean); + generators.put(Boolean.class, random::nextBoolean); + generators.put(char.class, () -> (char) (random.nextInt(26) + 'a')); + generators.put(Character.class, () -> (char) (random.nextInt(26) + 'a')); + generators.put(String.class, getRandomString()); + + return generators; + } + + private 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 = random.nextInt(3) + 1; // Используем переданный Random + for (int i = 0; i < size; i++) { + collection.add(createInstance(elementClass, depth + 1)); + } + } + } + return collection; + } + + private 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 = random.nextInt(3) + 1; // Используем переданный Random + 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 Supplier getRandomString() { + return () -> { + 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(right - left + 1) + left); + } + + return new String(chars); + }; + } +} \ No newline at end of file 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..6bd7778 --- /dev/null +++ b/src/main/java/org/example/generator/PackageUtils.java @@ -0,0 +1,130 @@ +package org.example.generator; + +import java.io.File; +import java.lang.annotation.Annotation; +import java.net.URISyntaxException; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class PackageUtils { + public static Optional> getAnnotatedClass( + final Class targetClass, + final Class annotation, + final String packageName + ) { + if (targetClass.isInterface()) { + return handleInterfaceCase(targetClass, annotation, packageName); + } else { + return handleClassCase(targetClass, annotation); + } + } + + private static Optional> handleInterfaceCase( + final Class interfaceClass, + final Class annotation, + final String packageName + ) { + boolean isInterfaceAnnotated = interfaceClass.isAnnotationPresent(annotation); + List> implementations = findInterfaceImplementations(interfaceClass, packageName); + + if (implementations.isEmpty()) { + System.out.printf("No implementations found for interface: %s%n", + interfaceClass.getCanonicalName()); + return Optional.empty(); + } + + Class chosenImplementation = implementations.get(new Random().nextInt(implementations.size())); + System.out.printf("Interface: %s, chosen implementation: %s%n", + interfaceClass.getCanonicalName(), chosenImplementation.getCanonicalName()); + + return isInterfaceAnnotated ? + Optional.of(chosenImplementation) : + getAnnotatedClass(chosenImplementation, annotation, packageName); + } + + private static Optional> handleClassCase( + final Class clazz, + final Class annotation + ) { + return traverseClassHierarchy(clazz, annotation); + } + + private static Optional> traverseClassHierarchy( + Class currentClass, + final Class annotation + ) { + while (currentClass != null && !currentClass.equals(Object.class)) { + if (isClassOrInterfacesAnnotated(currentClass, annotation)) { + return Optional.of(currentClass); + } + currentClass = currentClass.getSuperclass(); + } + return Optional.empty(); + } + + private static boolean isClassOrInterfacesAnnotated( + final Class clazz, + final Class annotation + ) { + return clazz.isAnnotationPresent(annotation) || + Arrays.stream(clazz.getInterfaces()) + .anyMatch(iface -> iface.isAnnotationPresent(annotation)); + } + + public static List> findInterfaceImplementations(Class interfaceClass, String packageName) { + return getPackageClasses(packageName) + .filter(clazz -> interfaceClass.isAssignableFrom(clazz) && !clazz.isInterface()) + .collect(Collectors.toList()); + } + + private static Stream> getPackageClasses(String packageName) { + try { + return getClassFiles(packageName) + .stream() + .map(file -> convertFileToClass(file, packageName)) + .filter(Optional::isPresent) + .map(Optional::get); + } catch (URISyntaxException e) { + System.err.printf("Invalid package URI: %s%n", e.getMessage()); + return Stream.empty(); + } + } + + private static List getClassFiles(String packageName) throws URISyntaxException { + String path = packageName.replace('.', '/'); + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + var resource = classLoader.getResource(path); + + if (resource == null) { + System.err.printf("Package not found: %s%n", packageName); + return Collections.emptyList(); + } + + File directory = new File(resource.toURI()); + File[] files = directory.listFiles(); + + if (files == null) { + System.err.printf("No files found in package: %s%n", packageName); + return Collections.emptyList(); + } + + return Arrays.stream(files) + .filter(file -> file.getName().endsWith(".class")) + .collect(Collectors.toList()); + } + + private static Optional> convertFileToClass(File classFile, String packageName) { + String className = packageName + "." + classFile.getName().replace(".class", "");; + + try { + return Optional.of(Class.forName(className)); + } catch (ClassNotFoundException e) { + System.err.printf("Class not found: %s - %s%n", className, e.getMessage()); + return Optional.empty(); + } catch (NoClassDefFoundError e) { + System.err.printf("Class definition not found: %s - %s%n", className, e.getMessage()); + return Optional.empty(); + } + } +} From 18997550b03cb8457c9b5c5ff4d7f6fb995980e5 Mon Sep 17 00:00:00 2001 From: ArtiKhog Date: Thu, 20 Nov 2025 16:15:23 +0300 Subject: [PATCH 3/3] fix set static fields --- src/main/java/org/example/generator/Generator.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java index c41f9c5..d3fcdee 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -82,6 +82,11 @@ private Object createInstance(Class clazz, int depth) // Заполняем поля объекта for (var field : clazz.getDeclaredFields()) { + // Пропускаем статические поля + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + field.setAccessible(true); var fieldType = field.getType(); Object value;