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 v4.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'll define a simple Example interface that has a single sayHello() method:

public interface Example {
void sayHello();
}

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

Class<Example> example = ClassBuilder
// DefiningClassLoader represents a loader for defining dynamically generated classes
.create(DefiningClassLoader.create(Thread.currentThread().getContextClassLoader()), Example.class)
.withMethod("sayHello", call(staticField(System.class, "out"), "println", value("Hello world")))
.build();

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

Example instance = example.getDeclaredConstructor().newInstance();
instance.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:

Class<Person> personClass = ClassBuilder.create(DefiningClassLoader.create(Thread.currentThread().getContextClassLoader()), 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")))
.build();

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 ClassBuilder that describes the class that will be generated. It will implement DoubleUnaryOperator interface and will have an applyAsDouble method.

Let's create the appropriate builder:

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

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 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, super fast
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've 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