diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..00d55db --- /dev/null +++ b/.mailmap @@ -0,0 +1,2 @@ +Caleb Cushing +Caleb Cushing Caleb C diff --git a/module/tools/src/main/java/com/xenoterracide/tools/java/exception/UncheckedReflectionException.java b/module/tools/src/main/java/com/xenoterracide/tools/java/exception/UncheckedReflectionException.java new file mode 100644 index 0000000..662ea85 --- /dev/null +++ b/module/tools/src/main/java/com/xenoterracide/tools/java/exception/UncheckedReflectionException.java @@ -0,0 +1,16 @@ +// © Copyright 2024 Caleb Cushing +// SPDX-License-Identifier: Apache-2.0 + +package com.xenoterracide.tools.java.exception; + +/** + * An unchecked exception that wraps a checked exception thrown during reflection. + */ +public class UncheckedReflectionException extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public UncheckedReflectionException(Throwable cause) { + super(cause); + } +} diff --git a/module/tools/src/main/java/com/xenoterracide/tools/java/reflect/ReflectionTools.java b/module/tools/src/main/java/com/xenoterracide/tools/java/reflect/ReflectionTools.java new file mode 100644 index 0000000..501acf7 --- /dev/null +++ b/module/tools/src/main/java/com/xenoterracide/tools/java/reflect/ReflectionTools.java @@ -0,0 +1,58 @@ +// © Copyright 2024 Caleb Cushing +// SPDX-License-Identifier: Apache-2.0 + +package com.xenoterracide.tools.java.reflect; + +import com.xenoterracide.tools.java.exception.UncheckedReflectionException; +import java.lang.reflect.Field; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; +import org.jspecify.annotations.Nullable; + +public final class ReflectionTools { + + private static final Map, Stream> FIELDS_CACHE = new ConcurrentHashMap<>(); + + private ReflectionTools() {} + + /** + * Reads the value of a field from an object. + * + * @param target the object to read the field from + * @param fieldName the name of the field to read + * @param returnType the type of the field + * @param the type of the field + * @return the value of the field + * @throws UncheckedReflectionException if the field cannot be read + * @throws ClassCastException if the field value cannot be cast to the return type + */ + public static @Nullable T readField(Object target, String fieldName, Class returnType) + throws UncheckedReflectionException, ClassCastException { + var targetClass = target.getClass(); + + var targetFields = FIELDS_CACHE.computeIfAbsent(targetClass, c -> { + try { + return Stream.of(targetClass.getFields()); + } catch (SecurityException e) { + throw new UncheckedReflectionException(e); + } + }); + + return targetFields + .filter(f -> Objects.equals(f.getName(), fieldName)) + .peek(Field::trySetAccessible) + .filter(f -> f.canAccess(target)) + .findAny() + .map(f -> { + try { + return f.get(target); + } catch (IllegalAccessException e) { + throw new UncheckedReflectionException(e); + } + }) + .map(returnType::cast) + .orElse(null); + } +} diff --git a/module/tools/src/main/java/com/xenoterracide/tools/java/reflect/package-info.java b/module/tools/src/main/java/com/xenoterracide/tools/java/reflect/package-info.java new file mode 100644 index 0000000..d0732e5 --- /dev/null +++ b/module/tools/src/main/java/com/xenoterracide/tools/java/reflect/package-info.java @@ -0,0 +1,4 @@ +/** + * Reflection utilities. + */ +package com.xenoterracide.tools.java.reflect; diff --git a/module/tools/src/main/java/module-info.java b/module/tools/src/main/java/module-info.java index 6a3da38..42e0107 100644 --- a/module/tools/src/main/java/module-info.java +++ b/module/tools/src/main/java/module-info.java @@ -8,8 +8,11 @@ */ @NullMarked module com.xenoterracide.tools.java { requires static org.jspecify; - requires java.base; - exports com.xenoterracide.tools.java.function; exports com.xenoterracide.tools.java.annotation; exports com.xenoterracide.tools.java.collection; + exports com.xenoterracide.tools.java.exception; + exports com.xenoterracide.tools.java.function; + exports com.xenoterracide.tools.java.reflect; + + opens com.xenoterracide.tools.java.annotation; } diff --git a/module/tools/src/test/java/com/xenoterracide/blackbox/ArrayToolsTest.java b/module/tools/src/test/java/com/xenoterracide/tools/java/test/ArrayToolsTest.java similarity index 93% rename from module/tools/src/test/java/com/xenoterracide/blackbox/ArrayToolsTest.java rename to module/tools/src/test/java/com/xenoterracide/tools/java/test/ArrayToolsTest.java index 9c307b4..b9626c8 100644 --- a/module/tools/src/test/java/com/xenoterracide/blackbox/ArrayToolsTest.java +++ b/module/tools/src/test/java/com/xenoterracide/tools/java/test/ArrayToolsTest.java @@ -1,7 +1,7 @@ // © Copyright 2024 Caleb Cushing // SPDX-License-Identifier: Apache-2.0 -package com.xenoterracide.blackbox; +package com.xenoterracide.tools.java.test; import static org.assertj.core.api.Assertions.assertThat; diff --git a/module/tools/src/test/java/com/xenoterracide/blackbox/CollectionToolsTest.java b/module/tools/src/test/java/com/xenoterracide/tools/java/test/CollectionToolsTest.java similarity index 96% rename from module/tools/src/test/java/com/xenoterracide/blackbox/CollectionToolsTest.java rename to module/tools/src/test/java/com/xenoterracide/tools/java/test/CollectionToolsTest.java index 6a7a7c0..75b3511 100644 --- a/module/tools/src/test/java/com/xenoterracide/blackbox/CollectionToolsTest.java +++ b/module/tools/src/test/java/com/xenoterracide/tools/java/test/CollectionToolsTest.java @@ -1,7 +1,7 @@ // © Copyright 2024 Caleb Cushing // SPDX-License-Identifier: Apache-2.0 -package com.xenoterracide.blackbox; +package com.xenoterracide.tools.java.test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatNoException; diff --git a/module/tools/src/test/java/com/xenoterracide/blackbox/ExceptionToolsTest.java b/module/tools/src/test/java/com/xenoterracide/tools/java/test/ExceptionToolsTest.java similarity index 96% rename from module/tools/src/test/java/com/xenoterracide/blackbox/ExceptionToolsTest.java rename to module/tools/src/test/java/com/xenoterracide/tools/java/test/ExceptionToolsTest.java index f33a412..ab6dc92 100644 --- a/module/tools/src/test/java/com/xenoterracide/blackbox/ExceptionToolsTest.java +++ b/module/tools/src/test/java/com/xenoterracide/tools/java/test/ExceptionToolsTest.java @@ -1,7 +1,7 @@ // © Copyright 2024 Caleb Cushing // SPDX-License-Identifier: Apache-2.0 -package com.xenoterracide.blackbox; +package com.xenoterracide.tools.java.test; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; diff --git a/module/tools/src/test/java/com/xenoterracide/blackbox/PredicateToolsTest.java b/module/tools/src/test/java/com/xenoterracide/tools/java/test/PredicateToolsTest.java similarity index 96% rename from module/tools/src/test/java/com/xenoterracide/blackbox/PredicateToolsTest.java rename to module/tools/src/test/java/com/xenoterracide/tools/java/test/PredicateToolsTest.java index ab7f052..79d1eab 100644 --- a/module/tools/src/test/java/com/xenoterracide/blackbox/PredicateToolsTest.java +++ b/module/tools/src/test/java/com/xenoterracide/tools/java/test/PredicateToolsTest.java @@ -1,7 +1,7 @@ // © Copyright 2024 Caleb Cushing // SPDX-License-Identifier: Apache-2.0 -package com.xenoterracide.blackbox; +package com.xenoterracide.tools.java.test; import static com.xenoterracide.tools.java.function.PredicateTools.prop; import static java.util.function.Predicate.isEqual; diff --git a/module/tools/src/test/java/com/xenoterracide/tools/java/test/ReflectionToolsTest.java b/module/tools/src/test/java/com/xenoterracide/tools/java/test/ReflectionToolsTest.java new file mode 100644 index 0000000..2e381f4 --- /dev/null +++ b/module/tools/src/test/java/com/xenoterracide/tools/java/test/ReflectionToolsTest.java @@ -0,0 +1,22 @@ +// © Copyright 2024 Caleb Cushing +// SPDX-License-Identifier: Apache-2.0 + +package com.xenoterracide.tools.java.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.xenoterracide.tools.java.reflect.ReflectionTools; +import org.junit.jupiter.api.Test; + +class ReflectionToolsTest { + + @Test + void readField() { + assertThat(ReflectionTools.readField(new TestObject(), "field", String.class)).isEqualTo("value"); + } + + class TestObject { + + String field = "value"; + } +} diff --git a/module/tools/src/test/java/module-info.java b/module/tools/src/test/java/module-info.java index 2d28c5b..750a162 100644 --- a/module/tools/src/test/java/module-info.java +++ b/module/tools/src/test/java/module-info.java @@ -3,5 +3,5 @@ requires org.junit.jupiter.api; requires org.assertj.core; requires io.vavr; - opens com.xenoterracide.blackbox to org.junit.platform.commons; + opens com.xenoterracide.tools.java.test to org.junit.platform.commons; }