Codegen Module

Codegen module allows to dynamically generate classes and methods in runtime without the overhead of reflection.

  • Built on top of the ObjectWeb ASM and basically works as embedded Lisp
  • Super concise and elegant solution with an easy-to-use API that encapsulates most of the complexity involved in working with bytecode.
  • Dynamically creates classes needed for runtime query processing (storing the results of computations, intermediate tuples, compound keys, etc).
  • It implements basic relational algebra operations for individual items: aggregate functions, projections, predicates, ordering, group-by, etc.
  • Easy to use API that encapsulates most of the complexity involved in working with bytecode.

Let’s look at one simple example.

Codegen can be used for generating in different scenarios, for example:

  • POJO containers that store the results of processing user data
  • predicates that filter data placed in database (which is also powered by DataKernel!)

Imagine that a service needs to find all adult users in its system. It produces such query to the database:

SELECT id, name, age FROM users WHERE age >= 18;

First, it is necessary to build a data container class (POJO):

class User {
      long id;
      String name;
      int age;
}

The process of a new class generation is simple and minimalistic. Use the builder pattern to produce the fields and methods of a new class.

So, let’s generate a User class:

Class<?> userClass = ClassBuilder.create(Object.class)
    .withField(id, long.class)
    .withField(name, String.class)
    .withField(age, int.class)
    .build()

Now let’s move on to the predicate generation:

class UserPredicate implements Predicate<User> {
    boolean test (User user) {
        return user.age >= 18;
    }
}

Methods production is a little bit trickier. Codegen provides special expression DSL for method body generation:

Class<Predicate> filter = ClassBuilder.create(Predicate.class)
    .withMethod(test, boolean.class, userClass, 
                cmpGe(property(arg(0), age), value(18)))
    .build()

How to use Codegen in your project? You can add the Codegen module to your project by inserting dependency in pom.xml:

<dependency>
    <groupId>io.datakernel</groupId>
    <artifactId>datakernel-codegen</artifactId>
    <version>3.1.0</version>
</dependency>

Examples

Note: To run the examples, you need to clone DataKernel from GitHub:
$ git clone https://github.com/softindex/datakernel
And import it as a Maven project. Check out branch v3.1. Before running the examples, build the project.
These examples are located at datakernel -> examples -> core -> codegen

Codegen Expressions Example

An example of creating a sayHello() method:

public final class CodegenExpressionsExample {
	public static void main(String[] args) throws InstantiationException, IllegalAccessException {
		Class<Example> example = ClassBuilder
				// context class loader is used because without it maven runner is not happy with our codegen
				.create(DefiningClassLoader.create(getSystemClassLoader()), Example.class)
				.withMethod("sayHello", call(staticField(System.class, "out"), "println", value("Hello world")))
				.build();
		Example instance = example.newInstance();

		instance.sayHello();
	}

	public interface Example {
		void sayHello();
	}
}

Dynamic Class Creation Example

In this example Codegen dynamically creates a class that implements Person interface:

public static void main(String[] args) throws IllegalAccessException, InstantiationException {
	// Construct 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();

	// Instantiate two objects of dynamically defined class
	Person jack = personClass.newInstance();
	Person martha = personClass.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));

	// Compare dynamically created hashing implementation with the conventional one
	ExamplePojo examplePojo = new ExamplePojo(5, "Jack");
	System.out.println(examplePojo);
	System.out.println("jack.hash(examplePojo)  = " + jack.hashOfPojo(examplePojo));
	System.out.println("jack.hash()             = " + jack.hash());
	System.out.println("examplePojo.hashCode()  = " + examplePojo.hashCode());
}