commit 094318904c948f6e478c3e3a21cc70dfa7948955 Author: DmitriyMX Date: Wed Apr 15 13:08:06 2020 +0300 import code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f84b18 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# IDEA # +.idea/ +out/ +*.iml +*.ipr +*.iws +*.ids + +# GRADLE # +.gradle/ +build/ +gradle/ +gradlew +gradlew.bat +publish.gradle diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..84deb08 --- /dev/null +++ b/build.gradle @@ -0,0 +1,37 @@ +/* Gradle 5.3 */ + +apply plugin: 'java' + +wrapper { + gradleVersion = '5.3' + distributionType = Wrapper.DistributionType.BIN +} + +project.group = projectGroup +project.version = projectVersion + +compileJava { + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + options.encoding = 'UTF-8' +} + +repositories { + mavenLocal() + mavenCentral() +} + +ext { + lombok_version = '1.18.2' +} + +dependencies { + /* LOMBOK */ + annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version) + compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version) + + /* TESTING */ + testAnnotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version) + testCompileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version) + testImplementation (group: 'junit', name: 'junit', version: '4.12') +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..399e53b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +projectGroup=ru.dmitriymx +projectName=reflection-object +projectVersion=1.0-BETA \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..569adbf --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = projectName diff --git a/src/main/java/ru/dmitriymx/reflection/ReflectionClass.java b/src/main/java/ru/dmitriymx/reflection/ReflectionClass.java new file mode 100644 index 0000000..e205866 --- /dev/null +++ b/src/main/java/ru/dmitriymx/reflection/ReflectionClass.java @@ -0,0 +1,106 @@ +package ru.dmitriymx.reflection; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class ReflectionClass { + + private final Class clazz; + + public ReflectionClass(Class clazz) { + this.clazz = clazz; + } + + public ReflectionClass(String className) { + this.clazz = ReflectionClass.forName(className); + } + + @SuppressWarnings("unchecked") + public ReflectionConstructor constructor(Class... parameterTypes) { + try { + return new ReflectionConstructor(this.clazz.getDeclaredConstructor(parameterTypes)); + } catch (NoSuchMethodException e) { + throw new ReflectionException(e); + } + } + + public List fieldsList() { + Field[] declaredFields = clazz.getDeclaredFields(); + + if (declaredFields.length > 0) { + List result = new ArrayList<>(10); + + for (Field field : declaredFields) { + if (Modifier.isStatic(field.getModifiers())) { + result.add(new ReflectionField(clazz, field)); + } + } + + return result; + } else { + return Collections.emptyList(); + } + } + + public ReflectionField field(String name) { + try { + Field field = clazz.getDeclaredField(name); + if (Modifier.isStatic(field.getModifiers())) { + return new ReflectionField(clazz, field); + } else { + throw new ReflectionException( + "No such static field '" + name + "' in Class " + clazz.getCanonicalName()); + } + } catch (NoSuchFieldException e) { + throw new ReflectionException(e); + } + } + + public List methodsList() { + Method[] declaredMethods = clazz.getDeclaredMethods(); + + if (declaredMethods.length > 0) { + List result = new ArrayList<>(10); + + for (Method method : declaredMethods) { + if (Modifier.isStatic(method.getModifiers())) { + result.add(new ReflectionMethod(clazz, method)); + } + } + + return result; + } else { + return Collections.emptyList(); + } + } + + @SuppressWarnings("unchecked") + public ReflectionMethod method(String name, Class... parameterTypes) { + try { + Method method = clazz.getDeclaredMethod(name, parameterTypes); + if (Modifier.isStatic(method.getModifiers())) { + return new ReflectionMethod(clazz, method); + } else { + throw new ReflectionException( + "No such static method '" + name + "(" + Arrays.toString(parameterTypes) + ")' " + + "in Class " + clazz.getCanonicalName() + ); + } + } catch (NoSuchMethodException e) { + throw new ReflectionException(e); + } + } + + public static Class forName(String className) { + try { + return Class.forName(className); + } catch (ClassNotFoundException e) { + throw new ReflectionException(e); + } + } +} diff --git a/src/main/java/ru/dmitriymx/reflection/ReflectionConstructor.java b/src/main/java/ru/dmitriymx/reflection/ReflectionConstructor.java new file mode 100644 index 0000000..92c9ae1 --- /dev/null +++ b/src/main/java/ru/dmitriymx/reflection/ReflectionConstructor.java @@ -0,0 +1,25 @@ +package ru.dmitriymx.reflection; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +public class ReflectionConstructor { + + private final Constructor constructor; + + public ReflectionConstructor(Constructor constructor) { + this.constructor = constructor; + } + + public ReflectionObject newInstance(Object... params) { + try { + if (!constructor.isAccessible()) { + constructor.setAccessible(true); + } + + return new ReflectionObject(constructor.newInstance(params)); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) { + throw new ReflectionException(e); + } + } +} diff --git a/src/main/java/ru/dmitriymx/reflection/ReflectionException.java b/src/main/java/ru/dmitriymx/reflection/ReflectionException.java new file mode 100644 index 0000000..8ca8d6a --- /dev/null +++ b/src/main/java/ru/dmitriymx/reflection/ReflectionException.java @@ -0,0 +1,12 @@ +package ru.dmitriymx.reflection; + +public class ReflectionException extends RuntimeException { + + public ReflectionException(Throwable cause) { + super(cause); + } + + public ReflectionException(String message) { + super(message); + } +} diff --git a/src/main/java/ru/dmitriymx/reflection/ReflectionField.java b/src/main/java/ru/dmitriymx/reflection/ReflectionField.java new file mode 100644 index 0000000..1bcd527 --- /dev/null +++ b/src/main/java/ru/dmitriymx/reflection/ReflectionField.java @@ -0,0 +1,59 @@ +package ru.dmitriymx.reflection; + +import lombok.EqualsAndHashCode; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; + +@EqualsAndHashCode +public class ReflectionField { + + private final Object object; + private final Class clazz; + private final Field field; + + public ReflectionField(Object object, Field field) { + this.object = object; + this.clazz = object.getClass(); + this.field = field; + } + + public ReflectionField(Class clazz, Field field) { + this.object = null; + this.clazz = clazz; + this.field = field; + } + + public String name() { + return field.getName(); + } + + public ReflectionObject get() { + try { + if (!field.isAccessible()) { + field.setAccessible(true); + } + + return new ReflectionObject(field.get(object)); + } catch (IllegalAccessException e) { + throw new ReflectionException(e); + } + } + + public T get(Class clazz) { + return get().getOriginalObject(clazz); + } + + public boolean isStatic() { + return Modifier.isStatic(field.getModifiers()); + } + + @Override + public String toString() { + return "ReflectionField{" + + "objectClass=" + clazz + + ", fieldName=" + field.getName() + + ", isStatic=" + isStatic() + + '}'; + } +} diff --git a/src/main/java/ru/dmitriymx/reflection/ReflectionMethod.java b/src/main/java/ru/dmitriymx/reflection/ReflectionMethod.java new file mode 100644 index 0000000..a6efac2 --- /dev/null +++ b/src/main/java/ru/dmitriymx/reflection/ReflectionMethod.java @@ -0,0 +1,64 @@ +package ru.dmitriymx.reflection; + +import lombok.EqualsAndHashCode; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +@EqualsAndHashCode +public class ReflectionMethod { + + private final Object object; + private final Class clazz; + private final Method method; + + public ReflectionMethod(Object object, Method method) { + this.object = object; + this.clazz = object.getClass(); + this.method = method; + } + + public ReflectionMethod(Class clazz, Method method) { + this.object = null; + this.clazz = clazz; + this.method = method; + } + + public String name() { + return method.getName(); + } + + public boolean isStatic() { + return Modifier.isStatic(method.getModifiers()); + } + + public ReflectionObject invoke(Object... parameters) { + try { + if (!method.isAccessible()) { + method.setAccessible(true); + } + Object resultInvoke = method.invoke(object, parameters); + if (resultInvoke != null) { + return new ReflectionObject(resultInvoke); + } else { + return ReflectionObject.NULL; + } + } catch (IllegalAccessException | InvocationTargetException e) { + throw new ReflectionException(e); + } + } + + public T invoke(Class clazz, Object... parameters) { + return invoke(parameters).getOriginalObject(clazz); + } + + @Override + public String toString() { + return "ReflectionMethod{" + + "objectClass=" + clazz + + ", methodName=" + method.getName() + + ", isStatic=" + isStatic() + + '}'; + } +} diff --git a/src/main/java/ru/dmitriymx/reflection/ReflectionObject.java b/src/main/java/ru/dmitriymx/reflection/ReflectionObject.java new file mode 100644 index 0000000..ded8434 --- /dev/null +++ b/src/main/java/ru/dmitriymx/reflection/ReflectionObject.java @@ -0,0 +1,100 @@ +package ru.dmitriymx.reflection; + +import lombok.ToString; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@ToString +public class ReflectionObject { + + public static final ReflectionObject NULL = new ReflectionObject(null); + + private final Object object; + + public ReflectionObject(Object object) { + this.object = object; + } + + public Object getOriginalObject() { + return object; + } + + public T getOriginalObject(Class clazz) { + return object == null ? null : clazz.cast(object); + } + + public List fieldList() { + final Class clazz = object.getClass(); + + Field[] declaredFields = clazz.getDeclaredFields(); + + if (declaredFields.length > 0) { + List result = new ArrayList<>(declaredFields.length); + + for (Field field : declaredFields) { + result.add(new ReflectionField(object, field)); + } + + return result; + } else { + return Collections.emptyList(); + } + } + + public ReflectionField field(String name) { + final Class clazz = object.getClass(); + + try { + return new ReflectionField(object, clazz.getDeclaredField(name)); + } catch (NoSuchFieldException e) { + throw new ReflectionException(e); + } + } + + public List methodsList() { + final Class clazz = object.getClass(); + + Method[] declaredMethods = clazz.getDeclaredMethods(); + if (declaredMethods.length > 0) { + List result = new ArrayList<>(declaredMethods.length); + + for (Method method : declaredMethods) { + result.add(new ReflectionMethod(object, method)); + } + + return result; + } else { + return Collections.emptyList(); + } + } + + @SuppressWarnings("unchecked") + public ReflectionMethod method(String methodName, Class... parameterTypes) { + final Class clazz = object.getClass(); + + try { + return new ReflectionMethod(object, clazz.getDeclaredMethod(methodName, parameterTypes)); + } catch (NoSuchMethodException e) { + throw new ReflectionException(e); + } + } + + @Override + public boolean equals(final Object o) { + if (o == this) return true; + if (!(o instanceof ReflectionObject)) return false; + + final ReflectionObject other = (ReflectionObject) o; + return this.object.equals(other.object); + } + + @Override + public int hashCode() { + final int PRIME = 59; + return PRIME + (this.object == null ? 43 : this.object.hashCode()); + } +} diff --git a/src/test/java/ru/dmitriymx/reflection/EmptyClass.java b/src/test/java/ru/dmitriymx/reflection/EmptyClass.java new file mode 100644 index 0000000..7f5afad --- /dev/null +++ b/src/test/java/ru/dmitriymx/reflection/EmptyClass.java @@ -0,0 +1,4 @@ +package ru.dmitriymx.reflection; + +public class EmptyClass { +} diff --git a/src/test/java/ru/dmitriymx/reflection/ReflectionClassTest.java b/src/test/java/ru/dmitriymx/reflection/ReflectionClassTest.java new file mode 100644 index 0000000..a1909ca --- /dev/null +++ b/src/test/java/ru/dmitriymx/reflection/ReflectionClassTest.java @@ -0,0 +1,124 @@ +package ru.dmitriymx.reflection; + +import org.junit.Test; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.*; + +public class ReflectionClassTest { + + @Test + public void fieldsList() { + final List expectedList = Collections.singletonList("MAGIC_NUMBER"); + + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + + List fieldsList = refStaticObj.fieldsList(); + assertNotNull(fieldsList); + fieldsList = fieldsList.stream().filter(refField -> !refField.name().equals("$jacocoData")) + .collect(Collectors.toList()); + assertFalse(fieldsList.isEmpty()); + + for (ReflectionField refField : fieldsList) { + assertTrue( + "Static field '" + refField.name() + "' not contains in expectedList", + expectedList.contains(refField.name())); + } + } + + @Test + public void fieldsList_noFields() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.EmptyClass")); + + assertEquals(0, refStaticObj.fieldsList().stream() + .filter(refField -> !refField.name().equals("$jacocoData")) + .count()); + } + + @Test + public void field() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + ReflectionField refField = refStaticObj.field("MAGIC_NUMBER"); + + assertNotNull(refField); + } + + @Test(expected = ReflectionException.class) + public void field_notExists() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + refStaticObj.field("field_not_exists"); + } + + @Test(expected = ReflectionException.class) + public void field_notStatic() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + refStaticObj.field("simpleField"); + } + + @Test + public void methodsList() { + final List expectedList = Collections.singletonList("getMagicNumber"); + + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + // исключаем проксирующий метод от JaCoCo + List methodList = refStaticObj.methodsList().stream() + .filter(refMethod -> !refMethod.name().equals("$jacocoInit")).collect(Collectors.toList()); + + for (ReflectionMethod refMethod : methodList) { + assertTrue( + "Static method '" + refMethod.name() + "' not contains in expectedList", + expectedList.contains(refMethod.name())); + } + } + + @Test + public void methodsList_noMethods() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.EmptyClass")); + // исключаем проксирующий метод от JaCoCo + List methodList = refStaticObj.methodsList().stream() + .filter(refMethod -> !refMethod.name().equals("$jacocoInit")).collect(Collectors.toList()); + + assertTrue(methodList.isEmpty()); + } + + @Test + public void method() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + + ReflectionMethod refMethod = refStaticObj.method("getMagicNumber"); + assertNotNull(refMethod); + } + + @Test(expected = ReflectionException.class) + public void method_notExists() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + refStaticObj.method("not_exists_method"); + } + + @Test(expected = ReflectionException.class) + public void method_notStatic() { + ReflectionClass refStaticObj = new ReflectionClass( + safeClassForName("ru.dmitriymx.reflection.SomeObject")); + refStaticObj.method("getSimpleField"); + } + + private Class safeClassForName(String clazz) { + try { + return Class.forName(clazz); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/ru/dmitriymx/reflection/ReflectionObjectTest.java b/src/test/java/ru/dmitriymx/reflection/ReflectionObjectTest.java new file mode 100644 index 0000000..0a5c2c1 --- /dev/null +++ b/src/test/java/ru/dmitriymx/reflection/ReflectionObjectTest.java @@ -0,0 +1,108 @@ +package ru.dmitriymx.reflection; + +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.*; + +public class ReflectionObjectTest { + + @Test + public void getOriginalObject() { + final String strObj = "StringObject"; + + assertEquals(strObj, new ReflectionObject(strObj).getOriginalObject()); + } + + @Test + public void getOriginalObject_classCast() { + final String strObj = "StringObject"; + + assertEquals(strObj, new ReflectionObject(strObj).getOriginalObject(String.class)); + } + + @Test(expected = ClassCastException.class) + public void getOriginalObject_classCast_Exception() { + final String strObj = "StringObject"; + + new ReflectionObject(strObj).getOriginalObject(Integer.class); + } + + @Test + public void fieldsList() { + final List expectedList = Arrays.asList("finalizedField", "simpleField"); + + ReflectionObject refObj = new ReflectionObject(new SomeObject()); + for (ReflectionField refField : refObj.fieldList()) { + if (!refField.isStatic()) { + assertTrue( + "Field '" + refField.name() + "' not contains in expectedList", + expectedList.contains(refField.name())); + } + } + } + + @Test + public void fieldsList_noFields() { + ReflectionObject refObj = new ReflectionObject(new Object()); + assertTrue(refObj.fieldList().isEmpty()); + } + + @Test + public void field() { + ReflectionObject refObj = new ReflectionObject(new SomeObject()); + ReflectionField refField = refObj.field("finalizedField"); + + assertNotNull(refField); + } + + @Test(expected = ReflectionException.class) + public void field_notExists() { + ReflectionObject refObj = new ReflectionObject(new SomeObject()); + refObj.field("field_not_exists"); + } + + @Test + public void methodsList() { + final List expectedList = Arrays.asList("getSimpleField", "setSimpleField"); + + ReflectionObject refObj = new ReflectionObject(new SomeObject()); + for (ReflectionMethod refMethod : refObj.methodsList()) { + if (!refMethod.isStatic()) { + assertTrue( + "Method '" + refMethod.name() + "' not contains in expectedList", + expectedList.contains(refMethod.name())); + } + } + } + + @Test + public void methodsList_noMethods() { + ReflectionObject refObj = new ReflectionObject(new EmptyClass()); + // исключаем проксирующий метод от JaCoCo + long count = refObj.methodsList().stream().filter(refMethod -> !refMethod.name().equals("$jacocoInit")).count(); + assertEquals(0, count); + } + + @Test + public void method() { + ReflectionObject refObj = new ReflectionObject(new SomeObject()); + + ReflectionMethod refMethod = refObj.method("getSimpleField"); + assertNotNull(refMethod); + + refMethod = refObj.method("setSimpleField"); + assertNotNull(refMethod); + + refMethod = refObj.method("setSimpleField", String.class); + assertNotNull(refMethod); + } + + @Test(expected = ReflectionException.class) + public void method_notExists() { + ReflectionObject refObj = new ReflectionObject(new SomeObject()); + refObj.method("not_exists_method"); + } +} \ No newline at end of file diff --git a/src/test/java/ru/dmitriymx/reflection/SomeObject.java b/src/test/java/ru/dmitriymx/reflection/SomeObject.java new file mode 100644 index 0000000..26c2545 --- /dev/null +++ b/src/test/java/ru/dmitriymx/reflection/SomeObject.java @@ -0,0 +1,24 @@ +package ru.dmitriymx.reflection; + +class SomeObject { + + private static final int MAGIC_NUMBER = 33; + private final String finalizedField = "value123"; + private String simpleField = "defaultValue"; + + public String getSimpleField() { + return simpleField; + } + + public void setSimpleField(String value) { + simpleField = value; + } + + private void setSimpleField() { + simpleField = "123value"; + } + + public static int getMagicNumber() { + return MAGIC_NUMBER; + } +}