Skip to content

Commit 4c500f9

Browse files
committed
Reflection utils
1 parent ae52ebd commit 4c500f9

3 files changed

Lines changed: 204 additions & 3 deletions

File tree

src/main/java/org/xomda/common/function/Predicates.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,19 +97,19 @@ public static <T> Predicate<T> isEqual(Object value) {
9797
* </pre></code>
9898
*/
9999
public static <T, V> Predicate<T> against(Function<T, V> getter, V value) {
100-
return against(getter, isEqual(value));
100+
return testAgainst(getter, isEqual(value));
101101
}
102102

103103
/**
104104
* Evaluate a sub-property of the evaluated input object. For example, when processing a stream of persons,
105105
* and you want to filter out the persons with a name called "John". Then you could say:
106106
* <code><pre>
107107
* Stream.of(persons)
108-
* .filter(against(Person::getName, isEqual("John")))
108+
* .filter(testAgainst(Person::getName, isEqual("John")))
109109
* .toList();
110110
* </pre></code>
111111
*/
112-
public static <T, V> Predicate<T> against(Function<T, V> getter, Predicate<V> predicate) {
112+
public static <T, V> Predicate<T> testAgainst(Function<T, V> getter, Predicate<V> predicate) {
113113
return (T value) -> predicate.test(getter.apply(value));
114114
}
115115

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package org.xomda.common.reflect;
2+
3+
import static org.xomda.common.function.Predicates.against;
4+
5+
import java.lang.reflect.Method;
6+
import java.util.Arrays;
7+
import java.util.Objects;
8+
import java.util.Optional;
9+
import java.util.stream.Stream;
10+
11+
import org.xomda.common.exception.SneakyThrow;
12+
13+
/**
14+
* Be careful what you do.
15+
*/
16+
public final class Reflect {
17+
18+
/**
19+
* Unchecked cast without warning.
20+
*/
21+
public static <P> P unchecked(final Object p) {
22+
@SuppressWarnings("unchecked")
23+
final P r = (P) p;
24+
return r;
25+
}
26+
27+
/**
28+
* Find a class with a given classname.
29+
*/
30+
public static <T> Optional<Class<T>> findClass(final String className) {
31+
try {
32+
final Class<T> found = unchecked(Class.forName(className));
33+
return Optional.of(found);
34+
} catch (final ClassNotFoundException e) {
35+
return Optional.empty();
36+
}
37+
}
38+
39+
/**
40+
* Find a class with a given classname and class loader and specify whether to initialize the class or not.
41+
*/
42+
public static <T> Optional<Class<T>> findClass(final String className, final boolean initialize, final ClassLoader classLoader) {
43+
try {
44+
final Class<T> found = unchecked(Class.forName(className, initialize, classLoader));
45+
return Optional.of(found);
46+
} catch (final ClassNotFoundException e) {
47+
return Optional.empty();
48+
}
49+
}
50+
51+
/**
52+
* Find a class with a given classname and class loader.
53+
*/
54+
public static <T> Optional<Class<T>> findClass(final String className, final ClassLoader classLoader) {
55+
return findClass(className, true, classLoader);
56+
}
57+
58+
/**
59+
* Find all methods in given class with a given name.
60+
*/
61+
public static Stream<Method> findMethods(final Class<?> clazz, final String name) {
62+
if (null == name || name.isBlank()) {
63+
throw new IllegalArgumentException("The name should not be blank or null");
64+
}
65+
return Arrays
66+
.stream(clazz.getDeclaredMethods())
67+
.filter(against(Method::getName, name));
68+
}
69+
70+
/**
71+
* Find a method in given class with a given name and arguments.
72+
*/
73+
public static Optional<Method> findMethod(final Class<?> clazz, final String name, Class<?>... args) {
74+
return findMethods(clazz, name)
75+
.filter(null == args || args.length == 0
76+
? (Method method) -> method.getParameterCount() == 0
77+
: (Method method) -> {
78+
if (method.getParameterCount() < args.length) {
79+
return false;
80+
}
81+
Class<?>[] params = method.getParameterTypes();
82+
for (int i = 0; i < args.length; i++) {
83+
if (!Objects.equals(params[i], args[i])) {
84+
return false;
85+
}
86+
}
87+
return true;
88+
})
89+
.findFirst();
90+
}
91+
92+
@FunctionalInterface
93+
public interface Invoker {
94+
<T, E extends Throwable> T invoke(Object subject, Object... args) throws E;
95+
}
96+
97+
/**
98+
* Find a method in given class with a given name and arguments.
99+
*/
100+
public static Optional<Invoker> findInvoker(final Class<?> clazz, final String name, Class<?>... args) {
101+
return findMethod(clazz, name, args)
102+
.map((Method method) -> new Invoker() {
103+
@Override
104+
public <T, E extends Throwable> T invoke(Object subject, final Object... args) throws E {
105+
try {
106+
return unchecked(method.invoke(subject, args));
107+
} catch (Exception e) {
108+
SneakyThrow.throwSneaky(e);
109+
}
110+
return null;
111+
}
112+
});
113+
}
114+
115+
private Reflect() {
116+
}
117+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package org.xomda.common.function;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertNotNull;
6+
import static org.junit.jupiter.api.Assertions.assertTrue;
7+
import static org.xomda.common.function.Predicates.against;
8+
import static org.xomda.common.function.Predicates.testAgainst;
9+
10+
import java.io.IOException;
11+
import java.lang.reflect.Method;
12+
import java.lang.reflect.Modifier;
13+
import java.util.List;
14+
import java.util.Locale;
15+
import java.util.Optional;
16+
17+
import org.junit.jupiter.api.Test;
18+
import org.xomda.common.reflect.Reflect;
19+
20+
public class ReflectTest {
21+
22+
@Test
23+
public void testFindMethods() {
24+
List<Method> methods = Reflect.findMethods(String.class, "getBytes").toList();
25+
26+
assertNotNull(methods);
27+
assertEquals(6, methods.size());
28+
assertEquals(4, methods.stream().filter(testAgainst(Method::getModifiers, Modifier::isPublic)).count());
29+
assertTrue(methods.stream().allMatch(against(Method::getName, "getBytes")));
30+
}
31+
32+
@Test
33+
public void testFindMethod() {
34+
String s = "";
35+
assertTrue(Reflect.findMethod(String.class, "toString").isPresent());
36+
assertTrue(Reflect.findMethod(String.class, "toUpperCase", Locale.class).isPresent());
37+
assertFalse(Reflect.findMethod(String.class, "toUpperCase", Object.class).isPresent());
38+
assertFalse(Reflect.findMethod(String.class, "toUpperCase", String.class).isPresent());
39+
}
40+
41+
@Test
42+
public void testFindClass() {
43+
assertTrue(Reflect.findClass("java.lang.String").isPresent());
44+
assertTrue(Reflect.findClass("java.util.List").isPresent());
45+
assertFalse(Reflect.findClass("java.xxx.xxx").isPresent());
46+
}
47+
48+
@Test
49+
public void testFindClassWithClassLoader() throws IOException {
50+
ClassLoader cl = ReflectTest.class.getClassLoader();
51+
assertTrue(Reflect.findClass("java.lang.String", cl).isPresent());
52+
assertTrue(Reflect.findClass("java.util.List", cl).isPresent());
53+
assertFalse(Reflect.findClass("java.xxx.xxx", cl).isPresent());
54+
55+
ClassLoader cl2 = new ClassLoader() {
56+
@Override
57+
protected Class<?> loadClass(final String name, final boolean resolve) throws ClassNotFoundException {
58+
return null;
59+
}
60+
};
61+
assertFalse(Reflect.findClass("java.lang.String", cl2).isPresent());
62+
assertFalse(Reflect.findClass("java.util.List", cl2).isPresent());
63+
assertFalse(Reflect.findClass("java.xxx.xxx", cl2).isPresent());
64+
}
65+
66+
public record TestInvoke() {
67+
public String test(String foo) {
68+
return "Hello " + foo;
69+
}
70+
}
71+
72+
@Test
73+
public void testFindInvoker() throws IOException {
74+
TestInvoke inv = new TestInvoke();
75+
76+
Optional<Reflect.Invoker> invoker = Reflect.findInvoker(TestInvoke.class, "test", String.class);
77+
assertNotNull(invoker);
78+
assertTrue(invoker.isPresent());
79+
80+
String test = invoker.map(i -> (String) i.invoke(inv, "World")).orElse(null);
81+
assertEquals("Hello World", test);
82+
}
83+
84+
}

0 commit comments

Comments
 (0)