java.lang.classfile.components (Java SE 23 & JDK 23)
Description: declaration: module: java.base, package: java.lang.classfile.components
Keywords: java.lang.classfile.components package

# Package java.lang.classfile.components

package java.lang.classfile.components
`java.lang.classfile.components` is a preview API of the Java platform.
Programs can only use `java.lang.classfile.components` when preview features are enabled.
Preview features may be removed in a future release, or upgraded to permanent features of the Java platform.

## Provides specific components, transformations, and tools built on top of the `java.lang.classfile`PREVIEW library.

The `java.lang.classfile.components` package contains specific transformation components and utility classes helping to compose very complex tasks with minimal effort.

### `ClassPrinter`PREVIEW

`ClassPrinter`PREVIEW is a helper class providing seamless export of a `ClassModel`PREVIEW, `FieldModel`PREVIEW, `MethodModel`PREVIEW, or `CodeModel`PREVIEW into human-readable structured text in JSON, XML, or YAML format, or into a tree of traversable and printable nodes.

Primary purpose of `ClassPrinter`PREVIEW is to provide human-readable class info for debugging, exception handling and logging purposes. The printed class also conforms to a standard format to support automated offline processing.

The most frequent use case is to simply print a class:

    ClassPrinter.toJson(classModel, ClassPrinter.Verbosity.TRACE_ALL, System.out::print);

`ClassPrinter`PREVIEW allows to traverse tree of simple printable nodes to hook custom printer:

    void customPrint(ClassModel classModel) {
        print(ClassPrinter.toTree(classModel, ClassPrinter.Verbosity.TRACE_ALL));
    }

    void print(ClassPrinter.Node node) {
        switch (node) {
            case ClassPrinter.MapNode mn -> {
                // print map header
                mn.values().forEach(this::print);
            }
            case ClassPrinter.ListNode ln -> {
                // print list header
                ln.forEach(this::print);
            }
            case ClassPrinter.LeafNode n -> {
                // print leaf node
            }
        }
    }

Another use case for `ClassPrinter`PREVIEW is to simplify writing of automated tests:

    @Test
    void printNodesInTest(ClassModel classModel) {
        var classNode = ClassPrinter.toTree(classModel, ClassPrinter.Verbosity.TRACE_ALL);
        assertContains(classNode, "method name", "myFooMethod");
        assertContains(classNode, "field name", "myBarField");
        assertContains(classNode, "inner class", "MyInnerFooClass");
    }

    void assertContains(ClassPrinter.Node node, ConstantDesc key, ConstantDesc value) {
        if (!node.walk().anyMatch(n -> n instanceof ClassPrinter.LeafNode ln
                               && ln.name().equals(key)
                               && ln.value().equals(value))) {
            node.toYaml(System.out::print);
            throw new AssertionError("expected %s: %s".formatted(key, value));
        }
    }

### `ClassRemapper`PREVIEW

ClassRemapper is a `ClassTransform`PREVIEW, `FieldTransform`PREVIEW, `MethodTransform`PREVIEW and `CodeTransform`PREVIEW deeply re-mapping all class references in any form, according to given map or map function.

The re-mapping is applied to superclass, interfaces, all kinds of descriptors and signatures, all attributes referencing classes in any form (including all types of annotations), and to all instructions referencing to classes.

Primitive types and arrays are never subjects of mapping and are not allowed targets of mapping.

Arrays of reference types are always decomposed, mapped as the base reference types and composed back to arrays.

Single class remapping example:

    var classRemapper = ClassRemapper.of(
            Map.of(CD_Foo, CD_Bar));
    var cc = ClassFile.of();
    for (var classModel : allMyClasses) {
        byte[] newBytes = classRemapper.remapClass(cc, classModel);

    }

Remapping of all classes under specific package:

    var classRemapper = ClassRemapper.of(cd ->
            ClassDesc.ofDescriptor(cd.descriptorString().replace("Lcom/oldpackage/", "Lcom/newpackage/")));
    var cc = ClassFile.of();
    for (var classModel : allMyClasses) {
        byte[] newBytes = classRemapper.remapClass(cc, classModel);

    }

