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/src/main/java/org/example/GenerateExample.java b/src/main/java/org/example/GenerateExample.java index 47679a9..43bb88b 100644 --- a/src/main/java/org/example/GenerateExample.java +++ b/src/main/java/org/example/GenerateExample.java @@ -1,7 +1,13 @@ package org.example; +import org.example.classes.BinaryTreeNode; +import org.example.classes.Cart; import org.example.classes.Example; +import org.example.classes.Product; +import org.example.classes.Rectangle; +import org.example.classes.Shape; +import org.example.classes.Triangle; import org.example.generator.Generator; public class GenerateExample { @@ -10,6 +16,24 @@ public static void main(String[] args) { try { Object generated = gen.generateValueOfType(Example.class); System.out.println(generated); + + Object cart = gen.generateValueOfType(Cart.class); + System.out.println(cart); + + Object binaryTreeNode = gen.generateValueOfType(BinaryTreeNode.class); + System.out.println(binaryTreeNode); + + Object product = gen.generateValueOfType(Product.class); + System.out.println(product); + + Object rectangle = gen.generateValueOfType(Rectangle.class); + System.out.println(rectangle); + + Shape shape = gen.generateValueOfType(Shape.class); + System.out.println(shape); + + Triangle triangle = gen.generateValueOfType(Triangle.class); + System.out.println(triangle); } 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..52a9157 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; diff --git a/src/main/java/org/example/classes/Cart.java b/src/main/java/org/example/classes/Cart.java index 965237d..bd34e2b 100644 --- a/src/main/java/org/example/classes/Cart.java +++ b/src/main/java/org/example/classes/Cart.java @@ -2,6 +2,9 @@ import java.util.List; +import org.example.generator.Generatable; + +@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..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..ba7aa89 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; diff --git a/src/main/java/org/example/classes/Rectangle.java b/src/main/java/org/example/classes/Rectangle.java index 90b0886..5f6b174 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; diff --git a/src/main/java/org/example/classes/Triangle.java b/src/main/java/org/example/classes/Triangle.java index 011e96f..e2c4c5a 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; 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..b9ea2ee --- /dev/null +++ b/src/main/java/org/example/generator/Generatable.java @@ -0,0 +1,10 @@ +package org.example.generator; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.annotation.ElementType; + +@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..4558b13 100644 --- a/src/main/java/org/example/generator/Generator.java +++ b/src/main/java/org/example/generator/Generator.java @@ -1,18 +1,269 @@ package org.example.generator; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Random; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.*; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; public class Generator { - public Object generateValueOfType(Class clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException { - Constructor[] constructors = clazz.getDeclaredConstructors(); + private static final int MAX_DEPTH = 3; + private final Random rnd = new Random(); - int randomConstructorIndex = new Random().nextInt(constructors.length); - Constructor randomConstructor = constructors[randomConstructorIndex]; - return randomConstructor.newInstance(111); + public T generateValueOfType(Class type) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + return (T) generate(type, 0); } + private Object generate(Class type, int depth) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + if (depth > MAX_DEPTH) return defaultFor(type); + + if (type.isPrimitive()) return randomPrimitiveOrWrapper(type); + if (isWrapper(type)) return randomPrimitiveOrWrapper(type); + if (type == String.class) return randomString(); + if (type.isEnum()) return randomEnum(type); + if (type.isArray()) return randomArray(type.getComponentType(), depth); + if (Collection.class.isAssignableFrom(type)) return new ArrayList<>(); + + if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { + Class impl = pickAnnotatedImplementation(type); + + if (impl == null) { + throw new IllegalArgumentException("Нет @Generatable реализаций для: " + type.getName()); + } + + return generate(impl, depth + 1); + } + + if (!type.isAnnotationPresent(Generatable.class)) { + throw new IllegalArgumentException("Класс не помечен @Generatable: " + type.getName()); + } + + Constructor[] constructors = type.getDeclaredConstructors(); + + for (Constructor c : constructors) { + Class[] params = c.getParameterTypes(); + Type[] gParams = c.getGenericParameterTypes(); + Object[] args = new Object[params.length]; + + boolean ok = true; + + for (int i = 0; i < params.length; i++) { + Class p = params[i]; + try { + if (Collection.class.isAssignableFrom(p)) { + args[i] = createAndFillCollection(p, gParams[i], depth); + } else { + args[i] = generate(p, depth + 1); + } + } catch (Throwable t) { + ok = false; + break; + } + } + + if (!ok) { + continue; + } + + try { + return c.newInstance(args); + } catch (InvocationTargetException e) { + System.out.println("Конструктор не подошел, пробуем следующий"); + } + } + + return defaultFor(type); + } + + private Object randomPrimitiveOrWrapper(Class p) { + if (p == boolean.class || p == Boolean.class) return rnd.nextBoolean(); + if (p == byte.class || p == Byte.class) return (byte) rnd.nextInt(); + if (p == short.class || p == Short.class) return (short) rnd.nextInt(); + if (p == char.class || p == Character.class) return (char) ('a' + rnd.nextInt(26)); + if (p == int.class || p == Integer.class) return rnd.nextInt(); + if (p == long.class || p == Long.class) return rnd.nextLong(); + if (p == float.class || p == Float.class) return rnd.nextFloat(); + if (p == double.class || p == Double.class) return rnd.nextDouble(); + throw new IllegalArgumentException("Неизвестный примитив: " + p); + } + + private Object randomEnum(Class e) { + Object[] constants = e.getEnumConstants(); + + if (constants == null || constants.length == 0) { + return null; + } + + return constants[rnd.nextInt(constants.length)]; + } + + private Object randomArray(Class component, int depth) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + int len = rnd.nextInt(3); + Object array = Array.newInstance(component, len); + + for (int i = 0; i < len; i++) { + Array.set(array, i, generate(component, depth + 1)); + } + + return array; + } + + private Collection createAndFillCollection(Class raw, Type gType, int depth) + throws InvocationTargetException, InstantiationException, IllegalAccessException { + Collection coll; + + if (Set.class.isAssignableFrom(raw)) { + coll = new HashSet<>(); + } else { + coll = new ArrayList<>(); + } + + Class elemClass = Object.class; + + if (gType instanceof ParameterizedType pt) { + Type t = pt.getActualTypeArguments()[0]; + + if (t instanceof Class c) { + elemClass = c; + } + else if (t instanceof ParameterizedType pt2 && pt2.getRawType() instanceof Class c2) { + elemClass = c2; + } + } + + int size = 1 + rnd.nextInt(3); + + for (int k = 0; k < size; k++) { + Object val = generate(elemClass, depth + 1); + coll.add(val); + } + + return coll; + } + + private String randomString() { + int n = 3 + rnd.nextInt(8); + StringBuilder sb = new StringBuilder(n); + + for (int i = 0; i < n; i++) { + sb.append((char) ('a' + rnd.nextInt(26))); + } + + return sb.toString(); + } + + private boolean isWrapper(Class c) { + return c == Boolean.class || c == Byte.class || c == Short.class || c == Character.class || + c == Integer.class || c == Long.class || c == Float.class || c == Double.class; + } + + private Object defaultFor(Class type) { + if (type.isPrimitive()) { + if (type == boolean.class) return false; + if (type == byte.class) return (byte) 0; + if (type == short.class) return (short) 0; + if (type == char.class) return (char) 0; + if (type == int.class) return 0; + if (type == long.class) return 0L; + if (type == float.class) return 0f; + if (type == double.class) return 0d; + } + return null; + } + + private Class pickAnnotatedImplementation(Class target) { + String pkg = target.getPackage().getName(); + List> candidates = new ArrayList<>(); + + for (Class c : getAllClassesInPackage(pkg)) { + if (c == target) continue; + if (Modifier.isAbstract(c.getModifiers()) || c.isInterface()) continue; + if (!c.isAnnotationPresent(Generatable.class)) continue; + if (target.isAssignableFrom(c)) candidates.add(c); + } + + if (candidates.isEmpty()) { + return null; + } + + return candidates.get(rnd.nextInt(candidates.size())); + } + + private List> getAllClassesInPackage(String packageName) { + List> classes = new ArrayList<>(); + String path = packageName.replace('.', '/'); + + try { + Enumeration resources = Thread.currentThread().getContextClassLoader().getResources(path); + + while (resources.hasMoreElements()) { + URL resource = resources.nextElement(); + String protocol = resource.getProtocol(); + + if ("file".equals(protocol)) { + String url = resource.getFile(); + String filePath = URLDecoder.decode(url, StandardCharsets.UTF_8); + findAndAddClassesInDirectory(packageName, filePath, classes); + } else if ("jar".equals(protocol)) { + JarURLConnection conn = (JarURLConnection) resource.openConnection(); + JarFile jar = conn.getJarFile(); + Enumeration entries = jar.entries(); + + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String name = entry.getName(); + if (name.startsWith(path) && name.endsWith(".class") && !entry.isDirectory()) { + String className = name.replace('/', '.').substring(0, name.length() - 6); + tryLoad(classes, className); + } + } + } + } + } catch (IOException e) { + System.out.println("Проблемы IO при чтении классов из пакета"); + } + + return classes; + } + + private void findAndAddClassesInDirectory(String packageName, String dirPath, List> classes) { + File dir = new File(dirPath); + + if (!dir.exists() || !dir.isDirectory()) { + return; + } + + File[] files = dir.listFiles(); + + if (files == null) { + return; + } + + for (File file : files) { + if (file.isDirectory()) { + findAndAddClassesInDirectory(packageName + "." + file.getName(), file.getAbsolutePath(), classes); + } else if (file.getName().endsWith(".class")) { + String className = packageName + '.' + file.getName().substring(0, file.getName().length() - 6); + tryLoad(classes, className); + } + } + } + + private void tryLoad(List> classes, String className) { + try { + classes.add(Class.forName(className)); + } catch (ClassNotFoundException e) { + System.out.println("Class не найден"); + } + } }