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 v6.0-beta2. 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 = ClassGenerator.builder(Greeter.class)
.withMethod("sayHello",
call(staticField(System.class, "out"), "println", value("Hello world")))
.build()
.generateClass(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", "NullableProblems"})
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(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 convention
Class<Person> personClass = ClassGenerator.builder(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)", comparableImpl("id", "name"))
.withMethod("equals", equalsImpl("id", "name"))
.withMethod("hash", hashCodeImpl("id", "name"))
.withMethod("hashOfPojo", HashCode.builder()
.with(property(arg(0), "id"))
.with(property(arg(0), "name"))
.build())
.withMethod("toString", ToString.builder()
.withField("id")
.with("name", property(self(), "name"))
.build())
.build()
.generateClass(CLASS_LOADER);

Now we can test our dynamically generated classes:

// Instantiate two objects of dynamically defined class
Person 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 ClassGenerator 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 ClassGenerator.builder(DoubleUnaryOperator.class)
.withMethod("applyAsDouble", PARSER.parse(expression))
.build()
.generateClass(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  Units
CalculatorBenchmark.generated avgt 10 115.882 ± 1.082 ns/op
CalculatorBenchmark.manual avgt 10 115.222 ± 1.600 ns/op

You can find example sources on GitHub