Core.Reflection - Year4000/Utilities GitHub Wiki
Our reflection system uses the proxy pattern plus the enhancements in Java 8 to create a fast reflection system.
Take a look at the unit tests to see how the system works.
Java Signatures
Below are signature types for Java types.
For a method you use ([Ljava/lang/String;)V
represents a method with an argument of String[] and returns void
.
-
V
void -
J
long -
I
int -
B
byte -
Z
boolean -
D
double -
S
short -
F
float -
C
char -
[
array -
L;
object -
()
method
The proxy interface lets us access an object's instance at runtime. This is allows us to create code for a system that we know is available at runtime but not possible to have at compile time.
Every proxy object has special handling for $this()
.
It will grab the underlying instance that the proxy is proxing.
We use the return type Object
for instances that are not known at compile time.
If the instance is known at compile time you may substitute the return type.
Standard Practices
- Proxy objects should include a static method that will create the proxy instance.
- Proxy objects should include
Object $this()
- Setters and Getter should avoid
set
andget
prefix as visibility views are ignored - Use default methods to provide additional functionality
This snippet shows how to set up a very basic Proxy interface and standard practices
@Proxied("path.to.Object")
public interface ProxyObject {
// standard practice - allow creating proxies of the instance
static ProxyObject of(Object instance) {
return Gateways.proxy(ProxyObject.class, instance);
}
// standard practice - allow retrieving the proxying instance
Object $this();
}
The getter annotation tells the proxy to retrieve a field. It will ignore the visibility modifier of the field. When the method matches the name of the field you do not need to provide a value. Read more about the @Getter.
@Proxied("path.to.Object")
public interface ProxyObject {
// Will access the field of `name`
@Getter("name")
String firstName();
// Will access the index of the fields by the signature
@Getter(signature = "Ljava/lang/String;", index = 0)
String lastName();
}
The setter annotation tells the proxy to mutate a field. It will ignore the visibility modifier of the field. When the method matches the name of the field you do not need to provide a value. Read more about the @Setter.
@Proxied("path.to.Object")
public interface ProxyObject {
// Will mutate the field of `name`
@Setter("name")
void firstName(String name);
// Will mutate the index of the fields by the signature
@Setter(signature = "Ljava/lang/String;", index = 0)
void lastName(String name);
}
The invoke annotation tells the proxy to invoke a method. It will ignore the visibility modifier of the method. When the method matches the name of the method you do not need to provide a value. Read more about the @Invoke.
@Proxied("path.to.Object")
public interface ProxyObject {
// Will invoke the method of `name`
@Invoke("display")
void displayFirstName();
// Will invoke the method by the signature
@Invoke(signature = "()V", index = 0)
void displayLastName();
}
The bridge annotation allows to bridge the return types of the method.
Even though the bridge accepts any Class<?>
for the value the class must be an interface with @Proxied
annotation.
This allows you to chain proxy interfaces together without dealing with only Object
.
Read more about the @Bridge.
@Proxied("path.to.other.Object")
public interface ProxyOtherObject {
Object $this();
}
@Proxied("path.to.Object")
public interface ProxyObject {
// Will bridge the return type to the proxy
@Bridge(ProxyOtherObject.class)
@Invoke(signature = "()Lpath/to/other/Object;", index = 0)
ProxyOtherObject otherObject();
}
The implements annotation is a power tool to allow the proxied instances to implement other runtime interfaces.
This allows for the proxies to be DuckTyped to other types.
While the Implements accepts the array of @Proxied
interfaces in this context it used to tell the Proxied class to implement the other interfaces.
Read more about the @Implements.
@Proxied("path.to.object")
@Implements({
@Proxied("path.to.interface"),
@Proxied("path.to.other.interface")
})
public interface ProxyObject {
static ProxyObject of(Object instance) {
return Gateways.proxy(ProxyObject.class, instance);
}
}
This is just for reading and has no effect.
It just states that the @Setter
, @Getter
, @Invoke
are on a static method or field.
There are times where you want to simply use standard reflection without the need of the proxy system.
The Reflections
utility class is the answer to this question.
The following system uses the Value system, to encapsulate the exceptions from the method.
When there was an error the value will be empty.
Note - When the value is empty it will either mean there was an error or the return value is empty.
Get a Method
Class<?> clazz = ...;
Value<Method> method = Reflections.method(clazz, "toString");
Invoke a Method
Method method = ...;
Object instance = ...;
Value<Object> returnType = Reflections.invoke(instance, method);
Get a Field
Class<?> clazz = ...;
Value<Field> field = Reflections.field(class, "fieldName");
Set a Field's Value
Object instance = ...;
Field field = ...;
Reflections.setter(instance, field, "Hello World");
Get a Field's Value
Object instance = ...;
Field field = ...;
Value<T> value = Reflections.getter(instance, field);
Create an Instance
Class<?> clazz = ...;
Value<T> value = Reflections.instance(clazz);
Get a Class
Value<Class<?>> clazz = Reflections.clazz("path.to.Class");