Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/cojen/maker

Cojen/Maker is a lightweight, full-featured, low-level dynamic Java class generator designed for ease of use.
https://github.com/cojen/maker

bytecode code-generation invokedynamic java java-bytecode jvm jvm-bytecode

Last synced: about 2 months ago
JSON representation

Cojen/Maker is a lightweight, full-featured, low-level dynamic Java class generator designed for ease of use.

Awesome Lists containing this project

README

        

[![Maven Central](https://img.shields.io/maven-central/v/org.cojen/cojen-maker.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22org.cojen%22%20AND%20a:%22cojen-maker%22)

The Cojen/Maker library is a lightweight, full-featured, low-level dynamic Java class generator which is designed for [ease of use](https://github.com/cojen/Maker/wiki/Ease-of-use).

Here's a simple "hello, world" example:

```java
ClassMaker cm = ClassMaker.begin().public_();

// public static void run()...
MethodMaker mm = cm.addMethod(null, "run").public_().static_();

// System.out.println(...
mm.var(System.class).field("out").invoke("println", "hello, world");

Class> clazz = cm.finish();
clazz.getMethod("run").invoke(null);
```

- [Javadocs](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/package-summary.html)
- [Coding patterns](https://github.com/cojen/Maker/wiki/Coding-patterns)
- [Examples](example/main/java/org/cojen/example)

A key feature of the library is that the JVM operand stack isn't directly accessible, which makes it much easier to use. Local variables are used exclusively, and conversion to the stack-based representation is automatic. In some cases, this can result in more local variables than is strictly necessary, but modern JVMs reduce the set using liveness analysis.

In addition to simplifying basic class generation, the features of the [`java.lang.invoke`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/package-summary.html) package are fully integrated, but without all the complexity. The [`ObjectMethods`](https://github.com/cojen/Maker/blob/master/example/main/java/org/cojen/example/ObjectMethods.java) example shows how to define a bootstrap method which generates code "just in time".

Design features
---------------

The API is designed to be very simple, defined by a few interfaces and no public classes. Despite its tiny size, the API supports nearly all of the JVM features and bytecode instructions. Features like generics aren't directly supported, although due to type erasure, dynamically generated classes don't require generics anyhow. Instructions which directly manipulate stack operands aren't supported either, because such access isn't necessary with this API.

In order to keep the API simple, some capabilities are merged into common interfaces. In particular, there's no `Type` class to represent things like variable types. Instead, a plain `Class` can be used to specify a type, as can a string name or descriptor. To reference types which are currently being made, a [`ClassMaker`](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/ClassMaker.html) instance can be used as a type. [`Variable`](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/Variable.html) instances themselves can be used as generic type specifiers, and this doesn't necessarily create an actual variable in the generated bytecode.

Java bytecode is imperative in nature, and for this reason, the API is also imperative. Code execution order is top to bottom, and labels are used to control execution flow. Higher-level scoping features are provided in a few cases as a convenience. In particular, exception handlers can be generated with a callback, eliminating the need to specify an explicit goto to skip past the handler. This is even more helpful when generating [`finally`](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/MethodMaker.html#finally_(org.cojen.maker.Label,java.lang.Runnable)) clauses, as it ensures that all exit paths from a guarded scope are properly covered.

Type conversions between primitive types are performed automatically, including boxing/unboxing conversions. Narrowing conversions aren't performed automatically, unless it can be proven that this causes no loss of information. This effectively limits such conversions to constants only.

Finished classes can be loaded immediately, or they can be written out to a file. Classes which are immediately loaded are eligible to be unloaded when all generated classes in the group are no longer referenced. In this context, a "group" is defined by a parent `ClassLoader` and an optional key object. The group itself is a child `ClassLoader`, and so classes in the group have package-level access to each other. Applications can control permissions and unloading behavior by carefully choosing an appropriate key object. Classes can also be defined as [_hidden_](https://github.com/cojen/Maker/wiki/Hidden-classes), in which case they can be unloaded even when the group itself cannot be unloaded.

The `java.lang.invoke` package provides powerful features for dynamically generating code, but it's quite complicated and somewhat incomplete. Nonetheless, it does provide very useful features for supporting the so called `invokedynamic` instruction. The Cojen/Maker API fully supports these features, and it does so seamlessly. [`MethodHandle`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/MethodHandle.html) and [`VarHandle`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/invoke/VarHandle.html) instances can be freely exchanged at various points in the API, at code generation time or at runtime. To use the `invokedynamic` instruction, the API provides [`indy`](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/Variable.html#indy(java.lang.String,java.lang.Object...)) and [`condy`](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/Variable.html#condy(java.lang.String,java.lang.Object...)) methods for specifying the required bootstrap method. Any kind of constant can be passed into the bootstrap method, because special encoding strategies are performed automatically, including the handling of [`Constable`](https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/constant/Constable.html) objects.

Another nice feature is the [`setExact`](https://cojen.github.io/Maker/javadoc/org.cojen.maker/org/cojen/maker/Variable.html#setExact(java.lang.Object)) method, which allows arbitrary object instances to be passed into dynamically generated classes. Ordinarily, only simple constants can be specified, or else the "condy" feature must be used to reconstruct the object upon demand. Underneath the covers, `setExact` uses the condy feature to extract the object instance from a special hash table, keyed by the class instance itself. This feature doesn't work for generated classes which are loaded from a file.

The implementation of the Cojen/Maker library is relatively small, and it has no dependencies. The release jar is about 250KB in size, and it includes all debugging information.

Limitations
-----------

The Cojen/Maker library is designed for implementing dynamic languages, and for designing utilities that achieve higher performance than is possible when using the reflection API. It isn't designed for modifying classes or for implementing instrumentation agents. That is, you cannot start with an existing class and make modifications to it — classes are only ever made "from scratch". A future version might support class modifications, but there's no plans at this time.

Although the library can be used for writing a frontend compiler, it doesn't have any facilities for reading class symbols. For example, it's possible to write a Java compiler that uses Cojen for writing class files, but it would need another tool for extracting symbols from pre-compiled jar files and so forth. Such a feature could be added of course, but it's a lower priority.

Because of its somewhat low-level design, the library doesn't prevent the creation of broken classes. For example, failing to definitely assign a value to a variable will cause a `VerifyError` to be thrown when loading the class. The [Coding errors](https://github.com/cojen/Maker/wiki/Coding-errors) page has more details.