From 3f64c3539eb551a807db70336f44faffc25bf125 Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 17:29:24 +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 f7067d0ee218a7712554893edbd46480121af85c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Sun, 5 Oct 2025 20:34:33 +0300 Subject: [PATCH 2/3] =?UTF-8?q?LAB-2=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=8D=D0=BA=D0=B7=D0=B5=D0=BC=D0=BF=D0=BB?= =?UTF-8?q?=D1=8F=D1=80=D0=BE=D0=B2=20=D0=B0=D0=BD=D0=BD=D0=BE=D1=82=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D0=BE=D0=B2=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=20=D1=80=D0=B5=D1=84=D0=BB=D0=B5=D0=BA=D1=81=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/GenerateExample.java | 27 +++- .../org/example/classes/BinaryTreeNode.java | 3 + src/main/java/org/example/classes/Cart.java | 3 + .../java/org/example/classes/Example.java | 3 + .../java/org/example/classes/Product.java | 3 + .../java/org/example/classes/Rectangle.java | 3 + src/main/java/org/example/classes/Shape.java | 3 + .../java/org/example/classes/Triangle.java | 3 + .../org/example/common/RandomProvider.java | 7 + .../example/generator/ClassPathScanner.java | 59 +++++++ .../java/org/example/generator/Generator.java | 18 --- .../generator/ImplementationIndex.java | 52 +++++++ .../example/generator/InstanceGenerator.java | 144 ++++++++++++++++++ .../generator/PrimitiveValueGenerator.java | 38 +++++ .../annotation/CustomClassGenerator.java | 10 ++ 15 files changed, 353 insertions(+), 23 deletions(-) create mode 100644 src/main/java/org/example/common/RandomProvider.java create mode 100644 src/main/java/org/example/generator/ClassPathScanner.java delete mode 100644 src/main/java/org/example/generator/Generator.java create mode 100644 src/main/java/org/example/generator/ImplementationIndex.java create mode 100644 src/main/java/org/example/generator/InstanceGenerator.java create mode 100644 src/main/java/org/example/generator/PrimitiveValueGenerator.java create mode 100644 src/main/java/org/example/generator/annotation/CustomClassGenerator.java diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..912f9b3 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,15 +1,32 @@ package org.example; - +import org.example.classes.BinaryTreeNode; +import org.example.classes.Cart; import org.example.classes.Example; -import org.example.generator.Generator; +import org.example.classes.Product; +import org.example.classes.Rectangle; +import org.example.classes.Shape; +import org.example.classes.Triangle; +import org.example.generator.InstanceGenerator; public class GenerateExample { public static void main(String[] args) { - var gen = new Generator(); + InstanceGenerator instanceGenerator = new InstanceGenerator(); try { - Object generated = gen.generateValueOfType(Example.class); - System.out.println(generated); + var generated1 = instanceGenerator.generateValueOf(Example.class); + System.out.println(generated1); + var generated2 = instanceGenerator.generateValueOf(Cart.class); + System.out.println(generated2); + var generated3 = instanceGenerator.generateValueOf(Product.class); + System.out.println(generated3); + var generated4 = instanceGenerator.generateValueOf(Rectangle.class); + System.out.println(generated4); + var generated5 = instanceGenerator.generateValueOf(Triangle.class); + System.out.println(generated5); + var generated6 = instanceGenerator.generateValueOf(Shape.class); + System.out.println(generated6); + var generated7 = instanceGenerator.generateValueOf(BinaryTreeNode.class); + System.out.println(generated7); } 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..ae41f3a 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.annotation.CustomClassGenerator; + +@CustomClassGenerator public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..e0c8f93 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.annotation.CustomClassGenerator; + import java.util.List; +@CustomClassGenerator public class Cart { private List items; diff --git a/src/main/java/org/example/classes/Example.java b/src/main/java/org/example/classes/Example.java index eac9463..b3c0eb6 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.annotation.CustomClassGenerator; + +@CustomClassGenerator 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..ede9cc8 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.annotation.CustomClassGenerator; + +@CustomClassGenerator public class Product { private String name; private double price; diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..d2537d5 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.annotation.CustomClassGenerator; + +@CustomClassGenerator public class Rectangle implements Shape { private double length; private double width; diff --git a/src/main/java/org/example/classes/Shape.java b/src/main/java/org/example/classes/Shape.java index c20a851..e2d8c34 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.annotation.CustomClassGenerator; + +@CustomClassGenerator 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..07cd6ff 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.annotation.CustomClassGenerator; + +@CustomClassGenerator public class Triangle implements Shape { private double sideA; private double sideB; diff --git a/src/main/java/org/example/common/RandomProvider.java b/src/main/java/org/example/common/RandomProvider.java new file mode 100644 index 0000000..a6ff502 --- /dev/null +++ b/src/main/java/org/example/common/RandomProvider.java @@ -0,0 +1,7 @@ +package org.example.common; + +import java.util.Random; + +public class RandomProvider { + public static final Random RANDOM = new Random(); +} diff --git a/src/main/java/org/example/generator/ClassPathScanner.java b/src/main/java/org/example/generator/ClassPathScanner.java new file mode 100644 index 0000000..57064df --- /dev/null +++ b/src/main/java/org/example/generator/ClassPathScanner.java @@ -0,0 +1,59 @@ +package org.example.generator; + +import org.example.generator.annotation.CustomClassGenerator; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class ClassPathScanner { + + static Set> scanForAnnotatedClasses() { + var classpath = System.getProperty("java.class.path"); + if (classpath == null || classpath.isEmpty()) { + return Collections.emptySet(); + } + + return Arrays.stream(classpath.split(File.pathSeparator)) + .map(File::new) + .filter(File::exists) + .filter(File::isDirectory) + .flatMap(dir -> scanDirectory(dir, "").stream()) + .collect(Collectors.toSet()); + } + + private static Set> scanDirectory(File dir, String targetPackage) { + var files = dir.listFiles(); + if (files == null) { + return Collections.emptySet(); + } + + return Arrays.stream(files) + .flatMap(file -> { + if (file.isDirectory()) { + var sub = targetPackage.isEmpty() ? file.getName() : targetPackage + "." + file.getName(); + return scanDirectory(file, sub).stream(); + } else if (file.getName().endsWith(".class")) { + var name = targetPackage + "." + file.getName().substring(0, file.getName().length() - 6); + return tryAddClass(name).stream(); + } + return Stream.empty(); + }) + .collect(Collectors.toSet()); + } + + private static Optional> tryAddClass(String className) { + try { + var name = Class.forName(className); + if (name.isAnnotationPresent(CustomClassGenerator.class)) { + return Optional.of(name); + } + } catch (Throwable ignored) {} + + return Optional.empty(); + } +} diff --git a/src/main/java/org/example/generator/Generator.java b/src/main/java/org/example/generator/Generator.java deleted file mode 100644 index 9d86bfb..0000000 --- a/src/main/java/org/example/generator/Generator.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.example.generator; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Random; - -public class Generator { - - 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); - } - - -} diff --git a/src/main/java/org/example/generator/ImplementationIndex.java b/src/main/java/org/example/generator/ImplementationIndex.java new file mode 100644 index 0000000..0f595e0 --- /dev/null +++ b/src/main/java/org/example/generator/ImplementationIndex.java @@ -0,0 +1,52 @@ +package org.example.generator; + +import org.example.common.RandomProvider; +import org.example.generator.annotation.CustomClassGenerator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class ImplementationIndex { + + private final Set> annotatedTypes; + private final Map, List>> interfaceToImplementations = new HashMap<>(); + + public ImplementationIndex(Set> annotatedTypes) { + this.annotatedTypes = Objects.requireNonNull(annotatedTypes); + buildIndex(); + } + + public Optional> randomImplementationFor(Class currentInterface) { + return Optional.ofNullable(interfaceToImplementations.get(currentInterface)) + .filter(list -> !list.isEmpty()) + .map(list -> list.get(RandomProvider.RANDOM.nextInt(list.size()))); + } + + private void buildIndex() { + annotatedTypes.stream() + .flatMap(currentClass -> { + CustomClassGenerator annotation = currentClass.getAnnotation(CustomClassGenerator.class); + + if (annotation != null && annotation.implementsFor().length > 0) { + return Arrays.stream(annotation.implementsFor()) + .map(target -> Map.entry(target, currentClass)); + } + + return Arrays.stream(currentClass.getInterfaces()) + .map(currentInterface -> Map.entry(currentInterface, currentClass)); + }) + .forEach(entry -> register(entry.getKey(), entry.getValue())); + } + + private void register(Class currentInterface, Class implementedClass) { + interfaceToImplementations + .computeIfAbsent(currentInterface, k -> new ArrayList<>()) + .add(implementedClass); + } +} diff --git a/src/main/java/org/example/generator/InstanceGenerator.java b/src/main/java/org/example/generator/InstanceGenerator.java new file mode 100644 index 0000000..15ad080 --- /dev/null +++ b/src/main/java/org/example/generator/InstanceGenerator.java @@ -0,0 +1,144 @@ +package org.example.generator; + +import org.example.common.RandomProvider; +import org.example.generator.annotation.CustomClassGenerator; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Collection; +import java.util.Collections; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +public class InstanceGenerator { + private final ImplementationIndex implementationIndex; + + public InstanceGenerator() { + var discovered = ClassPathScanner.scanForAnnotatedClasses(); + this.implementationIndex = new ImplementationIndex(discovered); + } + + public Object generateValueOf(Class type) { + var primitive = PrimitiveValueGenerator.getGenerator(type); + if (primitive.isPresent()) return primitive.get().get(); + + if (type.isInterface()) { + var impl = implementationIndex.randomImplementationFor(type); + if (impl.isEmpty()) throw new IllegalArgumentException("Не найдены реализации интерфейса: " + type.getName()); + return generateSafely(impl.get()); + } + + if (Modifier.isAbstract(type.getModifiers())) { + throw new IllegalArgumentException("Класс " + type.getName() + " является абстрактным, не удаётся создать экземпляр"); + } + + if (!type.isAnnotationPresent(CustomClassGenerator.class)) { + throw new IllegalArgumentException("Класс " + type.getName() + " нельзя сгенерировать. Требуется аннотация @CustomClassGenerator"); + } + + var constructors = type.getDeclaredConstructors(); + if (constructors.length == 0) throw new IllegalArgumentException("Класс " + type.getName() + " не имеет конструкторов"); + + + Constructor constructor = constructors[RandomProvider.RANDOM.nextInt(constructors.length)]; + var args = buildArgsForConstructor(constructor); + try { + constructor.setAccessible(true); + return type.cast(constructor.newInstance(args)); + } catch (Throwable e) { + throw new RuntimeException("Не удалось создать экземпляр " + type.getName(), e); + } + } + + private Object generateSafely(Class currentClass) { + try { + return generateValueOf(currentClass); + } catch (Exception e) { + throw new RuntimeException("Не удается создать значение для " + currentClass.getName(), e); + } + } + + private Object[] buildArgsForConstructor(Constructor constructor) { + var parameters = constructor.getParameters(); + var args = new Object[parameters.length]; + + IntStream.range(0, parameters.length).forEach(i -> { + var parameter = parameters[i]; + var parameterParameterizedType = parameter.getParameterizedType(); + var parameterType = parameter.getType(); + + if (parameterParameterizedType instanceof ParameterizedType parameterizedType) { + var rawType = parameterizedType.getRawType(); + + if (rawType instanceof Class rawClass && Collection.class.isAssignableFrom(rawClass)) { + var actualTypeArguments = parameterizedType.getActualTypeArguments(); + + if (actualTypeArguments.length == 1) { + var elementClass = resolveClassFromType(actualTypeArguments[0]); + if (elementClass == null) { + args[i] = Collections.emptyList(); + return; + } + + var size = 1 + RandomProvider.RANDOM.nextInt(3); + var created = IntStream.range(0, size) + .mapToObj(j -> generateSafely(elementClass)) + .collect(Collectors.toList()); + args[i] = created; + return; + } + + args[i] = Collections.emptyList(); + return; + } + } + + if (parameterType.isArray()) { + var componentType = parameterType.getComponentType(); + var len = 1 + RandomProvider.RANDOM.nextInt(3); + var array = Array.newInstance(componentType, len); + + IntStream.range(0, len) + .forEach(j -> Array.set(array, j, generateSafely(componentType))); + args[i] = array; + return; + } + + if (Collection.class.isAssignableFrom(parameterType)) { + args[i] = Collections.emptyList(); + return; + } + + if (parameterType.isInterface()) { + var impl = implementationIndex.randomImplementationFor(parameterType); + args[i] = impl.map(this::generateSafely).orElse(null); + return; + } + + args[i] = generateSafely(parameterType); + }); + + return args; + } + + private Class resolveClassFromType(Type type) { + if (type instanceof Class) return (Class) type; + + if (type instanceof ParameterizedType) { + Type raw = ((ParameterizedType) type).getRawType(); + if (raw instanceof Class) return (Class) raw; + } + + if (type instanceof GenericArrayType) { + var genericComponentType = ((GenericArrayType) type).getGenericComponentType(); + var classFromType = resolveClassFromType(genericComponentType); + if (classFromType != null) return Array.newInstance(classFromType, 0).getClass(); + } + + return null; + } +} diff --git a/src/main/java/org/example/generator/PrimitiveValueGenerator.java b/src/main/java/org/example/generator/PrimitiveValueGenerator.java new file mode 100644 index 0000000..cbfcc0d --- /dev/null +++ b/src/main/java/org/example/generator/PrimitiveValueGenerator.java @@ -0,0 +1,38 @@ +package org.example.generator; + +import org.example.common.RandomProvider; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.Supplier; + +public class PrimitiveValueGenerator { + + private static final Map, Supplier> DEFAULTS = new HashMap<>(); + + static { + DEFAULTS.put(int.class, () -> RandomProvider.RANDOM.nextInt(100)); + DEFAULTS.put(Integer.class, () -> RandomProvider.RANDOM.nextInt(100)); + DEFAULTS.put(long.class, RandomProvider.RANDOM::nextLong); + DEFAULTS.put(Long.class, RandomProvider.RANDOM::nextLong); + DEFAULTS.put(double.class, RandomProvider.RANDOM::nextDouble); + DEFAULTS.put(Double.class, RandomProvider.RANDOM::nextDouble); + DEFAULTS.put(float.class, RandomProvider.RANDOM::nextFloat); + DEFAULTS.put(Float.class, RandomProvider.RANDOM::nextFloat); + DEFAULTS.put(boolean.class, RandomProvider.RANDOM::nextBoolean); + DEFAULTS.put(Boolean.class, RandomProvider.RANDOM::nextBoolean); + DEFAULTS.put(char.class, () -> (char) (RandomProvider.RANDOM.nextInt(26) + 'a')); + DEFAULTS.put(Character.class, () -> (char) (RandomProvider.RANDOM.nextInt(26) + 'a')); + DEFAULTS.put(byte.class, () -> (byte) RandomProvider.RANDOM.nextInt()); + DEFAULTS.put(Byte.class, () -> (byte) RandomProvider.RANDOM.nextInt()); + DEFAULTS.put(short.class, () -> (short) RandomProvider.RANDOM.nextInt(Short.MAX_VALUE + 1)); + DEFAULTS.put(Short.class, () -> (short) RandomProvider.RANDOM.nextInt(Short.MAX_VALUE + 1)); + DEFAULTS.put(String.class, () -> UUID.randomUUID().toString().substring(0, 8)); + } + + static Optional> getGenerator(Class type) { + return Optional.ofNullable(DEFAULTS.get(type)); + } +} diff --git a/src/main/java/org/example/generator/annotation/CustomClassGenerator.java b/src/main/java/org/example/generator/annotation/CustomClassGenerator.java new file mode 100644 index 0000000..43e4ab2 --- /dev/null +++ b/src/main/java/org/example/generator/annotation/CustomClassGenerator.java @@ -0,0 +1,10 @@ +package org.example.generator.annotation; + +import java.lang.annotation.*; + +@Inherited +@Target(value= ElementType.TYPE) +@Retention(value= RetentionPolicy.RUNTIME) +public @interface CustomClassGenerator { + Class[] implementsFor() default {}; +} From 041f040a6cada283c00db682b42473777233a769 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BB=D0=B0=D0=B3=D0=BE=D1=80=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=B0=D1=8F=20=D0=9B=D1=8E=D0=B4=D0=BC=D0=B8=D0=BB=D0=B0?= Date: Fri, 10 Oct 2025 00:00:23 +0300 Subject: [PATCH 3/3] =?UTF-8?q?LAB-2=20=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D1=8F=20=D0=B7=D0=B0=D1=89=D0=B8=D1=82?= =?UTF-8?q?=D1=8B=20=D0=BE=D1=82=20=D0=B7=D0=B0=D1=86=D0=B8=D0=BA=D0=BB?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80=D0=B8=20?= =?UTF-8?q?=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=B2=20=D1=82?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=B6=D0=B5=20=D1=82=D0=B8=D0=BF=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=BA=D0=B5=D0=B9=D1=81=D0=B5=20=D1=81=20=D1=86=D0=B8?= =?UTF-8?q?=D0=BA=D0=BB=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=BC=D0=B8/?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BA=D1=80=D0=B5=D1=81=D1=82=D0=BD?= =?UTF-8?q?=D1=8B=D0=BC=D0=B8=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D1=8F=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/example/GenerateExample.java | 2 +- .../org/example/classes/BinaryTreeNode.java | 4 +- src/main/java/org/example/classes/Cart.java | 4 +- .../java/org/example/classes/Example.java | 4 +- .../java/org/example/classes/Product.java | 4 +- .../java/org/example/classes/Rectangle.java | 4 +- src/main/java/org/example/classes/Shape.java | 4 +- .../java/org/example/classes/Triangle.java | 4 +- .../example/generator/ClassPathScanner.java | 4 +- .../generator/ImplementationIndex.java | 4 +- .../example/generator/InstanceGenerator.java | 88 ++++++++++++++----- .../annotation/CustomClassGenerator.java | 10 --- .../generator/annotation/Generatable.java | 9 ++ 13 files changed, 92 insertions(+), 53 deletions(-) delete mode 100644 src/main/java/org/example/generator/annotation/CustomClassGenerator.java create mode 100644 src/main/java/org/example/generator/annotation/Generatable.java diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 912f9b3..1a436fb 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -11,7 +11,7 @@ public class GenerateExample { public static void main(String[] args) { - InstanceGenerator instanceGenerator = new InstanceGenerator(); + var instanceGenerator = new InstanceGenerator(); try { var generated1 = instanceGenerator.generateValueOf(Example.class); System.out.println(generated1); diff --git a/src/main/java/org/example/classes/BinaryTreeNode.java b/src/main/java/org/example/classes/BinaryTreeNode.java index ae41f3a..66588c5 100644 --- a/src/main/java/org/example/classes/BinaryTreeNode.java +++ b/src/main/java/org/example/classes/BinaryTreeNode.java @@ -1,8 +1,8 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; -@CustomClassGenerator +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index e0c8f93..38ca257 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/main/java/org/example/classes/Cart.java @@ -1,10 +1,10 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; import java.util.List; -@CustomClassGenerator +@Generatable public class Cart { private List items; diff --git a/src/main/java/org/example/classes/Example.java b/src/main/java/org/example/classes/Example.java index b3c0eb6..e452d02 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/main/java/org/example/classes/Example.java @@ -1,8 +1,8 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; -@CustomClassGenerator +@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 ede9cc8..96e8701 100644 --- a/src/main/java/org/example/classes/Product.java +++ b/src/main/java/org/example/classes/Product.java @@ -1,8 +1,8 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; -@CustomClassGenerator +@Generatable public class Product { private String name; private double price; diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index d2537d5..3bb6af5 100644 --- a/src/main/java/org/example/classes/Rectangle.java +++ b/src/main/java/org/example/classes/Rectangle.java @@ -1,8 +1,8 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; -@CustomClassGenerator +@Generatable public class Rectangle implements Shape { private double length; private double width; diff --git a/src/main/java/org/example/classes/Shape.java b/src/main/java/org/example/classes/Shape.java index e2d8c34..948bfe2 100644 --- a/src/main/java/org/example/classes/Shape.java +++ b/src/main/java/org/example/classes/Shape.java @@ -1,8 +1,8 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; -@CustomClassGenerator +@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 07cd6ff..c6a3ae3 100644 --- a/src/main/java/org/example/classes/Triangle.java +++ b/src/main/java/org/example/classes/Triangle.java @@ -1,8 +1,8 @@ package org.example.classes; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; -@CustomClassGenerator +@Generatable public class Triangle implements Shape { private double sideA; private double sideB; diff --git a/src/main/java/org/example/generator/ClassPathScanner.java b/src/main/java/org/example/generator/ClassPathScanner.java index 57064df..adde069 100644 --- a/src/main/java/org/example/generator/ClassPathScanner.java +++ b/src/main/java/org/example/generator/ClassPathScanner.java @@ -1,6 +1,6 @@ package org.example.generator; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; import java.io.File; import java.util.Arrays; @@ -49,7 +49,7 @@ private static Set> scanDirectory(File dir, String targetPackage) { private static Optional> tryAddClass(String className) { try { var name = Class.forName(className); - if (name.isAnnotationPresent(CustomClassGenerator.class)) { + if (name.isAnnotationPresent(Generatable.class)) { return Optional.of(name); } } catch (Throwable ignored) {} diff --git a/src/main/java/org/example/generator/ImplementationIndex.java b/src/main/java/org/example/generator/ImplementationIndex.java index 0f595e0..e85ac1f 100644 --- a/src/main/java/org/example/generator/ImplementationIndex.java +++ b/src/main/java/org/example/generator/ImplementationIndex.java @@ -1,7 +1,7 @@ package org.example.generator; import org.example.common.RandomProvider; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; import java.util.ArrayList; import java.util.Arrays; @@ -31,7 +31,7 @@ public Optional> randomImplementationFor(Class currentInterface) { private void buildIndex() { annotatedTypes.stream() .flatMap(currentClass -> { - CustomClassGenerator annotation = currentClass.getAnnotation(CustomClassGenerator.class); + Generatable annotation = currentClass.getAnnotation(Generatable.class); if (annotation != null && annotation.implementsFor().length > 0) { return Arrays.stream(annotation.implementsFor()) diff --git a/src/main/java/org/example/generator/InstanceGenerator.java b/src/main/java/org/example/generator/InstanceGenerator.java index 15ad080..a759388 100644 --- a/src/main/java/org/example/generator/InstanceGenerator.java +++ b/src/main/java/org/example/generator/InstanceGenerator.java @@ -1,7 +1,7 @@ package org.example.generator; import org.example.common.RandomProvider; -import org.example.generator.annotation.CustomClassGenerator; +import org.example.generator.annotation.Generatable; import java.lang.reflect.Array; import java.lang.reflect.Constructor; @@ -11,11 +11,16 @@ import java.lang.reflect.Type; import java.util.Collection; import java.util.Collections; +import java.util.Deque; +import java.util.ArrayDeque; +import java.util.Objects; import java.util.stream.Collectors; import java.util.stream.IntStream; public class InstanceGenerator { + private final ImplementationIndex implementationIndex; + private final ThreadLocal>> constructing = ThreadLocal.withInitial(ArrayDeque::new); public InstanceGenerator() { var discovered = ClassPathScanner.scanForAnnotatedClasses(); @@ -23,48 +28,79 @@ public InstanceGenerator() { } public Object generateValueOf(Class type) { + return generateValueOf(type, -1); + } + + private Object generateValueOf(Class type, int depth) { var primitive = PrimitiveValueGenerator.getGenerator(type); - if (primitive.isPresent()) return primitive.get().get(); + if (primitive.isPresent()) { + return primitive.get().get(); + } if (type.isInterface()) { var impl = implementationIndex.randomImplementationFor(type); - if (impl.isEmpty()) throw new IllegalArgumentException("Не найдены реализации интерфейса: " + type.getName()); - return generateSafely(impl.get()); + if (impl.isEmpty()) + throw new IllegalArgumentException("Не найдены реализации интерфейса: " + type.getName()); + return generateSafely(impl.get(), depth); } if (Modifier.isAbstract(type.getModifiers())) { throw new IllegalArgumentException("Класс " + type.getName() + " является абстрактным, не удаётся создать экземпляр"); } - if (!type.isAnnotationPresent(CustomClassGenerator.class)) { - throw new IllegalArgumentException("Класс " + type.getName() + " нельзя сгенерировать. Требуется аннотация @CustomClassGenerator"); + if (depth == 0) { + return null; } - var constructors = type.getDeclaredConstructors(); - if (constructors.length == 0) throw new IllegalArgumentException("Класс " + type.getName() + " не имеет конструкторов"); - + var stack = constructing.get(); + if (stack.contains(type) && !Objects.equals(stack.peek(), type)) { + return null; + } - Constructor constructor = constructors[RandomProvider.RANDOM.nextInt(constructors.length)]; - var args = buildArgsForConstructor(constructor); + stack.push(type); try { - constructor.setAccessible(true); - return type.cast(constructor.newInstance(args)); - } catch (Throwable e) { - throw new RuntimeException("Не удалось создать экземпляр " + type.getName(), e); + if (!type.isAnnotationPresent(Generatable.class)) { + throw new IllegalArgumentException("Класс " + type.getName() + " нельзя сгенерировать. Требуется аннотация @CustomClassGenerator"); + } + + var constructors = type.getDeclaredConstructors(); + if (constructors.length == 0) throw new IllegalArgumentException("Класс " + type.getName() + " не имеет конструкторов"); + + Constructor constructor = constructors[RandomProvider.RANDOM.nextInt(constructors.length)]; + + int allowedForChildren = depth > 0 ? depth - 1 : -1; + var args = buildArgsForConstructor(constructor, allowedForChildren); + try { + return type.cast(constructor.newInstance(args)); + } catch (Throwable e) { + throw new RuntimeException("Не удалось создать экземпляр " + type.getName(), e); + } + } finally { + stack.pop(); + if (stack.isEmpty()) constructing.remove(); } } - private Object generateSafely(Class currentClass) { + private Object generateSafely(Class currentClass, int depth) { try { - return generateValueOf(currentClass); + return generateValueOf(currentClass, depth); } catch (Exception e) { throw new RuntimeException("Не удается создать значение для " + currentClass.getName(), e); } } - private Object[] buildArgsForConstructor(Constructor constructor) { + private Object generateSafely(Class currentClass) { + return generateSafely(currentClass, -1); + } + + private Object[] buildArgsForConstructor(Constructor constructor, int allowedSelfDepthForChildren) { var parameters = constructor.getParameters(); var args = new Object[parameters.length]; + Class declaringClass = constructor.getDeclaringClass(); + + final Integer[] perConstructorSelfDepth = new Integer[] { + allowedSelfDepthForChildren >= 0 ? allowedSelfDepthForChildren : null + }; IntStream.range(0, parameters.length).forEach(i -> { var parameter = parameters[i]; @@ -108,17 +144,21 @@ private Object[] buildArgsForConstructor(Constructor constructor) { return; } - if (Collection.class.isAssignableFrom(parameterType)) { - args[i] = Collections.emptyList(); - return; - } - if (parameterType.isInterface()) { var impl = implementationIndex.randomImplementationFor(parameterType); args[i] = impl.map(this::generateSafely).orElse(null); return; } + if (parameterType.equals(declaringClass)) { + if (perConstructorSelfDepth[0] == null) { + perConstructorSelfDepth[0] = 1 + RandomProvider.RANDOM.nextInt(3); + } + int depthToPass = perConstructorSelfDepth[0]; + args[i] = generateSafely(parameterType, depthToPass); + return; + } + args[i] = generateSafely(parameterType); }); @@ -141,4 +181,4 @@ private Class resolveClassFromType(Type type) { return null; } -} +} \ No newline at end of file diff --git a/src/main/java/org/example/generator/annotation/CustomClassGenerator.java b/src/main/java/org/example/generator/annotation/CustomClassGenerator.java deleted file mode 100644 index 43e4ab2..0000000 --- a/src/main/java/org/example/generator/annotation/CustomClassGenerator.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.example.generator.annotation; - -import java.lang.annotation.*; - -@Inherited -@Target(value= ElementType.TYPE) -@Retention(value= RetentionPolicy.RUNTIME) -public @interface CustomClassGenerator { - Class[] implementsFor() default {}; -} diff --git a/src/main/java/org/example/generator/annotation/Generatable.java b/src/main/java/org/example/generator/annotation/Generatable.java new file mode 100644 index 0000000..44f876a --- /dev/null +++ b/src/main/java/org/example/generator/annotation/Generatable.java @@ -0,0 +1,9 @@ +package org.example.generator.annotation; + +import java.lang.annotation.*; + +@Target(value = ElementType.TYPE) +@Retention(value = RetentionPolicy.RUNTIME) +public @interface Generatable { + Class[] implementsFor() default {}; +}