@Retention(value=RUNTIME)
public @interface Slice
Using slices provides for a much more expressive way of identifying target injection points in a method, which has two advantages:
Consider the following example:
private void foo(Bar bar) {
bar.update();
List<Thing> list = bar.getThings();
for (Thing thing : list) {
thing.processStuff();
}
Thing specialThing = bar.getSpecialThing();
if (specialThing.isReady()) {
specialThing.processStuff();
bar.update();
}
bar.notifyFoo();
}
Let's assume we are interested in applying a Redirect to the
processStuff() method call within the if block. Using
ordinal we can achieve this as follows:
@At(value = "INVOKE", target = "processStuff", ordinal = 1)
This will work fine initially. However consider the case that in a future version of the target library the method is altered and an additional call to processStuff() is added:
private void foo(Bar bar) {
bar.update();
List<Thing> list = bar.getThings();
for (Thing thing : list) {
thing.processStuff();
}
// A new call to processStuff, which now has the ordinal 1
bar.getActiveThing().processStuff();
Thing specialThing = bar.getSpecialThing();
if (specialThing.isReady()) {
specialThing.processStuff();
bar.update();
}
bar.notifyFoo();
}
It's still pretty easy for a human to identify the correct point since the original if hasn't changed. However the use of ordinals means that the injection is now wrong and must be fixed by hand.
We can make the injection point more expressive and reliable by using a Slice. We know in this example that the call to processStuff() we want is the one immediately after a call to isReady(). We can slice the method at the call to isReady() and modify the injector accordingly:
@Inject(
slice = @Slice(
from = @At(value = "INVOKE", target = "isReady")
)
at = @At(value = "INVOKE", target = "processStuff", ordinal = 0)
)
The ordinal is specified as 0 because the scope of the injection point is now the region of the method defined by the slice .
Slices can be specified using a from() point, a to() point,
or both. Callback Injectors using multiple injection points
can distinguish different slices using id() and then specify the slice
to use in the At.slice() argument.
| Modifier and Type | Optional Element and Description |
|---|---|
At |
from
Injection point which specifies the start of the slice region.
|
java.lang.String |
id
The identifier for this slice, specified using the
At.slice() value
(if omitted, this slice becomes the default slice and applies to
all undecorated At queries). |
At |
to
Injection point which specifies the end of the slice region.
|
public abstract java.lang.String id
At.slice() value
(if omitted, this slice becomes the default slice and applies to
all undecorated At queries).
This value can be safely ignored for injector types which only accept
a single query (eg. Redirect and others). However since
Inject injectors can have multiple At queries, it may be
desirable to have multiple slices as well. When specifying multiple
slices, each should be designed a unique id which can then be
referenced in the corresponding At.
There are no specifications or restrictions for valid ids, however it is recommended that the id in some way describe the slice, thus allowing the At query to be read without necessarily reading the slice itself. For example, if the slice is selecting all instructions before the first call to some method init, then using an id "beforeInit makes sense. Using before, after and between prefixes as a loose standard is considered good practice.
This value defaults to an empty string, the empty string is used as a
default identifier throughout the injection subsystem, and any At
which doesn't specify a slice explicitly will use this identifer.
Specifying id or slice for injectors which only support
a single slice is ignored internally, so you may use this field to give a
descriptive name to the slice if you wish.
public abstract At from
Ats supplied here should generally specify a InjectionPoint.Selector
in order to identify which instruction should be used for queries which
return multiple results. The selector is specified by appending the
selector type to the injection point type as follows:
@At(value = "INVOKE:LAST", ... )
If from is not supplied then to() must be supplied. It
is allowed to specify both from and to as long
as the points select a region with positive size (eg the insn returned by
to must appear after that selected by from in the
method body).
public abstract At to
from(), Ats supplied here should generally specify a
InjectionPoint.Selector in order to identify which instruction should be used
for queries which return multiple results. The selector is specified by
appending the selector type to the injection point type as follows:
@At(value = "INVOKE:LAST", ... )
If to is not supplied then from() must be supplied. It
is allowed to specify both from and to as long
as the points select a region with positive size (eg the insn returned by
to must appear after that selected by from in
the method body).