Skip to content

ClassBuilder ​

com.jsmacrosce.jsmacros.core.library.impl.classes.ClassBuilder

Since1.6.5

Builds a Java class at runtime from source declarations or member builders. Scripts obtain one through Reflection.createClassBuilder, add members, and define it with finishBuildAndFreeze(). The generated class is defined in JsMacros' proxy package;
Reflection.getClassFromClassBuilderResult looks it up by its generated name.

Source declarations provide Java code directly. The typed member builders additionally support configuring modifiers, exceptions, and annotations; their guest-body forms can call back into the running script, unlike source method bodies. The callback body uses a key derived from the generated class name, method name, and JVM descriptor.
Use MethodBuilder.buildBody() to mix Java statements with guest calls;
BodyBuilder shows how to capture a guest call's result in a Java variable.

The returned Class is typed as its superclass, so script code may need a cast to access generated members. Defining the class freezes it; it cannot then be modified or built again.
Compilation and resolution failures are reported by the operation that encounters them.

js
// Build and define a generated subclass with one Java-source method and
// one method that calls back into this script.  
const builder = Reflection.createClassBuilder("Counting",  
  Reflection.getClass("java.util.ArrayList"));  

// members can be written as java source, one declaration at a time
builder.addField("private int total;");  
builder.addMethod("public int getTotal() { return this.total; }");  

// Typed member builders accept Class objects and guestBody calls back into
// this script; its handler receives the instance and boxed arguments.  
const check = builder.addMethod(Reflection.getClass("boolean"), "hasItems");  
check.makePublic();  
check.guestBody(JavaWrapper.methodToJava(function (self, args) {
  return self.size() > 0;  
}));  

// this is what freezes it, and it can only be called once
const CountingClass = builder.finishBuildAndFreeze();  

// The returned Class is exposed as its superclass type; JavaScript can still
// invoke generated members on the constructed instance.  
const counting = Reflection.newInstance(CountingClass);  
print(counting.getTotal());  
print(counting.hasItems());

Type Parameters ​

  • T: superclass type, used as the generated class's exposed type

Nested types ​

Constructors ​

new ClassBuilder ​

new ClassBuilder(String name, Class<T> parent, Class<?>... interfaces)#
Starts a generated class extending parent and implementing the listed interfaces. Dots in name become dollar signs; the generated class is placed in JsMacros' proxy package, and this name is used by Reflection.getClassFromClassBuilderResult to look it up. The parent and interfaces must resolve in the bytecode library's class pool or this constructor throws NotFoundException.
// extending a concrete class, with the shipped typings typing the result
// as that class rather than as the one being built
const builder = Reflection.createClassBuilder("Counting",
  Reflection.getClass("java.util.ArrayList"));
builder.addField("private int total;");

Parameters:

  • name: the name to generate the class under
  • parent: the class to extend
  • interfaces: any interfaces to implement

Fields ​

ctClass ​

ctClass: CtClassread-only#
Javassist handle for the class being assembled. It exposes Javassist operations beyond this builder's methods; the class is frozen when finishBuildAndFreeze() defines it.

methodWrappers ​

static methodWrappers: Map<String, MethodWrapper<Object, Object, Object, ?>>read-only#
Handlers called by generated guest-body code, indexed by generated class name, method name, and JVM descriptor. BodyBuilder adds a call-specific suffix when a method contains multiple guest calls. Handlers are registered as guestBody or appendGuestCode builds the guest-call code.

Methods ​

addAnnotation ​

Starts building an annotation for the class. Its retention policy determines whether the annotation is stored as runtime-visible or runtime-invisible metadata; it is added when finish is called, which returns this builder.
// an annotation with nothing on it. the cast is for the editor's
// benefit: the shipped typings do not describe the annotation types
builder.addAnnotation(Reflection.getClass("java.lang.FunctionalInterface") as any)
  .finish();

// one with a value, which takes two finishes: the first closes the
// array, the second puts the annotation on the class
builder.addAnnotation(Reflection.getClass("java.lang.SuppressWarnings") as any)
  .putArray("value")
  .putString("unchecked")
  .finish()
  .finish();

Parameters:

  • type: the annotation type to put on the class
Returns: a builder for the annotation, which is put on the class when it is finished

addClinit ​

