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
2 changes: 2 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Caleb Cushing <caleb.cushing@gmail.com> <xenoterracide@gmail.com>
Caleb Cushing <caleb.cushing@gmail.com> Caleb C <caleb.cushing@gmail.com>
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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<Class<?>, Stream<Field>> 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 <T> 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 <T> @Nullable T readField(Object target, String fieldName, Class<T> 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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/**
* Reflection utilities.
*/
package com.xenoterracide.tools.java.reflect;
7 changes: 5 additions & 2 deletions module/tools/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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;

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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";
}
}
2 changes: 1 addition & 1 deletion module/tools/src/test/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}