Annotation Interface Intrinsic
By default, all members within a mixin will overwrite (completely replace) matching methods in the target class without prejudice. However this can be a problem when soft-implementing an interface to deal with a method conflict because it may be desirable to only override the method if it does not already exist in the target, for example when running in a development environment.
This annotation introduces two new behaviours for dealing with target methods which implement a soft interface intrinsically.
Consider the following code:
@Shadow public int getSomething();
public int soft$getSomething() {
return this.getSomething();
}
This type of accessor is common when an interface conflict occurs, but has the problem that the injected accessor will be effectively self-referential in development, because the prefix will be removed and the accessor will thus cause a stack overflow.
The only way to address this issue is to replace the shadow with a copy of the original accessor, like so:
@Shadow private int something;
public int soft$getSomething() {
return this.something;
}
But this has the drawback of forcing the mixin to re-implement the target method, and thus forces ongoing maintenance of the mixin to include updating the contents of the accessor.
Intrinsic allows this problem to be circumvented in one of
two ways. The first way essentially declares "don't merge this method if it
already exists in the target". To use this behaviour, we simply tag our
accessor with @Intrinsic:
@Shadow public int getSomething(); @Intrinsic// don't merge the method if the target already existspublic int soft$getSomething() { return this.getSomething(); }
This is ideal if the accessor is simply providing a proxy through to an underlying obfuscated method, as in the example above. However, if the new method contains additional code, such as in this example:
@Shadow public int getSomething();
public int soft$getSomething() {
return this.someCondition ? this.someOtherValue : this.getSomething();
}
Then we still have the original problem that the method becomes re-entrant
without specifying the additional Overwrite. We can solve this with the
second Intrinsic behaviour, and set the displace() argument to
true. Setting displace instructs the mixin
transformer to proxy the method call if the target already
exists, but merge the method normally if it does not. This allows our new
accessor to call the original method in all circumstances.
When displace is enabled, if the target method exists then
instead of being overwritten it is instead renamed with a temporary
name and all references to the original method within the Intrinsic
method are updated to call the renamed method. This means that all
external code will still reference the Intrinsic accessor (just as it would
have done with a regular overwrite) but code within the Intrinsic
accessor will call the overwritten method.
@Shadow public int getSomething();
@Intrinsic(displace = true) // if the target already exists, displace it
public int soft$getSomething() {
return this.someCondition ? this.someOtherValue : this.getSomething();
}-
Optional Element Summary
Optional ElementsModifier and TypeOptional ElementDescriptionbooleanIf set to true, this intrinsic method will replace any existing method in the target class by renaming it and updating internal references inside the method to reference the displaced method.
-
Element Details
-
displace
boolean displaceIf set to true, this intrinsic method will replace any existing method in the target class by renaming it and updating internal references inside the method to reference the displaced method. If false or omitted, instructs this intrinsic method to not overwrite the target method if it exists, contrary to the normal behaviour for mixins- Returns:
- true if this intrinsic method should displace matching targets
- Default:
false
-