Package Summary Overview Summary

Module java.base
Package java.lang.classfilePREVIEW

# Interface ClassFileTransform<C extends ClassFileTransformPREVIEW<C, E, B>, E extends ClassFileElementPREVIEW, B extends ClassFileBuilderPREVIEW<E, B>>

Type Parameters:
  `C` - the transform type
  `E` - the element type
  `B` - the builder type

All Known Subinterfaces:
  `ClassRemapperPREVIEW`, `ClassTransformPREVIEW`, `CodeLocalsShifterPREVIEW`, `CodeRelabelerPREVIEW`, `CodeStackTrackerPREVIEW`, `CodeTransformPREVIEW`, `FieldTransformPREVIEW`, `MethodTransformPREVIEW`

public sealed interface ClassFileTransform<C extends ClassFileTransformPREVIEW<C, E, B>, E extends ClassFileElementPREVIEW, B extends ClassFileBuilderPREVIEW<E, B>> permits ClassTransformPREVIEW, FieldTransformPREVIEW, MethodTransformPREVIEW, CodeTransformPREVIEW
`ClassFileTransform` is a preview API of the Java platform.
Programs can only use `ClassFileTransform` when preview features are enabled.
Preview features may be removed in a future release, or upgraded to permanent features of the Java platform.
A transformation on streams of elements. Transforms are used during transformation of classfile entities; a transform is provided to a method like `ClassFile.transform(ClassModel, ClassTransform)` PREVIEW, and the elements of the class, along with a builder, are presented to the transform.

The subtypes of ClassFileTransformPREVIEW (e.g., `ClassTransform`PREVIEW) are functional interfaces that accept an element and a corresponding builder. Since any element can be reproduced on the builder via `ClassFileBuilder.with(ClassFileElement)`PREVIEW, a transform can easily leave elements in place, remove them, replace them, or augment them with other elements. This enables localized transforms to be represented concisely.

Transforms also have an `atEnd(ClassFileBuilder)` method, for which the default implementation does nothing, so that a transform can perform additional building after the stream of elements is exhausted.

Transforms can be chained together via the `andThen(ClassFileTransform)` method, so that the output of one becomes the input to another. This allows smaller units of transformation to be captured and reused.

Some transforms are stateful; for example, a transform that injects an annotation on a class may watch for the `RuntimeVisibleAnnotationsAttribute`PREVIEW element and transform it if found, but if it is not found, will generate a RuntimeVisibleAnnotationsAttributePREVIEW element containing the injected annotation from the atEnd(ClassFileBuilder) handler. To do this, the transform must accumulate some state during the traversal so that the end handler knows what to do. If such a transform is to be reused, its state must be reset for each traversal; this will happen automatically if the transform is created with `ClassTransform.ofStateful(Supplier)`PREVIEW (or corresponding methods for other classfile locations.)

Class transformation sample where code transformation is stateful:

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

Complex class instrumentation sample chaining multiple transformations:

                                                                            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)))));
    }

Sealed Class Hierarchy Graph:
Since:
  22

- ## Nested Class Summary

  Nested Classes
  Modifier and Type
  Interface
  Description
  `static interface`
  `ClassFileTransform.ResolvedTransformPREVIEW<E extends ClassFileElementPREVIEW>`
  Preview.
  The result of binding a transform to a builder.

- ## Method Summary

  Modifier and Type
  Method
  Description
  `void`
  `accept (B builder, E element)`
  Transform an element by taking the appropriate actions on the builder.
  `C`
  `andThen (C next)`
  Chain this transform with another; elements presented to the builder of this transform will become the input to the next transform.
  `default void`
  `atEnd (B builder)`
  Take any final action during transformation of a classfile entity.
  `default void`
  `atStart (B builder)`
  Take any preliminary action during transformation of a classfile entity.
  `ClassFileTransform.ResolvedTransformPREVIEW <E>`
  `resolve (B builder)`
  Bind a transform to a builder.

- ## Method Details

  - ### accept

    void accept (B builder, E element)
    Transform an element by taking the appropriate actions on the builder. Used when transforming a classfile entity (class, method, field, method body.) If no transformation is desired, the element can be presented to `ClassFileBuilder.with(ClassFileElement)`PREVIEW. If the element is to be dropped, no action is required.
    Parameters:
      `builder` - the builder for the new entity
      `element` - the element

  - ### atEnd

    default void atEnd (B builder)
    Take any final action during transformation of a classfile entity. Called after all elements of the class are presented to `accept(ClassFileBuilder, ClassFileElement)` .
    Implementation Requirements:
      The default implementation does nothing.
    Parameters:
      `builder` - the builder for the new entity

  - ### atStart

    default void atStart (B builder)
    Take any preliminary action during transformation of a classfile entity. Called before any elements of the class are presented to `accept(ClassFileBuilder, ClassFileElement)` .
    Implementation Requirements:
      The default implementation does nothing.
    Parameters:
      `builder` - the builder for the new entity

  - ### andThen

    C andThen (C next)
    Chain this transform with another; elements presented to the builder of this transform will become the input to the next transform.
    Parameters:
      `next` - the downstream transform
    Returns:
      the chained transform

  - ### resolve

    ClassFileTransform.ResolvedTransformPREVIEW<E> resolve (B builder)
    Bind a transform to a builder. If the transform is chained, intermediate builders are created for each chain link. If the transform is stateful (see, e.g., `ClassTransform.ofStateful(Supplier)`PREVIEW), the supplier is invoked to get a fresh transform object.

    This method is a low-level method that should rarely be used by user code; most of the time, user code should prefer `ClassFileBuilder.transform(CompoundElement, ClassFileTransform)` PREVIEW, which resolves the transform and executes it on the current builder.

    Parameters:
      `builder` - the builder to bind to
    Returns:
      the bound result