Reflecting Types

REFLECTO

Overview

The ReflectoType class is a cornerstone of the Cariochi Reflecto library, offering a rich and intuitive interface for comprehensive type introspection and manipulation in Java. It simplifies accessing detailed information about any given type, making reflective programming more accessible and powerful.

Complete Type Information

ReflectoType provides a unified approach to access a wealth of information about types:

  • Actual Type and Arguments: Discern the actual type, including generic type arguments, enabling precise type manipulation and inspection.

  • Fields and Methods: Access detailed information about fields and methods, including their types, visibility, and whether they are static or instance members. Choose between declared members or all class members.

  • Modifiers: Examine a type's modifiers, such as visibility (public, private), abstraction (abstract classes, interfaces), and more.

  • Constructors: List a type's constructors, facilitating dynamic object instantiation.

  • Super Type and Interfaces: Identify a type's superclass and implemented interfaces, preserving generic type information.

  • Special Types: Special handling for arrays and enums, including component type access and enum constant retrieval.

  • Utility Methods: Includes methods like is(), as(), isAssignableFrom(), and isInstance() to query type properties and relationships intuitively.

Example Usage

Here are some practical examples demonstrating the power of ReflectoType:

Constructors

// ArrayList<String> type to discover
Type type = Types.type(ArrayList.class, String.class);

ReflectoType reflection = reflect(type);

List<ReflectoConstructor> constructors = reflection.constructors().list();
List<ReflectoConstructor> declaredConstructors = reflection.constructors().declared().list();

// find constructor by arguments types
ReflectoConstructor constructor = reflection.constructors().get(Collection.class);
    
Object instance = constructor.newInstance(Set.of(1));

Methods

// lists all methods
List<ReflectoMethod> methods = type.methods().list();

// lists declared methods
List<ReflectoMethod> declaredMethods = type.methods().declared().list();

// retrieves the ReflectoMethod for the "setUsername" method with a String parameter from the type.
ReflectoMethod method = type.methods().get("setUsername(?)", String.class);

// binds the ReflectoMethod to the target object (user) and creates a TargetMethod.
TargetMethod targetMethod = method.withTarget(user);

// invokes the "setUsername" method on the target object (user) with "test_user" as the argument.
targetMethod.invoke("test_user");

// filter methods with multiple criteria
List<ReflectoMethods> postProcessors = type.methods().declared().stream()
    .filter(method -> method.modifiers().isPublic())
    .filter(method -> method.annotations().contains(PostProcessor.class))
    .filter(method -> method.returnType().is(void.class))
    .filter(method -> method.parameters().size() == 1)
    .collect(Collectors.toList());

// retrieves the ReflectoMethod for the static "sayHello" method with a String parameter from the type.
ReflectoMethod method = type.methods().get("sayHello(?)", String.class);

// converts the ReflectoMethod into a static method representation, as it doesn't require a target instance.
TargetMethod staticMethod = method.asStatic();

// invokes the static "sayHello" method with the argument "World" and stores the result.
String result = staticMethod.invoke("World");

Fields

// list all fields
List<ReflectoField> fields = type.fields().list();

// list declared fields
List<ReflectoField> declaredFields = type.fields().declared().list();

// find field
ReflectoField field = type.fields().get("username");

// binds the ReflectoField to the target object (user) and creates a TargetField.
TargetField targetField = field.withTarget(user);

// retrieves the current value of the 'username' field from the target object (user).
String username = targetField.getValue();

// sets a new value ("test_user") for the 'username' field on the target object (user).
targetField.setValue("test_user");


// filter fields
List<ReflectoField> fields = type.fields().declared().stream()
                .filter(field -> field.modifiers().isPrivate())
                .filter(field -> field.annotations().contains(NotNull.class))
                .filter(field -> field.type().is(String.class))
                .collect(toList());
                
// Retrieves the ReflectoField for the static field "NAME" of type String from the type.
ReflectoField field = type.fields().get("NAME", String.class);

