wiki:JavaFfi/RuntimeClass

Version 5 (modified by Alessiostalla, 8 years ago) (diff)

--

Defining Java classes in Lisp (aka runtime-class)

Rationale

Contrary to most other Lisp implementation, ABCL is deeply intertwined with its hosting platform, the JVM. It is an important design goal for ABCL not only to coexist with Java and other languages, but to actually interoperate with them at a deep level. This includes providing components to be consumed by foreign libraries, such as callbacks, event listeners, configuration objects, et cetera.

Generally, when possible, it's advisable to use interfaces rather than concrete classes for that purpose (see Implementing Java interfaces in Lisp). However, not all foreign libraries are properly designed; and sometimes a concrete class is needed for valid reasons (e.g. to inherit important functionality). The "runtime-class" functionality presented here addresses those use cases.

History

ABCL used to have a form of runtime-class functionality in its early days. It was implemented using the popular ASM library. At one point, though, it was disabled in order to eliminate the need for external dependencies. It stayed disabled for quite a long time - enough for getting affected by bit rot with a high probability.

Between late 2011 and early 2012, part of the functionality was resurrected by Alessio Stalla, this time completely in Lisp with no external dependencies. This was made possible by ABCL's class file writer, an internal library coded some time earlier by Erik Huelsmann to better separate concerns in the compiler. The class file writer was slightly extended in order to make it generate Java 1.5 annotation metadata.

The API has been inspired by the original, but has been changed in some places.

Terminology

Before digging into the API, a few terms must be defined for clarity:

  • access flag: a keyword understood by the Java class file writer as a modifier for a class, field, or method. Examples: :public, :private, :static.

Main API entry points

The define-java-class macro

This functionality is not yet implemented. The java:define-java-class macro is the high-level entry point to the runtime-class functionality. It takes a specification of a Java class and expands into code for generating such a class at runtime. If the macro is expanded as part of file compilation, it (optionally?) generates the class at compile-time, dumps it to a file, and expands into code for loading the class from that file.

The jnew-runtime-class function

The java:jnew-runtime-class is the lower-level entry point to the runtime-class functionality. It takes a specification of a Java class and generates the bytecode for it, optionally saving it to a file (not yet implemented) and (not yet optionally) loading it into the current JVM.

Dissecting a Java class definition

In the context of runtime-class, a class definition is a series of arguments that can (or must) be passed to either define-java-class or jnew-runtime-class. They are detailed below.

  • class-name - mandatory argument. A string that must be a valid Java class name according to the Java Language Specification, specifying the name of the defined class.
  • superclass - keyword argument; defaults to "java.lang.Object". A string that must be a valid Java class name according to the Java Language Specification, specifying the superclass of the class being defined.
  • interfaces - keyword argument. A (possibly empty) list of strings naming Java interfaces to be implemented by the defined class.
  • methods - keyword argument. A (possibly empty) list of method definitions (see the appropriate section below).
  • fields - keyword argument. A (possibly empty) list of field definitions (see the appropriate section below).
  • access-flags - keyword argument; defaults to (:public). A (possibly empty) list of access flags as accepted by the Java class file writer, which will be applied to the defined class.
  • annotations - keyword argument. A (possibly empty) list of annotation definitions (see the appropriate section below) to be applied to the defined class.

Method definitions

Arguably, the most important feature of runtime-class is to define Java methods in Lisp. Each method is implemented as a call to a pre-existing Lisp function, with the necessary conversions for arguments and return values. Contrarily to the old implementation of runtime-class, no API is specified to replace method implementations at runtime; instead, you can leverage the usual mechanism of passing a symbol as a function designator and later replacing its symbol-function.