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** Ваша задача --- написать генератор экземпляров произвольных классов. diff --git a/build.gradle.kts b/build.gradle.kts index b4738ae..b5970f3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -10,6 +10,9 @@ repositories { } dependencies { + compileOnly("org.projectlombok:lombok:1.18.42") + annotationProcessor("org.projectlombok:lombok:1.18.42") + testImplementation(platform("org.junit:junit-bom:5.10.0")) testImplementation("org.junit.jupiter:junit-jupiter") testRuntimeOnly("org.junit.platform:junit-platform-launcher") diff --git a/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..af2223c 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,17 +1,96 @@ package org.example; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; -import org.example.classes.Example; +import org.example.classes.*; import org.example.generator.Generator; public class GenerateExample { - public static void main(String[] args) { + public static void main(String[] args) throws NoSuchFieldException { var gen = new Generator(); + + System.out.println("=== Testing Generator ==="); + + try { + System.out.println("\n1. Generating Example:"); + Object example = gen.generateValueOfType(Example.class); + System.out.println("Generated: " + example); + } catch (Exception e) { + System.err.println("Error generating Example: " + e.getMessage()); + } + try { - Object generated = gen.generateValueOfType(Example.class); - System.out.println(generated); - } catch (Throwable e) { - throw new RuntimeException(e); + System.out.println("\n2. Generating Shape:"); + Object shape = gen.generateValueOfType(Shape.class); + System.out.println("Generated: " + shape); + if (shape instanceof Shape s) { + System.out.println("Area: " + s.getArea()); + System.out.println("Perimeter: " + s.getPerimeter()); + } + } catch (Exception e) { + System.err.println("Error generating Shape: " + e.getMessage()); + } + + try { + System.out.println("\n3. Generating Product:"); + Object product = gen.generateValueOfType(Product.class); + System.out.println("Generated: " + product); + } catch (Exception e) { + System.err.println("Error generating Product: " + e.getMessage()); + } + + try { + System.out.println("\n4. Generating Rectangle:"); + Object rectangle = gen.generateValueOfType(Rectangle.class); + System.out.println("Generated: " + rectangle); + if (rectangle instanceof Rectangle r) { + System.out.println("Area: " + r.getArea()); + System.out.println("Perimeter: " + r.getPerimeter()); + } + } catch (Exception e) { + System.err.println("Error generating Rectangle: " + e.getMessage()); + } + + try { + System.out.println("\n5. Generating Triangle:"); + Object triangle = gen.generateValueOfType(Triangle.class); + System.out.println("Generated: " + triangle); + if (triangle instanceof Triangle t) { + System.out.println("Area: " + t.getArea()); + System.out.println("Perimeter: " + t.getPerimeter()); + } + } catch (Exception e) { + System.err.println("Error generating Triangle: " + e.getMessage()); + } + + try { + System.out.println("\n6. Generating Cart:"); + Object cart = gen.generateValueOfType(Cart.class); + System.out.println("Generated: " + cart); + } catch (Exception e) { + System.err.println("Error generating Cart: " + e.getMessage()); + } + + try { + System.out.println("\n7. Generating BinaryTreeNode:"); + Object node = gen.generateValueOfType(BinaryTreeNode.class); + System.out.println("Generated: " + node); + } catch (Exception e) { + System.err.println("Error generating BinaryTreeNode: " + e.getMessage()); + } + + Field items = Cart.class.getDeclaredField("items"); + Type genericType = items.getGenericType(); + if (genericType instanceof ParameterizedType pType) { + Type[] actualTypeArguments = pType.getActualTypeArguments(); + for (Type type : actualTypeArguments) { + if (type instanceof Class elementClass) { + System.out.println("Declared element type: " + elementClass.getName()); + // Output: Declared element type: java.lang.String + } + } } } } \ No newline at end of file diff --git a/src/main/java/org/example/classes/BinaryTreeNode.java b/src/main/java/org/example/classes/BinaryTreeNode.java index 046ff56..e6af71a 100644 --- a/src/main/java/org/example/classes/BinaryTreeNode.java +++ b/src/main/java/org/example/classes/BinaryTreeNode.java @@ -1,5 +1,14 @@ package org.example.classes; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; +import org.example.generator.Generatable; + +@Getter +@Setter +@ToString +@Generatable public class BinaryTreeNode { private Integer data; private BinaryTreeNode left; @@ -10,24 +19,4 @@ public BinaryTreeNode(Integer data, BinaryTreeNode left, BinaryTreeNode right) { this.left = left; this.right = right; } - - public Integer getData() { - return data; - } - - public BinaryTreeNode getLeft() { - return left; - } - - public BinaryTreeNode getRight() { - return right; - } - - public void setLeft(BinaryTreeNode left) { - this.left = left; - } - - public void setRight(BinaryTreeNode right) { - this.right = right; - } } diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..2a08d1d 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/main/java/org/example/classes/Cart.java @@ -2,6 +2,11 @@ import java.util.List; +import lombok.ToString; +import org.example.generator.Generatable; + +@ToString +@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 eac9463..8080791 100644 --- a/src/main/java/org/example/classes/Example.java +++ b/src/main/java/org/example/classes/Example.java @@ -1,14 +1,17 @@ package org.example.classes; +import java.util.Map; + +import lombok.ToString; +import org.example.generator.Generatable; + +@ToString +@Generatable public class Example { int i; + public Map myMap; public Example(int i) { this.i = i; } - - @Override - public String toString() { - return "Example(" + i + ")"; - } } diff --git a/src/main/java/org/example/classes/Product.java b/src/main/java/org/example/classes/Product.java index e7dcc89..d8d0923 100644 --- a/src/main/java/org/example/classes/Product.java +++ b/src/main/java/org/example/classes/Product.java @@ -1,5 +1,10 @@ package org.example.classes; +import lombok.ToString; +import org.example.generator.Generatable; + +@ToString +@Generatable public class Product { private String name; private double price; @@ -40,9 +45,4 @@ public boolean equals(Object obj) { return super.equals(obj); } - @Override - public String toString() { - return super.toString(); - } - } diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..4018fc8 100644 --- a/src/main/java/org/example/classes/Rectangle.java +++ b/src/main/java/org/example/classes/Rectangle.java @@ -1,5 +1,10 @@ package org.example.classes; +import lombok.ToString; +import org.example.generator.Generatable; + +@ToString +@Generatable public class Rectangle implements Shape { private double length; private double width; diff --git a/src/main/java/org/example/classes/Triangle.java b/src/main/java/org/example/classes/Triangle.java index 011e96f..a0a9c85 100644 --- a/src/main/java/org/example/classes/Triangle.java +++ b/src/main/java/org/example/classes/Triangle.java @@ -1,5 +1,10 @@ package org.example.classes; +import lombok.ToString; +import org.example.generator.Generatable; + +@ToString +@Generatable public class Triangle implements Shape { private double sideA; private double sideB; 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..5d02ef2 --- /dev/null +++ b/src/main/java/org/example/generator/Generatable.java @@ -0,0 +1,11 @@ +package org.example.generator; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface Generatable { +} \ 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..f98c30b 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,18 +1,195 @@ package org.example.generator; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Random; +import java.lang.reflect.*; +import java.util.*; public class Generator { - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { + private static final int MAX_COLLECTION_SIZE = 10; + private static final int MAX_DEPTH = 10; + + private final Random random = new Random(); + private final InterfaceRegistry interfaceRegistry = new InterfaceRegistry(); + + public Object generateValueOfType(Class clazz) throws Exception { + return generateValueOfType(clazz, 0); + } + + private Object generateValueOfType(Class clazz, int currentDepth) throws Exception { + return generateValueOfType(clazz, currentDepth, null); + } + + private Object generateValueOfType(Class clazz, int currentDepth, Field field) throws Exception { + if (currentDepth > MAX_DEPTH) { + return null; + } + + if (clazz == int.class || clazz == Integer.class) { + return random.nextInt(1000); + } + if (clazz == double.class || clazz == Double.class) { + return random.nextDouble() * 100; + } + if (clazz == float.class || clazz == Float.class) { + return random.nextFloat() * 100; + } + if (clazz == long.class || clazz == Long.class) { + return random.nextLong(); + } + if (clazz == boolean.class || clazz == Boolean.class) { + return random.nextBoolean(); + } + if (clazz == byte.class || clazz == Byte.class) { + return (byte) random.nextInt(256); + } + if (clazz == short.class || clazz == Short.class) { + return (short) random.nextInt(32768); + } + if (clazz == char.class || clazz == Character.class) { + return (char) (random.nextInt(26) + 'a'); + } + + if (clazz == String.class) { + return generateRandomString(); + } + + if (List.class.isAssignableFrom(clazz)) { + return field == null ? new ArrayList<>() : generateList(field, currentDepth); + } + if (Set.class.isAssignableFrom(clazz)) { + return field == null ? new HashSet<>() : generateSet(field, currentDepth); + } + if (Map.class.isAssignableFrom(clazz)) { + return field == null ? new HashMap<>() : generateMap(field, currentDepth); + } + + if (clazz.isInterface()) { + return generateInterfaceImplementation(clazz, currentDepth); + } + + return generateObject(clazz, currentDepth); + } + + private Object generateObject(Class clazz, int currentDepth) throws Exception { + if (!clazz.isAnnotationPresent(Generatable.class)) { + return null; + } + + Constructor[] constructors = clazz.getDeclaredConstructors(); + if (constructors.length == 0) { + return null; + } + + Object instance = null; + for (Constructor constructor : constructors) { + constructor.setAccessible(true); + Class[] paramTypes = constructor.getParameterTypes(); + Object[] params = new Object[paramTypes.length]; + + for (int i = 0; i < paramTypes.length; i++) { + params[i] = generateValueOfType(paramTypes[i], currentDepth + 1); + } + + try { + instance = constructor.newInstance(params); + } catch (Exception e) { + System.out.println("Error generating object: " + e.getMessage()); + } + } + if (instance == null) { + return null; + } + + populateFields(instance, clazz, currentDepth); + return instance; + } + + public void populateFields(Object instance, Class clazz, int currentDepth) { + for (Field field : getAllFields(clazz)) { + if (Modifier.isFinal(field.getModifiers())) { + continue; + } + + try { + field.setAccessible(true); + Object fieldValue = generateValueOfType(field.getType(), currentDepth + 1, field); + field.set(instance, fieldValue); + } catch (Exception ignored) { + } + } + } + + private List generateList(Field field, int currentDepth) throws Exception { + List list = new ArrayList<>(); + Class elementClass = resolveSingleTypeArgument(field, 0); + int size = random.nextInt(MAX_COLLECTION_SIZE); - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); + for (int i = 0; i < size; i++) { + list.add(elementClass != null ? generateValueOfType(elementClass, currentDepth + 1) : null); + } + return list; } + private Set generateSet(Field field, int currentDepth) throws Exception { + Set set = new HashSet<>(); + Class elementClass = resolveSingleTypeArgument(field, 0); + int size = random.nextInt(MAX_COLLECTION_SIZE); + + for (int i = 0; i < size; i++) { + set.add(elementClass != null ? generateValueOfType(elementClass, currentDepth + 1) : null); + } + return set; + } + + private Map generateMap(Field field, int currentDepth) throws Exception { + Map map = new HashMap<>(); + Class keyClass = resolveSingleTypeArgument(field, 0); + Class valueClass = resolveSingleTypeArgument(field, 1); + int size = random.nextInt(MAX_COLLECTION_SIZE); + + for (int i = 0; i < size; i++) { + Object key = keyClass != null ? generateValueOfType(keyClass, currentDepth + 1) : null; + Object value = valueClass != null ? generateValueOfType(valueClass, currentDepth + 1) : null; + map.put(key, value); + } + return map; + } + public static Class resolveSingleTypeArgument(Field field, int idx) { + Type genericType = field.getGenericType(); + if (genericType instanceof ParameterizedType p) { + Type[] args = p.getActualTypeArguments(); + if (idx >= 0 && idx < args.length && args[idx] instanceof Class clazz) { + return clazz; + } + } + return null; + } + + public static List getAllFields(Class type) { + List result = new ArrayList<>(); + Class cur = type; + while (cur != null && cur != Object.class) { + Collections.addAll(result, cur.getDeclaredFields()); + cur = cur.getSuperclass(); + } + return result; + } + + private String generateRandomString() { + String[] words = {"apple", "banana", "cherry", "dog", "elephant", "forest", "guitar", "house", "ice", "jungle"}; + return words[random.nextInt(words.length)] + random.nextInt(100); + } + + private Object generateInterfaceImplementation(Class interfaceClass, int depth) throws Exception { + List> implementations = interfaceRegistry.getImplementations(interfaceClass); + + if (implementations.isEmpty()) { + throw new IllegalArgumentException("No @Generatable implementations found for interface: " + interfaceClass.getName()); + } + + Class selectedImpl = implementations.get(random.nextInt(implementations.size())); + return generateValueOfType(selectedImpl, depth); + } } diff --git a/src/main/java/org/example/generator/InterfaceRegistry.java b/src/main/java/org/example/generator/InterfaceRegistry.java new file mode 100644 index 0000000..74bf989 --- /dev/null +++ b/src/main/java/org/example/generator/InterfaceRegistry.java @@ -0,0 +1,76 @@ +package org.example.generator; + +import java.io.File; +import java.net.URL; +import java.util.*; + +public class InterfaceRegistry { + private static final String PACKAGE_NAME = "org.example.classes"; + + private final Map, List>> interfaceToImplementations = new HashMap<>(); + private boolean initialized = false; + + public List> getImplementations(Class interfaceClass) { + if (!initialized) { + scanForGeneratableClasses(); + } + return interfaceToImplementations.getOrDefault(interfaceClass, Collections.emptyList()); + } + + private void registerClass(Class clazz) { + if (!clazz.isAnnotationPresent(Generatable.class)) { + return; + } + + Class[] interfaces = clazz.getInterfaces(); + for (Class interfaceClass : interfaces) { + interfaceToImplementations + .computeIfAbsent(interfaceClass, k -> new ArrayList<>()) + .add(clazz); + } + } + + private void scanForGeneratableClasses() { + try { + String packagePath = PACKAGE_NAME.replace('.', '/'); + + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Enumeration resources = classLoader.getResources(packagePath); + + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + if (resource.getProtocol().equals("file")) { + scanDirectory(new File(resource.getFile()), PACKAGE_NAME); + } + } + + initialized = true; + } catch (Exception e) { + System.err.println("Error scanning for @Generatable classes: " + e.getMessage()); + } + } + + private void scanDirectory(File directory, String packageName) { + if (!directory.exists()) { + return; + } + + File[] files = directory.listFiles(); + if (files == null) { + return; + } + + for (File file : files) { + if (file.isDirectory()) { + scanDirectory(file, packageName + "." + file.getName()); + } else if (file.getName().endsWith(".class")) { + String className = packageName + "." + file.getName().substring(0, file.getName().length() - 6); + try { + Class clazz = Class.forName(className); + registerClass(clazz); + } catch (ClassNotFoundException ignored) { + } + } + } + } +} \ No newline at end of file diff --git a/src/test/java/org/example/generator/GeneratorTest.java b/src/test/java/org/example/generator/GeneratorTest.java new file mode 100644 index 0000000..91fa07a --- /dev/null +++ b/src/test/java/org/example/generator/GeneratorTest.java @@ -0,0 +1,64 @@ +package org.example.generator; + +import org.example.classes.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; + +public class GeneratorTest { + + private Generator generator; + + @BeforeEach + void setUp() { + generator = new Generator(); + } + + @Test + void testGenerateExample_ShouldCreateObject() throws Exception { + Object result = generator.generateValueOfType(Example.class); + + assertNotNull(result, "Generated object should not be null"); + assertInstanceOf(Example.class, result, "Generated object should be instance of Example"); + } + + @Test + void testGenerateProduct_ShouldCreateObject() throws Exception { + Object result = generator.generateValueOfType(Product.class); + + assertNotNull(result, "Generated Product should not be null"); + assertInstanceOf(Product.class, result, "Generated object should be instance of Product"); + + Product product = (Product) result; + assertNotNull(product.getName(), "Product name should not be null"); + assertTrue(product.getPrice() != 0 || product.getPrice() == 0, "Product price should be set"); + } + + @Test + void testGenerateShape_ShouldCreateShapeImplementation() throws Exception { + Object result = generator.generateValueOfType(Shape.class); + + assertNotNull(result, "Generated Shape should not be null"); + assertInstanceOf(Shape.class, result, "Generated object should implement Shape interface"); + + assertTrue(result instanceof Rectangle || result instanceof Triangle, + "Shape implementation should be either Rectangle or Triangle"); + } + + @Test + void testGenerateCart_ShouldCreateCartWithNonEmptyCollection() throws Exception { + Object result = generator.generateValueOfType(Cart.class); + + assertNotNull(result, "Generated Cart should not be null"); + assertInstanceOf(Cart.class, result, "Generated object should be instance of Cart"); + + Cart cart = (Cart) result; + List items = cart.getItems(); + + assertNotNull(items, "Cart items should not be null"); + assertFalse(items.isEmpty(), "Cart should have non-empty collection of items"); + } +} \ No newline at end of file