# @Decorate Usage

We provide `@Decorate` as a new mixin injector. It provides:

* ultimate compatibility to `@Redirect` injector, and
* be able to modify/allocate multiple variables in target method stack, and
* be able to insert control flow instructions

# Basic Usage

## Inject mode

`@Decorate` can insert a piece of code into target method, just like `@Inject`:

```java
@Decorate(method = "test", inject = true, at = @At("HEAD"))
public void inject() throws Throwable {
    System.out.println("test() is callded");
}
```

In inject mode, you **MUST NOT** have `CallbackInfo` in method signature.

## Redirect mode

`@Decorate` can work like `@Redirect`, redirects method call, field access or object creation. In this case, `@Decorate` has the same method signature as `@Redirect`:

```java
// redirect method call
@Decorate(method = "test", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
public void redirect1(PrintStream s, String s2) throws Throwable {
    if (s2.equals("foo")) {
        return; // Do not print "foo"
    }
    DecorationOps.callsite().invoke(s, s2);
}

// redirect field access
@Decorate(method = "test", at = @At(value = "FIELD", target = "Ljava/lang/System;out:Ljava/io/PrintStream;"))
public PrintStream redirect2() throws Throwable {
    var s = (PrintStream) DecorationOps.callsite().invoke();
    return new PrintStream(s);
}

// redirect object creation
@Decorate(method = "test", at = @At(value = "NEW", target = "net/minecraft/core/BlockPos"))
public BlockPos redirect3(int x, int y, int z) throws Throwable {
    return (BlockPos) DecorationOps.callsite().invoke(x+1, y+2, z+3);
}
```

Additionally, `@Decorate` support redirect method return. In this case, it accept the target method return type as argument and MUST have a void return type:

```java
// redirect method return
@Decorate(method = "getBlockState", at = @At("RETURN"))
public void redirect4(BlockState state) throws Throwable {
    if (state == null) {
        state = Blocks.AIR.defaultBlockState();
    }
    DecorationOps.callsite().invoke(state);
}
```

In redirect mode, you **MUST** have **EXACT ONE** `DecorationOps.callsite().invoke` call. It represents the injection point of this mixin, and the parameters passed to `invoke` is given to the original injection point:

```java
// target method
private void test() {
    System.out.println("foo");
    LOGGER.info("bar");
}

@Decorate(method = "test", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
public void redirect(PrintStream s, String s2) throws Throwable {
    if (s2.equals("foo")) {
        return; // Do not print "foo"
    } else {
        DecorationOps.callsite().invoke(s, s2 + "bar");
    }
}

// after decorate
private void test() {
    if (s2.equals("foo")) {
    } else {
        System.out.println("foo" + "bar");
    }
    LOGGER.info("bar");
}
```

Mixins from other mods can still redirect the `println` method call, together with our `@Decorate` mixin.


# Insert RETURN in target method

`@Decorate` can cancel the target method like `@Inject(cancellable = true)`. All you need to do is to add a `DecorationOps.cancel().invoke` call:

```java
// target method
private void test() {
    System.out.println("foo");
    LOGGER.info("bar");
}

@Decorate(method = "test", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
public void redirect(PrintStream s, String s2) throws Throwable {
    if (!s2.equals("foo")) {
        DecorationOps.callsite().invoke(s, s2 + "bar");
    } else {
        DecorationOps.cancel().invoke(); 
        return; // this return is essential, even if your IDE report it redundant
    }
}

// after decorate
private void test() {
    if (!s2.equals("foo")) {
        System.out.println("foo" + "bar");
    } else {
        return;
    }
    LOGGER.info("bar");
}
```

All `cancel()` **MUST** be followed by a return statement, even if it’s at the end of a code block, or your IDE report it redundant.

If target method has non-void return type, a parameter with exact type MUST be passed to `cancel().invoke`:

```java
@Decorate(method = "getBlockState", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
public void redirect2(PrintStream s, String s2) throws Throwable {
    if (!s2.equals("foo")) {
        DecorationOps.callsite().invoke(s, s2 + "bar");
    } else {
        // type must be exact with target method return type
        DecorationOps.cancel().invoke((BlockState) null);
        return;
    }
}
```


