Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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**

Ваша задача --- написать генератор экземпляров произвольных классов.
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/org/example/GenerateExample.java
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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);
}
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/BinaryTreeNode.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.example.classes;

import org.example.generator.Generatable;

@Generatable
public class BinaryTreeNode {
private Integer data;
private BinaryTreeNode left;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/Cart.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

import java.util.List;

import org.example.generator.Generatable;

@Generatable
public class Cart {
private List<Product> items;

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/Example.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.example.classes;

import org.example.generator.Generatable;

@Generatable
public class Example {
int i;

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/Product.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.example.classes;

import org.example.generator.Generatable;

@Generatable
public class Product {
private String name;
private double price;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/Rectangle.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/Triangle.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/example/generator/Generatable.java
Original file line number Diff line number Diff line change
@@ -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 {}
267 changes: 259 additions & 8 deletions src/main/java/org/example/generator/Generator.java
Original file line number Diff line number Diff line change
@@ -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> T generateValueOfType(Class<T> 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<Object> 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<Class<?>> 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<Class<?>> getAllClassesInPackage(String packageName) {
List<Class<?>> classes = new ArrayList<>();
String path = packageName.replace('.', '/');

try {
Enumeration<URL> 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<JarEntry> 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<Class<?>> 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<Class<?>> classes, String className) {
try {
classes.add(Class.forName(className));
} catch (ClassNotFoundException e) {
System.out.println("Class не найден");
}
}
}