### `CodeLocalsShifter`PREVIEW

`CodeLocalsShifter`PREVIEW is a `CodeTransform`PREVIEW shifting locals to newly allocated positions to avoid conflicts during code injection. Locals pointing to the receiver or to method arguments slots are never shifted. All locals pointing beyond the method arguments are re-indexed in order of appearance.

Sample of code transformation shifting all locals in all methods:

    byte[] newBytes = ClassFile.of().transform(
            classModel,
            (classBuilder, classElement) -> {
                if (classElement instanceof MethodModel method)
                    classBuilder.transformMethod(method,
                            MethodTransform.transformingCode(
                                    CodeLocalsShifter.of(method.flags(), method.methodTypeSymbol())));
                else
                    classBuilder.accept(classElement);
            });

### `CodeRelabeler`PREVIEW

`CodeRelabeler`PREVIEW is a `CodeTransform`PREVIEW replacing all occurrences of `Label`PREVIEW in the transformed code with new instances. All `LabelTarget`PREVIEW instructions are adjusted accordingly. Relabeled code graph is identical to the original.

Primary purpose of `CodeRelabeler`PREVIEW is for repeated injections of the same code blocks. Repeated injection of the same code block must be relabeled, so each instance of `Label`PREVIEW is bound in the target bytecode exactly once.

Sample transformation relabeling all methods:

    byte[] newBytes = ClassFile.of().transform(
            classModel,
            ClassTransform.transformingMethodBodies(
                    CodeTransform.ofStateful(CodeRelabeler::of)));

### Class Instrumentation Sample

Following snippet is sample composition of `ClassRemapper`PREVIEW, `CodeLocalsShifter`PREVIEW and `CodeRelabeler`PREVIEW into fully functional class instrumenting transformation:

    byte[] classInstrumentation(ClassModel target, ClassModel instrumentor, Predicate<MethodModel> instrumentedMethodsFilter) {
        var instrumentorCodeMap = instrumentor.methods().stream()
                                              .filter(instrumentedMethodsFilter)
                                              .collect(Collectors.toMap(mm -> mm.methodName().stringValue() + mm.methodType().stringValue(), mm -> mm.code().orElseThrow()));
        var targetFieldNames = target.fields().stream().map(f -> f.fieldName().stringValue()).collect(Collectors.toSet());
        var targetMethods = target.methods().stream().map(m -> m.methodName().stringValue() + m.methodType().stringValue()).collect(Collectors.toSet());
        var instrumentorClassRemapper = ClassRemapper.of(Map.of(instrumentor.thisClass().asSymbol(), target.thisClass().asSymbol()));
        return ClassFile.of().transform(target,
                ClassTransform.transformingMethods(
                        instrumentedMethodsFilter,
                        (mb, me) -> {
                            if (me instanceof CodeModel targetCodeModel) {
                                var mm = targetCodeModel.parent().get();
                                //instrumented methods code is taken from instrumentor
                                mb.transformCode(instrumentorCodeMap.get(mm.methodName().stringValue() + mm.methodType().stringValue()),
                                        //all references to the instrumentor class are remapped to target class
                                        instrumentorClassRemapper.asCodeTransform()
                                        .andThen((codeBuilder, instrumentorCodeElement) -> {
                                            //all invocations of target methods from instrumentor are inlined
                                            if (instrumentorCodeElement instanceof InvokeInstruction inv
                                                && target.thisClass().asInternalName().equals(inv.owner().asInternalName())
                                                && mm.methodName().stringValue().equals(inv.name().stringValue())
                                                && mm.methodType().stringValue().equals(inv.type().stringValue())) {

                                                //store stacked method parameters into locals
                                                var storeStack = new ArrayDeque<StoreInstruction>();
                                                int slot = 0;
                                                if (!mm.flags().has(AccessFlag.STATIC))
                                                    storeStack.push(StoreInstruction.of(TypeKind.ReferenceType, slot++));
                                                for (var pt : mm.methodTypeSymbol().parameterList()) {
                                                    var tk = TypeKind.from(pt);
                                                    storeStack.push(StoreInstruction.of(tk, slot));
                                                    slot += tk.slotSize();
                                                }
                                                storeStack.forEach(codeBuilder::with);

                                                //inlined target locals must be shifted based on the actual instrumentor locals
                                                codeBuilder.block(inlinedBlockBuilder -> inlinedBlockBuilder
                                                        .transform(targetCodeModel, CodeLocalsShifter.of(mm.flags(), mm.methodTypeSymbol())
                                                        .andThen(CodeRelabeler.of())
                                                        .andThen((innerBuilder, shiftedTargetCode) -> {
                                                            //returns must be replaced with jump to the end of the inlined method
                                                            if (shiftedTargetCode instanceof ReturnInstruction)
                                                                innerBuilder.goto_(inlinedBlockBuilder.breakLabel());
                                                            else
                                                                innerBuilder.with(shiftedTargetCode);
                                                        })));
                                            } else
                                                codeBuilder.with(instrumentorCodeElement);
                                        }));
                            } else
                                mb.with(me);
                        })
                .andThen(ClassTransform.endHandler(clb ->
                    //remaining instrumentor fields and methods are injected at the end
                    clb.transform(instrumentor,
                            ClassTransform.dropping(cle ->
                                    !(cle instanceof FieldModel fm
                                            && !targetFieldNames.contains(fm.fieldName().stringValue()))
                                    && !(cle instanceof MethodModel mm
                                            && !ConstantDescs.INIT_NAME.equals(mm.methodName().stringValue())
                                            && !targetMethods.contains(mm.methodName().stringValue() + mm.methodType().stringValue())))
                            //and instrumentor class references remapped to target class
                            .andThen(instrumentorClassRemapper)))));
    }