// Converts the ReflectoField into a static field representation, as it doesn't require a target instance.
TargetField staticField = field.asStatic();

// Retrieves the current value of the static field "NAME".
String name = staticField.getValue();

// Sets a new value ("New Name") for the static field "NAME".
staticField.setValue("New Name");

Working with Arrays and Enums

ReflectoType arrayType = Reflecto.reflect(String[].class);
boolean isArray = arrayType.isArray();
ReflectoType componentType= arrayType.asArray().componentType();

ReflectoType enumType = Reflecto.reflect(MyEnum.class);
boolean isEnum = enumTypee.isEnum();
List<Object> enumType = enumType.asEnum().constants();

Methods for Type Checking

// Reflects the List<String> type.
ReflectoType type = Reflecto.reflect(Types.listOf(String.class));

boolean isIterable = type.is(Iterable.class); // true
boolean isIterableOfStrings = type.is(Types.type(Iterable.class, String.class)); // true
boolean isIterableOfLongs = type.is(Types.type(Iterable.class, Long.class)); // false

Class<?> firstGenericArgument = type.as(Iterable.class).arguments().get(0).actualType(); // String.class

boolean isAssignableFromArrayList = type.isAssignableFrom(ArrayList.class); // true
boolean isAssignableFromArrayListOfStrings = type.isAssignableFrom(Types.type(ArrayList.class, String.class)); // true
boolean isAssignableFromArrayListOfLongs = type.isAssignableFrom(Types.type(ArrayList.class, Long.class)); // false

boolean isInstanceOfArrayList = type.isInstance(new ArrayList<>()); // true

Inspecting Types

This example demonstrates how to use the Reflecto library to introspect generic types in Java, using a Dto<T> class as the case study.

// Example Dto class with generics
public static class Dto<T> {
    private T value;
    private Dto<T> child;
    private Set<Dto<T>> set;
    private Map<String, Set<Dto<T>>> map;
}

Type type = Types.type(Dto.class, Integer.class);
ReflectoType reflectoType = Reflecto.reflect(type);

// Get the actual type of the first generic argument (T), which is Integer
Class<?> firstGenericArgumentType = reflectoType.arguments().get(0).actualType(); // Integer.class
Class<?> firstArgumentFromPath = reflectoType.reflect("[0]").actualType(); // Integer.class

// Get the actual type of the 'value' field, which is Integer (matches T)
Class<?> valueFieldType = reflectoType.reflect("value").actualType(); // Integer.class

// Get the actual type of the 'child.value' field, which is Integer (nested access of generic field)
Class<?> childValueType = reflectoType.reflect("child.value").actualType(); // Integer.class

// Get the actual type of the first argument (T) of the first element in the 'set' (Set<Dto<T>>), which is Integer
Class<?> setElementType = reflectoType.reflect("set[0][0]").actualType(); // Integer.class

// Get the actual type of the 'value' field (T) within the first element of 'set' (Set<Dto<T>>), which is Integer
Class<?> setElementValueType = reflectoType.reflect("set[0].value").actualType(); // Integer.class

// Get the actual type of the first generic argument (String) in the 'child.map' field (Map<String, Set<Dto<T>>>)
Class<?> mapKeyType = reflectoType.reflect("child.map[0]").actualType(); // String.class

// Get the actual type of the 'value' field (T) within the first element of the second generic argument (Set<Dto<T>>) in the 'child.map' field, which is Integer
Class<?> mapElementValueType = reflectoType.reflect("child.map[1][0].value").actualType(); // Integer.class

Conclusion

The ReflectoType class from Cariochi Reflecto offers a comprehensive toolkit for working with Java types reflectively. It simplifies obtaining detailed information about types, their relationships, and their members, enabling developers to write more dynamic, type-safe, and intuitive reflective code. Whether dealing with complex generic types, navigating type hierarchies, or performing runtime type checks and conversions, ReflectoType provides all the necessary functionalities in an accessible manner.

Last updated