/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.asm.mixin; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * This annotation allows fine-tuning of the overwrite policy for a * soft-implemented interface member method. * *

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 {@link Intrinsic @Intrinsic}:

* *
@Shadow public int getSomething();
 *
 *@Intrinsic // don't merge the method if the target already exists
 *public 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 {@link Intrinsic} behaviour, and set the {@link #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();
 *}
* */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.CLASS) public @interface Intrinsic { /** * If 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 * * @return true if this intrinsic method should displace matching targets */ public boolean displace() default false; }