From 316ffb0b39bcea5021b72305b11eaf0ab812583c Mon Sep 17 00:00:00 2001 From: "github-classroom[bot]" <66690702+github-classroom[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 14:24:39 +0000 Subject: [PATCH 1/2] 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 d927a3ec26674c12b28bcbd2e141a74fc077f221 Mon Sep 17 00:00:00 2001 From: Maria Barkovskaya Date: Wed, 22 Oct 2025 22:33:30 +0300 Subject: [PATCH 2/2] implement homework --- .../java/org/example/GenerateExample.java | 47 ++- .../org/example/annotations/Generatable.java | 11 + .../org/example/classes/BinaryTreeNode.java | 14 + src/main/java/org/example/classes/Cart.java | 12 + .../java/org/example/classes/Example.java | 3 + .../java/org/example/classes/Product.java | 13 +- .../java/org/example/classes/Rectangle.java | 15 + src/main/java/org/example/classes/Shape.java | 3 + .../java/org/example/classes/Triangle.java | 16 + .../java/org/example/generator/Generator.java | 336 +++++++++++++++++- 10 files changed, 459 insertions(+), 11 deletions(-) create mode 100644 src/main/java/org/example/annotations/Generatable.java diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..9b704f5 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,17 +1,60 @@ package org.example; - -import org.example.classes.Example; +import org.example.classes.*; import org.example.generator.Generator; public class GenerateExample { public static void main(String[] args) { var gen = new Generator(); + + try { + Object generated = gen.generateValueOfType(BinaryTreeNode.class); + System.out.println(generated); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + try { + Object generated = gen.generateValueOfType(Cart.class); + System.out.println(generated); + } catch (Throwable e) { + throw new RuntimeException(e); + } + try { Object generated = gen.generateValueOfType(Example.class); System.out.println(generated); } catch (Throwable e) { throw new RuntimeException(e); } + + try { + Object generated = gen.generateValueOfType(Product.class); + System.out.println(generated); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + try { + Object generated = gen.generateValueOfType(Rectangle.class); + System.out.println(generated); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + try { + Object generated = gen.generateValueOfType(Shape.class); + System.out.println(generated); + } catch (Throwable e) { + throw new RuntimeException(e); + } + + try { + Object generated = gen.generateValueOfType(Triangle.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/annotations/Generatable.java b/src/main/java/org/example/annotations/Generatable.java new file mode 100644 index 0000000..decec9a --- /dev/null +++ b/src/main/java/org/example/annotations/Generatable.java @@ -0,0 +1,11 @@ +package org.example.annotations; + +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/BinaryTreeNode.java b/src/main/java/org/example/classes/BinaryTreeNode.java index 046ff56..f9ee01c 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.annotations.Generatable; + +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; @@ -30,4 +33,15 @@ public void setLeft(BinaryTreeNode left) { public void setRight(BinaryTreeNode right) { this.right = right; } + + @Override + public String toString() { + return new StringBuilder() + .append("BinaryTreeNode{") + .append("data=").append(data) + .append(", left=").append(left != null ? left.toString() : "null") + .append(", right=").append(right != null ? right.toString() : "null") + .append("}") + .toString(); + } } diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..76248b1 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.annotations.Generatable; + import java.util.List; +@Generatable public class Cart { private List items; @@ -18,4 +21,13 @@ public void setItems(List items) { } // Конструктор, методы добавления и удаления товаров, геттеры и другие методы + + @Override + public String toString() { + return new StringBuilder() + .append("Cart{") + .append("items=").append(items != null ? items.toString() : "null") + .append('}') + .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..3d32c58 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.annotations.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..1d0d1b4 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.annotations.Generatable; + +@Generatable public class Product { private String name; private double price; @@ -42,7 +45,15 @@ public boolean equals(Object obj) { @Override public String toString() { - return super.toString(); + return new StringBuilder() + .append("Product{") + .append("name='") + .append(name) + .append('\'') + .append(", price=") + .append(price) + .append('}') + .toString(); } } diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..78b2d5d 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.annotations.Generatable; + +@Generatable public class Rectangle implements Shape { private double length; private double width; @@ -18,4 +21,16 @@ public double getArea() { public double getPerimeter() { return 2 * (length + width); } + + @Override + public String toString() { + return new StringBuilder() + .append("Rectangle{") + .append("length=").append(length) + .append(", width=").append(width) + .append(", area=").append(getArea()) + .append(", perimeter=").append(getPerimeter()) + .append('}') + .toString(); + } } \ 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..f659c32 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.annotations.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..293bcc5 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.annotations.Generatable; + +@Generatable public class Triangle implements Shape { private double sideA; private double sideB; @@ -21,4 +24,17 @@ public double getArea() { public double getPerimeter() { return sideA + sideB + sideC; } + + @Override + public String toString() { + return new StringBuilder() + .append("Triangle{") + .append("sideA=").append(sideA) + .append(", sideB=").append(sideB) + .append(", sideC=").append(sideC) + .append(", area=").append(getArea()) + .append(", perimeter=").append(getPerimeter()) + .append('}') + .toString(); + } } \ 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..edcc580 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,18 +1,338 @@ package org.example.generator; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Random; +import org.example.annotations.Generatable; + +import java.io.File; +import java.lang.reflect.*; +import java.net.URL; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; public class Generator { + private final Random random; + private final int maxRecursionDepth; + private final Map, ObjectGenerator> primitiveGenerators; + private final Map, List>> interfaceImplementations; + private static final String packageName = "org.example.classes"; + + private static final Set> IMMUTABLE_TYPES = Set.of( + String.class, Integer.class, int.class, Long.class, long.class, + Double.class, double.class, Float.class, float.class, + Boolean.class, boolean.class, Character.class, char.class, + Byte.class, byte.class, Short.class, short.class + ); + + public Generator() { + this(new Random(), 3); + } + + public Generator(Random random, int maxRecursionDepth) { + this.random = random; + this.maxRecursionDepth = maxRecursionDepth; + this.primitiveGenerators = createPrimitiveGenerators(); + this.interfaceImplementations = new ConcurrentHashMap<>(); + } + + public Object generateValueOfType(Class clazz) throws RuntimeException { + return generateValueOfType(clazz, 0); + } + + private Object generateValueOfType(Class clazz, int depth) throws RuntimeException { + if (depth > maxRecursionDepth) { + return getDefaultValue(clazz); + } + + if (!isPrimitiveOrJavaType(clazz) && !clazz.isAnnotationPresent(Generatable.class)) { + throw new IllegalArgumentException( + new StringBuilder() + .append("Класс ") + .append(clazz.getCanonicalName()) + .append(" не является Generatable") + .toString() + ); + } - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { + try { + if (primitiveGenerators.containsKey(clazz)) { + return primitiveGenerators.get(clazz).generate(); + } + + if (clazz.isArray()) { + return generateArray(clazz, depth); + } + + if (Collection.class.isAssignableFrom(clazz)) { + return generateCollection(clazz); + } + + if (Map.class.isAssignableFrom(clazz)) { + return generateMap(); + } + + if (clazz.isEnum()) { + return generateEnum(clazz); + } + + if (clazz.isInterface()) { + Class implementation = findImplementation(clazz); + if (implementation != null) { + return generateValueOfType(implementation, depth); + } else { + throw new IllegalArgumentException( + new StringBuilder() + .append("Интерфейс ") + .append(clazz.getCanonicalName()) + .append(" не имеет Generatable классов") + .toString() + ); + } + } + + return generateCustomClassInstance(clazz, depth); + } catch (Exception e) { + throw new RuntimeException("Failed to create instance of " + clazz.getName(), e); + } + } + + private Object generateCustomClassInstance(Class clazz, int depth) throws InvocationTargetException, InstantiationException, IllegalAccessException, IllegalArgumentException { Constructor[] constructors = clazz.getDeclaredConstructors(); + Constructor chosenConstructor = constructors[random.nextInt(constructors.length)]; + + chosenConstructor.setAccessible(true); + + Class[] parameterTypes = chosenConstructor.getParameterTypes(); + Object[] generatedParameters = new Object[parameterTypes.length]; - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); + for (int i = 0; i < parameterTypes.length; i++) { + generatedParameters[i] = generateValueOfType(parameterTypes[i], depth + 1); + } + + Object instance = chosenConstructor.newInstance(generatedParameters); + + Field[] fields = clazz.getDeclaredFields(); + + for (Field field : fields) { + if ( + Modifier.isFinal(field.getModifiers()) || + Modifier.isStatic(field.getModifiers()) + ) { + continue; + } + + field.setAccessible(true); + + Object fieldValue = createFieldValue(field, depth); + field.set(instance, fieldValue); + } + + return instance; + } + + private Object createFieldValue(Field field, int depth) throws RuntimeException { + Class fieldType = field.getType(); + + if (Collection.class.isAssignableFrom(fieldType)) { + return generateCollectionWithGenerics(field, depth); + } + + if (Map.class.isAssignableFrom(fieldType)) { + return generateMapWithGenerics(field, depth); + } + + return generateValueOfType(fieldType, depth + 1); + } + + private Collection generateCollection(Class collectionType) { + if (Set.class.isAssignableFrom(collectionType)) { + return new HashSet<>(); + } else if (Queue.class.isAssignableFrom(collectionType)) { + return new LinkedList<>(); + } else { + return new ArrayList<>(); + } + } + + private Object generateCollectionWithGenerics(Field field, int depth) throws RuntimeException { + Type genericType = field.getGenericType(); + Collection collection = generateCollection(field.getType()); + + if (genericType instanceof ParameterizedType parameterizedType) { + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + if (typeArgs.length == 1 && typeArgs[0] instanceof Class elementType) { + int length = random.nextInt(1, 3); + + for (int i = 0; i < length; i++) { + Object element = generateValueOfType(elementType, depth + 1); + collection.add(element); + } + } + } + + return collection; } + private Map generateMap() { + return new HashMap<>(); + } -} + private Object generateMapWithGenerics(Field field, int depth) throws RuntimeException { + Type genericType = field.getGenericType(); + Map map = generateMap(); + + if (genericType instanceof ParameterizedType parameterizedType) { + Type[] typeArgs = parameterizedType.getActualTypeArguments(); + + if ( + typeArgs.length == 2 && + typeArgs[0] instanceof Class keyType && + typeArgs[1] instanceof Class valueType + ) { + + if (!IMMUTABLE_TYPES.contains(keyType) && !keyType.isEnum()) { + throw new IllegalArgumentException("Тип ключа для map должен быть immutable или enum: " + keyType); + } + + int length = random.nextInt(1, 3); + + for (int i = 0; i < length; i++) { + Object key = generateValueOfType(keyType, depth + 1); + Object value = generateValueOfType(valueType, depth + 1); + map.put(key, value); + } + } + } + + return map; + } + + private Object generateArray(Class arrayType, int depth) { + Class componentType = arrayType.getComponentType(); + int length = random.nextInt(1, 3); + Object array = Array.newInstance(componentType, length); + + for (int i = 0; i < length; i++) { + Object element = generateValueOfType(componentType, depth + 1); + Array.set(array, i, element); + } + + return array; + } + + private Object generateEnum(Class enumType) { + Object[] constants = enumType.getEnumConstants(); + + if (constants.length == 0) { + return null; + } else { + return constants[random.nextInt(constants.length)]; + } + } + + private Class findImplementation(Class interfaceType) { + List> listOfImplementations = interfaceImplementations.computeIfAbsent(interfaceType, this::scanImplementations); + + if (listOfImplementations.isEmpty()) { + return null; + } else { + return listOfImplementations.get(random.nextInt(listOfImplementations.size())); + } + } + + private List> scanImplementations(Class interfaceType) { + List> implementations = new ArrayList<>(); + + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + + String path = packageName.replace('.', '/'); + URL resource = classLoader.getResource(path); + + if (resource != null && resource.getProtocol().equals("file")) { + findClassesInDirectory(new File(resource.toURI()), packageName, interfaceType, implementations); + } + } catch (Exception e) { + throw new RuntimeException("Error scanning implementations of " + interfaceType.getCanonicalName(), e); + } + + return implementations; + } + + private void findClassesInDirectory( + File directory, + String packageName, + Class interfaceType, + List> implementations + ) { + File[] files = directory.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + if (file.isDirectory()) { + findClassesInDirectory(file, packageName + "." + file.getName(), interfaceType, implementations); + } else if (file.getName().endsWith(".class")) { + String className = packageName + '.' + file.getName().replace(".class", ""); + + try { + Class clazz = Class.forName(className); + + if (interfaceType.isAssignableFrom(clazz) && !clazz.isInterface() && clazz.isAnnotationPresent(Generatable.class)) { + implementations.add(clazz); + } + } catch (ClassNotFoundException e) { + System.err.println("Can't load class " + className); + } + } + } + } + + private Object getDefaultValue(Class clazz) { + if (primitiveGenerators.containsKey(clazz)) { + return primitiveGenerators.get(clazz).generate(); + } + + return null; + } + + private boolean isPrimitiveOrJavaType(Class clazz) { + return primitiveGenerators.containsKey(clazz) || + clazz.isArray() || + Collection.class.isAssignableFrom(clazz) || + Map.class.isAssignableFrom(clazz) || + clazz.isEnum(); + } + + private Map, ObjectGenerator> createPrimitiveGenerators() { + Map, ObjectGenerator> generators = new HashMap<>(); + + generators.put(boolean.class, random::nextBoolean); + generators.put(Boolean.class, random::nextBoolean); + 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(char.class, () -> (char)(random.nextInt('z' - 'a' + 1) + 'a')); + generators.put(Character.class, () -> (char)(random.nextInt('z' - 'a' + 1) + 'a')); + generators.put(byte.class, () -> (byte) random.nextInt(Byte.MAX_VALUE)); + generators.put(Byte.class, () -> (byte) random.nextInt(Byte.MAX_VALUE)); + generators.put(short.class, () -> (short) random.nextInt(Short.MAX_VALUE)); + generators.put(Short.class, () -> (short) random.nextInt(Short.MAX_VALUE)); + generators.put(String.class, () -> random.ints('a', 'z' + 1) + .limit(random.nextInt(3,20)) + .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) + .toString()); + + return generators; + } + + @FunctionalInterface + private interface ObjectGenerator { + T generate(); + } +} \ No newline at end of file