Since:
  22

- Related Packages
  Package
  Description
  java.lang.classfilePREVIEW
  Preview.
  Provides classfile parsing, generation, and transformation library.
  java.lang.classfile.attributePREVIEW
  Preview.
  Provides interfaces describing classfile attributes for the `java.lang.classfile`PREVIEW library.
  java.lang.classfile.constantpoolPREVIEW
  Preview.
  Provides interfaces describing classfile constant pool entries for the `java.lang.classfile`PREVIEW library.
  java.lang.classfile.instructionPREVIEW
  Preview.
  Provides interfaces describing code instructions for the `java.lang.classfile`PREVIEW library.
- Class
  Description
  ClassPrinterPREVIEW
  Preview.
  A printer of classfiles and its elements.
  ClassPrinter.LeafNodePREVIEW
  Preview.
  A leaf node holding single printable value.
  ClassPrinter.ListNodePREVIEW
  Preview.
  A tree node holding `List` of nested nodes.
  ClassPrinter.MapNodePREVIEW
  Preview.
  A tree node holding `Map` of nested nodes.
  ClassPrinter.NodePREVIEW
  Preview.
  Named, traversable, and printable node parent.
  ClassPrinter.VerbosityPREVIEW
  Preview.
  Level of detail to print or export.
  ClassRemapperPREVIEW
  Preview.
  `ClassRemapper` is a `ClassTransform`PREVIEW, `FieldTransform`PREVIEW, `MethodTransform`PREVIEW and `CodeTransform`PREVIEW deeply re-mapping all class references in any form, according to given map or map function.
  CodeLocalsShifterPREVIEW
  Preview.
  `CodeLocalsShifter`PREVIEW is a `CodeTransform`PREVIEW shifting locals to newly allocated positions to avoid conflicts during code injection.
  CodeRelabelerPREVIEW
  Preview.
  A code relabeler is a `CodeTransform`PREVIEW replacing all occurrences of `Label`PREVIEW in the transformed code with new instances.
  CodeStackTrackerPREVIEW
  Preview.
  `CodeStackTracker`PREVIEW is a `CodeTransform`PREVIEW tracking stack content and calculating max stack size.