Coder Social home page Coder Social logo

bytecode-compatibility-transformer's Introduction

Bytecode Compatibility Transformer

This Java library provides a set of annotations and bytecode transformer that helps you evolve modular codebase without losing compatibility.

Imagine a modular system where two parts of the code are compiled separately and linked together at runtime. You have module Foo that exposes class Foo, and you have module Bar that uses it.

class Foo {
    public static final Foo INSTANCE = new Foo();
}

class Bar {
    void bar() {
        System.out.println(Foo.INSTANCE);
    }
}

Now you are refactoring the Foo module, and you want to turn this singleton field into a method call.

class Foo {
    public static final Foo getInstance() {
        return new Foo();
    }
}

But unfortunately, you can't do this without breaking existing versions of the Bar module, since their bytecode already has a reference to Foo.INSTANCE baked in.

This library helps you resolve this situation.

When you refactor the code, you put an annotation to the newly introduced method, signaling the fact that a field reference to INSTANCE should resolve to a method call getInstance():

class Foo {
    @AdaptField(name="INSTANCE",was=Foo.class)
    public static Foo getInstance() {
        return new Foo();
    }
}

In the module system where we load Foo and Bar, you then construct a Transformer and loads up the definitions.

import org.jenkinsci.bytecode.Transformer;
Transformer t = new Transformer();
t.loadRules(fooClassLoader);

The loadRules method call takes a ClassLoader, and looks for all the use of AdaptField annotations, which is automatically indexed at compile-time through annotation processor.

The Transformer class has byte[] transform(final String className, byte[] image) method that transforms bytecode according to the rules. You'll have to use this when loading the Bar module. This depends on the module system in question, but for example, with AntClassLoader in Ant you can do the following:

AntClassLoader cl = new AntClassLoader() {
    @Override
    protected Class<?> defineClassFromData(File container, byte[] classData, String className) throws IOException {
        return super.defineClassFromData(container, t.transform(className,classData), className);
    }
};

Using This Library

The Maven coordinates is as follows:

<dependency>
  <groupId>org.jenkins-ci</groupId>
  <artifactId>bytecode-compatibility-transformer</artifactId>
  <version>1.3</version>
</dependency>

<repositories>
  <repository>
    <id>repo.jenkins-ci.org</id>
    <url>http://repo.jenkins-ci.org/public/</url>
  </repository>
</repositories>

What Can Be Adapted

You can adapt both instance and static field access. You can adapt to a field access to either getter/setter methods or to another field.

A static field access must be adapted to another static field access, or static methods. Similarly, An instance field access must be adapter to another instance field access, or instance methods.

When adapting a field to a method call, you can choose to only have a setter without any getter, as in the first example above. In this case, an attempt to set to a field will not be rewritten, and therefore it will result in NoSuchFieldError.

If you want to support both read/write, then you need to create a getter/setter method pair and annotate both methods:

class Foo {
    @AdaptField(name="INSTANCE",was=Foo.class)
    public static Foo getInstance() {
        return new Foo();
    }

    @AdaptField(name="INSTANCE",was=Foo.class)
    public static void setInstance(Foo foo) {
        ...
    }
}

Adapting type

When you adapt a field of a reference type, you can choose a different type. This is convenient when you change the field type to a subtype of what it used to be:

// v1
class Foo {
    public static final Foo INSTANCE = ...;
}

// v2
class abstract Foo {
    @AdaptField(was=Foo.class)
    public static final FooImpl INSTANCE = ...;
}

When you do this, a necessary cast operator is inserted both during get and set. This allows you to adapt any reference type to any other reference type, not just to a subtype, but obviously the actual execution of the code can fail with ClassCastException.

Sibling Projects

For adapting methods, see bridge method injector that provides a related functionality but without a need for runtime class transformation.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.