Skip to main content

Examples

note

To run the examples, you need to clone ActiveJ from GitHub:

git clone https://github.com/activej/activej

And import it as a Maven project. Check out tag v5.4.3. Before running the examples, build the project. These examples are located at activej/examples/core/codegen

Bytecode Expressions

Let's create a simple sayHello() method that prints out "Hello world!". First, we will define a simple Example interface that has a single sayHello() method:

public interface Greeter {  void sayHello();}

Now we can proceed to the description of Example subclass behaviour. For this purpose we will use ClassBuilder class.

Class<Greeter> greeterClass = ClassBuilder.create(Greeter.class)    .withMethod("sayHello",        call(staticField(System.class, "out"), "println", value("Hello world")))    .defineClass(CLASS_LOADER);

To instantiate the described class, simply use newInstance():

Greeter greeter = greeterClass.getDeclaredConstructor().newInstance();greeter.sayHello();
See full example on GitHub

Dynamic Class Creation

In this example we will dynamically create a class that implements an interface. So let's first create a simple Person interface:

@SuppressWarnings("unused")public interface Person extends Comparable<Person> {  void setIdAndName(int id, String name);
  int getId();
  String getName();
  int hashOfPojo(ExamplePojo personPojo);
  int hash();
  @Override  int compareTo(@NotNull Person o);
  @Override  String toString();
  @Override  boolean equals(Object obj);}

Move on to constructing a class that implements Person interface:

// declare fields// setter for both fields - a sequence of actions// compareTo, equals, hashCode and toString methods implementations follow the standard conventionClass<Person> personClass = ClassBuilder.create(Person.class)    // declare fields    .withField("id", int.class)    .withField("name", String.class)
    // setter for both fields - a sequence of actions    .withMethod("setIdAndName", sequence(        set(property(self(), "id"), arg(0)),        set(property(self(), "name"), arg(1))))    .withMethod("getId", property(self(), "id"))    .withMethod("getName", property(self(), "name"))
    // compareTo, equals, hashCode and toString methods implementations follow the standard convention    .withMethod("int compareTo(Person)", compareToImpl("id", "name"))    .withMethod("equals", equalsImpl("id", "name"))    .withMethod("hashOfPojo", hash(property(arg(0), "id"), property(arg(0), "name")))    .withMethod("hash", hash(property(self(), "id"), property(self(), "name")))    .withMethod("toString", ExpressionToString.create()        .withQuotes("{", "}", ", ")        .with("id: ", property(self(), "id"))        .with("name: ", property(self(), "name")))    .defineClass(CLASS_LOADER);

Now we can test our dynamically generated classes:

// Instantiate two objects of dynamically defined classPerson jack = personClass.getDeclaredConstructor().newInstance();Person martha = personClass.getDeclaredConstructor().newInstance();
jack.setIdAndName(5, "Jack");martha.setIdAndName(jack.getId() * 2, "Martha");
System.out.println("First person: " + jack);System.out.println("Second person: " + martha);
System.out.println("jack.equals(martha) ? : " + jack.equals(martha));
See full example on GitHub

Calculator example

In this example we will create a calculator that parses an input equation string to an AST. Then, it generates an optimized class to calculate the expression.

First, create a parser that returns an AST of the expression:

private static final Parser<Expression> EXPRESSION = new OperatorTable<Expression>()    .infixl(DELIMITERS.token("+").retn(Expressions::add), 10)    .infixl(DELIMITERS.token("-").retn(Expressions::sub), 10)    .infixl(DELIMITERS.token("*").retn(Expressions::mul), 20)    .infixl(DELIMITERS.token("/").retn(Expressions::div), 20)    .infixl(DELIMITERS.token("%").retn(Expressions::rem), 20)    .prefix(DELIMITERS.token("-").retn(Expressions::neg), 30)    .infixr(DELIMITERS.token("^").retn((left, right) -> Expressions.staticCall(Math.class, "pow", left, right)), 40)    .build(ATOM);

Next, create a ClassBuilder that describes the class that will be generated. It will implement the DoubleUnaryOperator interface and will have the applyAsDouble method.

Let's create the appropriate builder:

public static Class<DoubleUnaryOperator> compile(String expression) {  return ClassBuilder.create(DoubleUnaryOperator.class)      .withMethod("applyAsDouble", PARSER.parse(expression))      .defineClass(CLASS_LOADER);}

The method will have a var1 parameter for the unknown x:

private static final Parser<Expression> UNKNOWN = DELIMITERS.token("x").retn(Expressions.arg(0));

As a result, ActiveJ Codegen will generate a bytecode of the following class:

public final class Class1 implements DoubleUnaryOperator {    public Class1() {    }
    public double applyAsDouble(double var1) {        return (2.0D + 2.0D * 2.0D) * -var1 + 5.0D + 1024.0D / (100.0D + 58.0D) * 50.0D * 37.0D - 100.0D + 2.0D * Math.pow(var1, 2.0D) % 4.0D;    }}

Now let's processes a manually written code and dynamically generated instance evaluation:

public static void main(String[] args) throws Exception {  double x = -1;
  // manual code, superfast  System.out.println(((2.0 + 2.0 * 2.0) * -x) + 5.0 + 1024.0 / (100.0 + 58.0) * 50.0 * 37.0 - 100.0 + 2.0 * Math.pow(x, 2.0) % 3.0);
  DoubleUnaryOperator instance = compile("((2 + 2 * 2) * -x) + 5 + 1024 / (100 + 58) * 50 * 37 - 100 + 2 * x ^ 2 % 3").getDeclaredConstructor().newInstance();
  // generated instance evaluation, literally equivalent to manual code (with a method call around it), except it was dynamically generated  System.out.println(instance.applyAsDouble(x));}

We have also ran benchmarks for this expression to compare the performance:

Benchmark                        Mode  Cnt    Score    Error  UnitsCalculatorBenchmark.generated    avgt   10  115.882 ±  1.082  ns/opCalculatorBenchmark.manual       avgt   10  115.222 ±  1.600  ns/op

You can find example sources on GitHub