# Capture and modify variables

`@Decorate` can capture variables from target method by appending arguments to injector method, just like most standard mixin injectors.

You can assign these captured variable with new value and they will take effect in target method.

```java
// target method
private BlockState test(Level level, BlockPos pos) {
    BlockState state = level.getBlockState(pos);
    if (state.is(Blocks.AIR)) {
        LOGGER.info("bar");
    }
    return state;
}

@Decorate(method = "test", inject = true, at = @At("HEAD"))
public void modifyVariable(Level level, BlockPos pos) throws Throwable {
    pos = pos.above();
}

// after decorate
private BlockState test(Level level, BlockPos pos) {
    pos = pos.above();
    BlockState state = level.getBlockState(pos);
    if (state.is(Blocks.AIR)) {
        LOGGER.info("bar");
    }
    return state;
}
```

You can use `@Local(ordinal = <ordinal>)` to capture variable by type and their ordinal, negative ordinal refers to last N variable by type:

```java
@Decorate(method = "test", inject = true, at = @At(value = "INVOKE", target = "is(**)*"))
public void modifyVariable(@Local(ordinal = -1) BlockState state) throws Throwable {
    // state refers to the most close BlockState variable before the state.is call
}
```

You can use `@Local(allocate = <name>)` to allocate new variable in target method. These variable can be shared across `@Decorate` injectors:

```java
// target method
private BlockState test(Level level, BlockPos pos) {
    BlockState state = level.getBlockState(pos);
    if (state.is(Blocks.AIR)) {
        LOGGER.info("foo");
    }
    if (state.is(Blocks.STONE)) {
        LOGGER.info("bar");
    }
    return state;
}

@Decorate(method = "test", inject = true, at = @At(value = "INVOKE", target = "is(**)*"))
public void modifyVariable(@Local(allocate = "count") int count) throws Throwable {
    count++;
    LOGGER.info("is() called {} times", count)
}

// after decorate
private BlockState test(Level level, BlockPos pos) {
    BlockState state = level.getBlockState(pos);
    int count = 0;
    count++;
    LOGGER.info("is() called {} times", count);
    if (state.is(Blocks.AIR)) {
        LOGGER.info("foo");
    }
    count++;
    LOGGER.info("is() called {} times", count);
    if (state.is(Blocks.STONE)) {
        LOGGER.info("bar");
    }
    return state;
}
```


# DecorationOps.blackhole() Usage

In the cases where parts of the mixin code reports as redundant, `DecorationOps.blackhole()` can eliminate these warnings:

```java
@Decorate(method = "test", inject = true, at = @At("HEAD"))
public void blackhole1(Level level, BlockPos pos) throws Throwable {
    pos = pos.above(); // warning: Variable 'pos' is never used
    DecorationOps.blackhole().invoke(pos);
}

@Decorate(method = "test", at = @At(value = "INVOKE", target = "Ljava/io/PrintStream;println(Ljava/lang/String;)V"))
public void redirect(PrintStream s, String s2) throws Throwable {
    if (!s2.equals("foo")) {
        DecorationOps.callsite().invoke(s, s2 + "bar");
    } else {
        DecorationOps.cancel().invoke(); 
        return; // warning: 'return' is unnecessary as the last statement in a 'void' method 
    }
    DecorationOps.blackhole().invoke();
}
```

All expressions passed to the `invoke` will be erased after mixin applied.

---

**Documents**

- [Versions and its supporting status](https://wiki.izzel.io/s/arclight-docs/doc/versions-and-its-supporting-status-NCKYxXI3mt)
- [Tutorials](https://wiki.izzel.io/s/arclight-docs/doc/tutorials-LsIkwojL9P)
- [Administration](https://wiki.izzel.io/s/arclight-docs/doc/administration-puh66tln4f)
- [Development](https://wiki.izzel.io/s/arclight-docs/doc/development-kCaAexaIdQ)
- [FAQ](https://wiki.izzel.io/s/arclight-docs/doc/faq-ApcZ25PXVT)
- [Donate](https://wiki.izzel.io/s/arclight-docs/doc/donate-Bj9cf2F5YH)