Skip to content
forked from lucko/shadow

An annotation based API for Java reflection.

License

Notifications You must be signed in to change notification settings

Nyaadanbou/shadow

 
 

Repository files navigation

shadow

Javadocs Maven Central

An annotation based API for Java reflection.

The system was inspired by the Shadow feature in the SpongePowered Mixin library. The code in this repository is adapted from the package previously built into lucko/helper.

Example

Given the following example base class:

public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

Let's assume we want to increment the Persons age on their birthday. The class is immutable, and doesn't allow us to modify the age once constructed - so, we need to use reflection to change the value of the field.

Normal Java Reflection

This can be done using plain old reflection like this.

public static void incrementAge(Person person) {
    Field ageField;
    try {
        ageField = Person.class.getDeclaredField("age");
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    }

    ageField.setAccessible(true);

    try {
        ageField.setInt(person, ageField.getInt(person) + 1);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

Shadow

However, with shadow, our approach is slightly different.

We start by defining a "shadow interface" for the Person class.

@ClassTarget(Person.class)
public interface PersonShadow extends Shadow {

    int getAge();

    @Field
    void setAge(int age);

    default void incrementAge() {
        setAge(getAge() + 1);
    }
}

The getAge method simply mirrors the existing method defined on the Person class - nothing special going on there. However, the setAge method is bound to the age field.

Once the shadow interface has been defined, we can use the ShadowFactory to obtain a "shadow" instance for our person.

The incrementAge method can then be implemented as follows.

public static void incrementAge(Person person) {
    PersonShadow personShadow = ShadowFactory.global().shadow(PersonShadow.class, person);
    personShadow.incrementAge();
}

The shadow approach has a number of key advantages over the plain reflection method.

  • The structure of the Person class is outlined in one central location - the shadow interface.
    • If the layout of Person changes - we only have to update one obvious place.
    • The places in our program using the shadow (in this case the incrementAge method) aren't cluttered with the details of the person class.
  • We don't have to deal with the checked exceptions associated with obtaining the field or modifying the value. These are simply wrapped up into a RuntimeException thrown when the shadow is obtained.
  • The shadow implementation caches the underlying Field, Method etc instances behind the scenes, we don't have to worry!

About

An annotation based API for Java reflection.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 99.4%
  • Kotlin 0.6%