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
18 changes: 15 additions & 3 deletions src/main/java/org/example/GenerateExample.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
package org.example;


import org.example.classes.Example;
import org.example.classes.*;
import org.example.generator.Generator;

import java.util.Random;

public class GenerateExample {
public static void main(String[] args) {
var gen = new Generator();
Random random = new Random(42);
var gen = new Generator(random, 3);
generateAndPrint(gen, Product.class);
generateAndPrint(gen, Rectangle.class);
generateAndPrint(gen, Triangle.class);
generateAndPrint(gen, Shape.class);
generateAndPrint(gen, Cart.class);
generateAndPrint(gen, BinaryTreeNode.class);
}

private static void generateAndPrint(Generator generator, Class<?> clazz) {
try {
Object generated = gen.generateValueOfType(Example.class);
Object generated = generator.generateByType(clazz, "org.example.classes");
System.out.println(generated);
} catch (Throwable e) {
throw new RuntimeException(e);
Expand Down
19 changes: 19 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 Expand Up @@ -30,4 +33,20 @@ public void setLeft(BinaryTreeNode left) {
public void setRight(BinaryTreeNode right) {
this.right = right;
}

@Override
public String toString() {
String leftString = "null";
if (this.left != null) {
leftString = this.left.toString();
}
String rightString = "null";
if (this.right != null) {
rightString = this.right.toString();
}
return "BinaryTreeNode(data: " + this.data +
", left: " + leftString +
", right: " + rightString +
")";
}
}
8 changes: 7 additions & 1 deletion src/main/java/org/example/classes/Cart.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package org.example.classes;

import org.example.generator.Generatable;

import java.util.List;

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

Expand All @@ -17,5 +20,8 @@ public void setItems(List<Product> items) {
this.items = items;
}

// Конструктор, методы добавления и удаления товаров, геттеры и другие методы
@Override
public String toString() {
return "Cart(items: " + this.items.toString() + ")";
}
}
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
5 changes: 4 additions & 1 deletion 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 Expand Up @@ -42,7 +45,7 @@ public boolean equals(Object obj) {

@Override
public String toString() {
return super.toString();
return "Product(name: " + this.name + ", price: " + this.price + ")";
}

}
8 changes: 8 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 All @@ -18,4 +21,9 @@ public double getArea() {
public double getPerimeter() {
return 2 * (length + width);
}

@Override
public String toString() {
return "Rectangle(length: " + this.length + ", width: " + this.width + ")";
}
}
3 changes: 3 additions & 0 deletions src/main/java/org/example/classes/Shape.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 interface Shape {
double getArea();
double getPerimeter();
Expand Down
8 changes: 8 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 All @@ -21,4 +24,9 @@ public double getArea() {
public double getPerimeter() {
return sideA + sideB + sideC;
}

@Override
public String toString() {
return "Triangle(sideA: " + this.sideA + ", sideB: " + this.sideB + ", sideC: " + this.sideC + ")";
}
}
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.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 {}
187 changes: 179 additions & 8 deletions src/main/java/org/example/generator/Generator.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,189 @@
package org.example.generator;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Random;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.function.Supplier;

import static org.example.generator.PackageUtils.getAnnotatedClass;

public class Generator {
private final Random random;
private final int maxDepth;

private final Map<Class<?>, Supplier<?>> primitiveTypesGenerators;

private static final Set<Class<?>> IMMUTABLE_KEY_TYPES = Set.of(
int.class, Integer.class, long.class, Long.class, double.class, Double.class,
float.class, Float.class, boolean.class, Boolean.class, char.class, Character.class,
String.class
);

public Generator() {
this(new Random(), 4);
}

public Generator(Random random, int maxDepth) {
this.random = random;
this.maxDepth = maxDepth;
this.primitiveTypesGenerators = initializePrimitiveGenerators();
}

public Object generateByType(Class<?> clazz,
String packageName
)
throws IllegalArgumentException,
InvocationTargetException,
InstantiationException,
IllegalAccessException
{
var annotatedClass = getAnnotatedClass(clazz, Generatable.class, packageName);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Кажется вы забыли тестики. В связи с этим вопрос -- а если я на вход вашему главному методу дам коллекцию какую нибудь типа списка, оно будет работать?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Хотя по условиям этого особо не требуется конечно

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Если коллекция не помечена аннотацией, то главный метод выбросит исключение. Если коллекция помечена аннотацией, то метод просто создаст пустую коллекцию


if (annotatedClass.isEmpty()) {
throw new IllegalArgumentException(clazz.getCanonicalName() + " is not annotated with @" + Generatable.class.getCanonicalName());
}

return createInstance(annotatedClass.get(), 0);
}

private Object createInstance(Class<?> clazz, int depth)
throws InvocationTargetException, InstantiationException, IllegalAccessException {

if (primitiveTypesGenerators.containsKey(clazz)) {
return primitiveTypesGenerators.get(clazz);
}

if (depth > maxDepth) {
return null;
}

if (Collection.class.isAssignableFrom(clazz)) {
return new ArrayList<>();
}

if (Map.class.isAssignableFrom(clazz)) {
return new HashMap<>();
}

var constructors = clazz.getDeclaredConstructors();
var randomConstructorIndex = constructors.length == 0 ? 0 : random.nextInt(constructors.length);
var constructor = constructors[randomConstructorIndex];
constructor.setAccessible(true);

var paramTypes = constructor.getParameterTypes();
var params = new Object[paramTypes.length];

public Object generateValueOfType(Class<?> clazz) throws InvocationTargetException, InstantiationException, IllegalAccessException {
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
for (int i = 0; i < paramTypes.length; i++) {
params[i] = createInstance(paramTypes[i], depth + 1);
}

int randomConstructorIndex = new Random().nextInt(constructors.length);
Constructor<?> randomConstructor = constructors[randomConstructorIndex];
return randomConstructor.newInstance(111);
var instance = constructor.newInstance(params);

// Заполняем поля объекта
for (var field : clazz.getDeclaredFields()) {
// Пропускаем статические поля
if (Modifier.isStatic(field.getModifiers())) {
continue;
}

field.setAccessible(true);
var fieldType = field.getType();
Object value;

if (Collection.class.isAssignableFrom(fieldType)) {
value = generateCollection(field, depth + 1);
} else if (Map.class.isAssignableFrom(fieldType)) {
value = generateMap(field, depth + 1);
} else if (primitiveTypesGenerators.containsKey(fieldType)) {
value = primitiveTypesGenerators.get(fieldType);
} else {
value = createInstance(fieldType, depth + 1);
}

field.set(instance, value);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

а если оно статик? нет ли нюансов с остальными модификаторами?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

да, это может привести к ошибкам. добавил исключение изменения статических полей в коде выше

}

return instance;
}

private Map<Class<?>, Supplier<?>> initializePrimitiveGenerators() {
Map<Class<?>, Supplier<?>> generators = new HashMap<>();

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(boolean.class, random::nextBoolean);
generators.put(Boolean.class, random::nextBoolean);
generators.put(char.class, () -> (char) (random.nextInt(26) + 'a'));
generators.put(Character.class, () -> (char) (random.nextInt(26) + 'a'));
generators.put(String.class, getRandomString());

return generators;
}

private Object generateCollection(Field field, int depth)
throws InvocationTargetException, InstantiationException, IllegalAccessException {
var collection = new ArrayList<>();

}
var genericType = field.getGenericType();
if (genericType instanceof ParameterizedType parameterizedType) {
var typeArgs = parameterizedType.getActualTypeArguments();
if (typeArgs.length == 1 && typeArgs[0] instanceof Class<?> elementClass) {
int size = random.nextInt(3) + 1; // Используем переданный Random
for (int i = 0; i < size; i++) {
collection.add(createInstance(elementClass, depth + 1));
}
}
}
return collection;
}

private Object generateMap(Field field, int depth)
throws InvocationTargetException, InstantiationException, IllegalAccessException
{
var map = new HashMap<>();

var genericType = field.getGenericType();
if (genericType instanceof ParameterizedType parameterizedType) {
var typeArgs = parameterizedType.getActualTypeArguments();
if (typeArgs.length == 2 && typeArgs[0] instanceof Class<?> keyClass &&
typeArgs[1] instanceof Class<?> valueClass) {
int size = random.nextInt(3) + 1; // Используем переданный Random
for (int i = 0; i < size; i++) {
Object key;
if (keyClass.isEnum() || IMMUTABLE_KEY_TYPES.contains(keyClass)) {
key = createInstance(keyClass, depth + 1);
} else {
throw new IllegalArgumentException("key for Map is not immutable");
}
Object value = createInstance(valueClass, depth + 1);
map.put(key, value);
}
}
}

return map;
}

private Supplier<String> getRandomString() {
return () -> {
int length = 10;
char[] chars = new char[length];

for (int i = 0; i < length; i++) {
boolean lower = random.nextBoolean();
char left = lower ? 'a' : 'A';
char right = lower ? 'z' : 'Z';
chars[i] = (char) (random.nextInt(right - left + 1) + left);
}

return new String(chars);
};
}
}
Loading