graftt - api

annotation-driven bytecode surgery

License

License

Categories

Categories

Net
GroupId

GroupId

net.onedaybeard.graftt
ArtifactId

ArtifactId

api
Last Version

Last Version

0.3.0
Release Date

Release Date

Type

Type

jar
Description

Description

graftt - api
annotation-driven bytecode surgery

Download api

How to add to project

<!-- https://jarcasting.com/artifacts/net.onedaybeard.graftt/api/ -->
<dependency>
    <groupId>net.onedaybeard.graftt</groupId>
    <artifactId>api</artifactId>
    <version>0.3.0</version>
</dependency>
// https://jarcasting.com/artifacts/net.onedaybeard.graftt/api/
implementation 'net.onedaybeard.graftt:api:0.3.0'
// https://jarcasting.com/artifacts/net.onedaybeard.graftt/api/
implementation ("net.onedaybeard.graftt:api:0.3.0")
'net.onedaybeard.graftt:api:jar:0.3.0'
<dependency org="net.onedaybeard.graftt" name="api" rev="0.3.0">
  <artifact name="api" type="jar" />
</dependency>
@Grapes(
@Grab(group='net.onedaybeard.graftt', module='api', version='0.3.0')
)
libraryDependencies += "net.onedaybeard.graftt" % "api" % "0.3.0"
[net.onedaybeard.graftt/api "0.3.0"]

Dependencies

There are no dependencies for this project. It is a standalone project that does not depend on any other jars.

Project Modules

There are no modules declared in this project.

graftt - annotation-driven bytecode surgery

Rewrite existing classes by grafting bytecode from Transplant classes. Transplants are plain java classes and function like templates or patches; annotations define interactions with the recipient class. The entire API consists of 4 annotations.

Transplants have complete access to the recipient class: Existing methods can be wrapped, changed or replaced entirely. Interfaces can be retrofitted and additional fields added. Update annotations on classes, methods and fields.

An agent (java -javaagent) applies transplants at load-time. Alternatively, graftt-maven-plugin finds and applies transplants within target/classes.

The core module can be used for building custom tools. It imposes few restrictions on usage and revolves around ASM's tree API.

graftt sprung from artemis-odb/570, refer to it for the discussion leading up to this.

See wiki for more documentation.

Use-cases

  • Functionality for editors: callbacks and tighter tooling integration
  • Additional debugging capabilities: logging, record stack traces, additional callbacks
  • Extending existing functionality for final or otherwise non-extendable classes
  • The odd bug fix in imported dependencies
  • Add or modify hashcode() and toString()
  • Retrofit classes with additional interfaces

Example Transplant for SomeClass

A third-party class we wish to modify

public class SomeClass {
    public final void yo() {
        yolo();
    }

    private void yolo() {
        // boo! we want to call "invokedWithTransplant = true"
        // here (for some reason or other), but yo() is final
        // and can't be extended, and this method is private
        //
        // ...
        //
        yoloCalled = true;
    }

    public boolean yoloCalled = false;
    public static boolean invokedWithTransplant = false;
}

Create a transplant class to donate some bytecode

@Graft.Recipient(SomeClass.class) // target to modify
public class SomeClassTransplant {
    
    @Graft.Fuse // fuse with method in SomeClass
    private void yolo() { // signature matches SomeClass.yolo()
        SomeClass.invokedWithTransplant = true; // whoop-whoop 
        yolo(); // "recursive continuation", actually invokes SomeClass::yolo  
    }
}

Resulting class

Once transplanted, decompiling the modified class yields something similar to:

public class SomeClass {
    public final void yo() {
        yolo();
    }

    private void yolo() {
        SomeClass.invokedWithTransplant = true;
        yolo$original();
    }

    private void yolo$original() {
        yoloCalled = true;
    }

    public boolean yoloCalled = false;
    public static boolean invokedWithTransplant = false;
}

API

  • @Graft.Recipient specifies which class to transplant to.
  • @Graft.Fuse transplants bytecode over to @Graft.Recipient, translating any FooTransplant references to Foo. Call the original method at any time by invoking the method currently being fused; e.g. Fusing FooTransplant::bar with Foo::bar, any call to bar() inside the transplant will point to Foo::bar$original once applied.
  • @Graft.Mock to keep the compiler happy when you need to reference fields or methods in the target class. Mocked references point to target class after transplant.
  • @Graft.Annotations overrides default configuration for removal and updating of annotations. The default behavior copies all annotations from the transplanted elements to the recipient.
  • Interfaces implemented by the transplant are added to the recipient.
  • All fields and methods, except those annotated with @Graft.Mock, are copied to recipient.

Nice to have, but not now:

  • @Graft.Remove: Remove field or method from target class.

Caveats

  • You're working against internal implementation; there are no semver guarantees
  • No rewiring of parent type on target class
  • No @Graft.Fuse for constructors; nice to have, but not initially
  • No GWT support
  • No android support (possible with a custom gradle task)

Usage

Current $VERSION is 0.3.0

Maven

<dependency>
    <groupId>net.onedaybeard.graftt</groupId>
    <artifactId>api</artifactId>
    <version>${VERSION}</version>
</dependency>

Gradle

implementation "net.onedaybeard.graftt:api:${VERSION}"

Agent: Download

$ java -ea -javaagent:agent-${VERSION}.jar ...

Versions

Version
0.3.0
0.2.1
0.2.0
0.1.3