shadow

An annotation based API for Java reflection

License

License

MIT
GroupId

GroupId

me.lucko
ArtifactId

ArtifactId

shadow
Last Version

Last Version

1.3
Release Date

Release Date

Type

Type

jar
Description

Description

shadow
An annotation based API for Java reflection
Project URL

Project URL

https://github.com/lucko/shadow
Source Code Management

Source Code Management

https://github.com/lucko/shadow

Download shadow

How to add to project

<!-- https://jarcasting.com/artifacts/me.lucko/shadow/ -->
<dependency>
    <groupId>me.lucko</groupId>
    <artifactId>shadow</artifactId>
    <version>1.3</version>
</dependency>
// https://jarcasting.com/artifacts/me.lucko/shadow/
implementation 'me.lucko:shadow:1.3'
// https://jarcasting.com/artifacts/me.lucko/shadow/
implementation ("me.lucko:shadow:1.3")
'me.lucko:shadow:jar:1.3'
<dependency org="me.lucko" name="shadow" rev="1.3">
  <artifact name="shadow" type="jar" />
</dependency>
@Grapes(
@Grab(group='me.lucko', module='shadow', version='1.3')
)
libraryDependencies += "me.lucko" % "shadow" % "1.3"
[me.lucko/shadow "1.3"]

Dependencies

test (3)

Group / Artifact Type Version
org.checkerframework : checker-qual jar 2.4.0
org.junit.jupiter : junit-jupiter-api jar 5.1.0
org.junit.jupiter : junit-jupiter-engine jar 5.1.0

Project Modules

There are no modules declared in this project.

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!

Versions

Version
1.3
1.2
1.1
1.0