Starts a static class initializer, which runs when the JVM initializes the class. It has no instance receiver or superclass-constructor call. Finish it with a braced Java body, a guest body, a BodyBuilder, or a bytecode-building callback. A guest handler receives a null receiver and an empty argument array.
Returns: a builder for a static class initialiser

addConstructor ​

addConstructor(Class<?>... params): ClassBuilder.ConstructorBuilder#
Starts a constructor builder; the constructor is added when one of its finishing methods is called. A constructor body must call the superclass itself when needed; superclass arguments are not forwarded automatically. Use addClinit() for a static class initializer, which runs when the JVM initializes the class.
const init = builder.addConstructor(Reflection.getClass("java.lang.String"));
init.makePublic();
init.body('super($1); this.label = $1;');

Parameters:

  • params: the parameter types, in order
Returns: a builder for the constructor, which is added to the class when it is finished
addConstructor(String code): ClassBuilder<T>Since1.8.4#
Adds a constructor from its complete Java source declaration. Generic types, varargs, and annotations are not supported. Types in java.lang may be unqualified, as in Java.
public MyClass() { }
public MyClass(String text) { System.out.println(text); }
protected MyClass(String text, int number) { super(text, number, ""); }
public MyClass(String text, int number, String other) {
     this(text, number);
     this.other = other;
}

Parameters:

  • code: the code for the constructor
Returns: self for chaining.

addField ​

addField(Class<?> fieldType, String name): ClassBuilder.FieldBuilder#
Starts a field builder; the field is added only when its end method is called. Use this form to configure modifiers, annotations, or an initializer. Since the field type is supplied as a Class, this form does not specify a generic signature; set any initial value through initializer.
builder.addField(Reflection.getClass("java.lang.String"), "label")
  .makePrivate()
  .toggleFinal()
  .initializer().setString("unnamed")
  .end();

Parameters:

  • fieldType: the type of the field
  • name: the name of the field
Returns: a builder for the field, which is added to the class when it is ended
addField(String code): ClassBuilder<T>Since1.8.4#
Adds a field from its complete Java source declaration. The declaration may include an initializer, but not annotations or generic types. Types in java.lang may be unqualified, as in Java.
private String name;
private java.lang.String name;
public java.util.List list = new java.util.ArrayList();
static int value = 10;

Parameters:

  • code: the code for the field
Returns: self for chaining.

addMethod ​

addMethod(Class<?> returnType, String name, Class<?>... params): ClassBuilder.MethodBuilder#
Starts a method builder; the method is added when one of its finishing methods is called. Use this form to configure exceptions or annotations, create an abstract method, or provide a body in steps or through a script handler. A method body is required unless the method is ended abstract. This form does not declare generic methods or varargs.
const build = builder.addMethod(Reflection.getClass("java.lang.String"), "describe",
  Reflection.getClass("java.lang.Object"));
build.makePublic();
build.body("return "an object: " + $1;");

// the same method, but abstract, which is what an interface needs
const required = builder.addMethod(Reflection.getClass("void"), "reset");
required.makePublic();
required.endAbstract();

Parameters:

  • returnType: the type the method returns
  • name: the name of the method
  • params: the parameter types, in order
Returns: a builder for the method, which is added to the class when it is finished
addMethod(String code): ClassBuilder<T>Since1.8.4#
Adds a method from its complete Java source declaration. Generic types, varargs, and annotations are not supported. Types in java.lang may be unqualified, as in Java.
public Object id(Object obj) { return obj; }
private void print(String text) { System.out.println(text); }
private static java.util.Map toMap(Object[] keys, Object[] values) {
     java.util.Map map = new java.util.HashMap();
     for (int i = 0; i < keys.length; i++) {
         map.put(keys[i], values[i]);
     }
     return map;
 }
public String toString() {
     System.out.println(super.toString());
     return "Hello World!";
 }

Parameters:

  • code: the code for the method
Returns: self for chaining.

finishBuildAndFreeze ​

finishBuildAndFreeze(): Class<? extends T>#
Defines the generated class and returns its Class, typed as its superclass. The class-level annotations are attached before definition. The returned type does not expose generated members, so accessing them may require a cast. This freezes the builder; call it once, after adding all members, since the class cannot then be modified or built again.
const CountingClass = builder.finishBuildAndFreeze();

// typed as what it extends, so an added method needs a cast
const counting = Reflection.newInstance(CountingClass) as any;
print(counting.getTotal());
Returns: the built class, typed as the class it extends