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 members, with support for including or excluding enclosing 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

ReflectoType type = reflect(Types.type(ArrayList.class, String.class));

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

// find constructor by parameter types
ReflectoConstructor constructor = type.constructors().find(Collection.class)
    .orElseThrow();
    
Object instance = constructor.newInstance(Set.of(1));

Methods

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

// find and invoke method
ReflectoMethod method = type.methods().find("setUsername(?)", String.class).orElseThrow();
TargetMethod targetMethod = method.withTarget(user);
targetMethod.invoke("test_user");

// filter methods
List<ReflectoMethods> postProcessors = type.declared().methods().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());

// find and invoke a static method
ReflectoMethod method = type.methods().find("sayHello(?)", String.class).orElseThrow();
TargetMethod staticMethod = method.asStatic();
String result = staticMethod.invoke("World");

Fields

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

// find field
ReflectoField field = type.fields().find("username").orElseThrow();
TargetField targetField = field.withTarget(user);
String username = targetField.getValue();
targetField.setValue("test_user");

// filter fields
List<ReflectoField> fields = type.declared().fields().stream()
                .filter(field -> field.modifiers().isPrivate())
                .filter(field -> field.annotations().contains(NotNull.class))
                .filter(field -> field.type().is(String.class))
                .collect(toList());
                
// find static field
ReflectoField field = type.fields().find("NAME", String.class).orElseThrow();
TargetField staticField = field.asStatic();
String name = staticField.getValue();
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

ReflectoType type = Reflecto.reflect(Types.listOf(String.class));

assertThat(type.is(Iterable.class)).isTrue();
assertThat(type.is(Types.type(Iterable.class, String.class))).isTrue();
assertThat(type.is(Types.type(Iterable.class, Long.class))).isFalse();

assertThat(type.as(Iterable.class).arguments().get(0).actualType())
                .isEqualTo(String.class);
                
assertThat(type.isAssignableFrom(ArrayList.class)).isTrue();
assertThat(type.isAssignableFrom(Types.type(ArrayList.class, String.class))).isTrue();
assertThat(type.isAssignableFrom(Types.type(ArrayList.class, Long.class))).isFalse();

assertThat(type.isInstance(new ArrayList<>())).isTrue();

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);

// Type of the first argument
assertThat(reflectoType.arguments().get(0).actualType()).isEqualTo(Integer.class);
assertThat(reflectoType.reflect("[0]").actualType()).isEqualTo(Integer.class);

// Type of the 'value' field
assertThat(reflectoType.reflect("value").actualType()).isEqualTo(Integer.class);

// Type of the 'child.value' nested field
assertThat(reflectoType.reflect("child.value").actualType()).isEqualTo(Integer.class);

// Type of the first argument (T) of the first argument (Dto<T>) of the 'set' field type (Set<Dto<T>>)
assertThat(reflectoType.reflect("set[0][0]").actualType()).isEqualTo(Integer.class);

// Type of the 'value' field (T) of the first argument (Dto<T>) of the 'set' field type (Set<Dto<T>>)
assertThat(reflectoType.reflect("set[0].value").actualType()).isEqualTo(Integer.class);

// Type of the first argument (String) of the nested 'child.map' field type (Map<String, Set<Dto<T>>>)
assertThat(reflectoType.reflect("child.map[0]").actualType()).isEqualTo(String.class);

// Type of the 'value' field (T) of the first argument (Dto<T>) of the second argument (Set<Dto<T>>) of the 'child.map' nested field type (Map<String, Set<Dto<T>>>)
assertThat(reflectoType.reflect("child.map[1][0].value").actualType()).isEqualTo(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