org.eclipse.edc.util.reflection.ReflectionUtil Maven / Gradle / Ivy
/*
* Copyright (c) 2020 - 2022 Microsoft Corporation
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0
*
* SPDX-License-Identifier: Apache-2.0
*
* Contributors:
* Microsoft Corporation - initial API and implementation
*
*/
package org.eclipse.edc.util.reflection;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
public class ReflectionUtil {
private static final String ARRAY_INDEXER_REGEX = ".*\\[([0-9])+\\]";
private static final String OPENING_BRACKET = "[";
private static final String CLOSING_BRACKET = "]";
/**
* Utility function to get value of a field from an object. For field names currently the dot notation and array
* indexers are supported:
*
* someObject.someValue
* someObject[2].someValue //someObject must impement the List interface
*
*
* @param object The object
* @param propertyName The name of the field
* @return The field's value.
* @throws ReflectionException if the field does not exist or is not accessible
*/
public static T getFieldValue(String propertyName, Object object) {
Objects.requireNonNull(propertyName, "propertyName");
Objects.requireNonNull(object, "object");
var path = PathItem.parse(propertyName);
return getFieldValue(path, object);
}
private static T getFieldValue(List path, Object object) {
var first = path.get(0);
if (path.size() > 1) {
var nested = getFieldValue(List.of(first), object);
if (nested == null) {
return null;
}
var rest = path.stream().skip(1).toList();
return getFieldValue(rest, nested);
} else if (first.toString().matches(ARRAY_INDEXER_REGEX)) { //array indexer
var openingBracketIx = first.toString().indexOf(OPENING_BRACKET);
var closingBracketIx = first.toString().indexOf(CLOSING_BRACKET);
var propName = first.toString().substring(0, openingBracketIx);
var arrayIndex = Integer.parseInt(first.toString().substring(openingBracketIx + 1, closingBracketIx));
var iterableObject = (List) getFieldValue("'%s'".formatted(propName), object);
return (T) iterableObject.get(arrayIndex);
} else {
if (object instanceof Map, ?> map) {
return (T) map.get(first.toString());
} else if (object instanceof List> list) {
return (T) list.stream().filter(Objects::nonNull).map(it -> getRecursiveValue(first.toString(), it)).toList();
} else {
return getRecursiveValue(first.toString(), object);
}
}
}
/**
* Gets a field with a given name from all declared fields of a class including supertypes. Will include protected
* and private fields.
*
* @param clazz The class of the object
* @param fieldName The fieldname
* @return A field with the given name, null if the field does not exist
*/
public static Field getFieldRecursive(Class> clazz, String fieldName) {
return getAllFieldsRecursive(clazz).stream().filter(f -> f.getName().equals(fieldName)).findFirst().orElse(null);
}
/**
* Recursively gets all fields declared in the class and all its superclasses
*
* @param clazz The class of the object
* @return A list of {@link Field}s
*/
public static List getAllFieldsRecursive(Class> clazz) {
if (clazz == null) {
return Collections.emptyList();
}
List result = new ArrayList<>(getAllFieldsRecursive(clazz.getSuperclass()));
var filteredFields = Arrays.stream(clazz.getDeclaredFields()).toList();
result.addAll(filteredFields);
return result;
}
private static T getRecursiveValue(String propertyName, Object object) {
var field = getFieldRecursive(object.getClass(), propertyName);
if (field == null) {
throw new ReflectionException(propertyName);
}
field.setAccessible(true);
try {
return (T) field.get(object);
} catch (IllegalAccessException e) {
throw new ReflectionException(e);
}
}
/**
* Get the first type argument for the given target from the given clazz.
* It goes through the hierarchy starting from class and looking for target
* And return the first type argument of the target
*
* @param clazz The class of the object
* @return The type argument {@link Class} or null
*/
public static Class> getSingleSuperTypeGenericArgument(Class> clazz, Class> target) {
var supertype = clazz.getGenericSuperclass();
var superclass = clazz.getSuperclass();
while (superclass != null && superclass != Object.class) {
if (supertype instanceof ParameterizedType pt) {
if (pt.getRawType() == target) {
return getSingleTypeArgument(supertype);
}
}
supertype = superclass.getGenericSuperclass();
superclass = superclass.getSuperclass();
}
return null;
}
/**
* If the Type is a ParameterizedType return the actual type of the first type parameter
*
* @param genericType The genericType
* @return The class of the type argument
*/
private static Class> getSingleTypeArgument(Type genericType) {
if (genericType instanceof ParameterizedType pt) {
var actualTypeArguments = pt.getActualTypeArguments();
if (actualTypeArguments.length == 1) {
var actualTypeArgument = actualTypeArguments[0];
if (actualTypeArgument instanceof Class> clazz) {
return clazz;
}
if (actualTypeArgument instanceof ParameterizedType actualParametrizedType) {
var rawType = actualParametrizedType.getRawType();
if (rawType instanceof Class) {
return (Class>) rawType;
}
}
}
}
return null;
}
}