インタフェースClassFileTransform<C extends ClassFileTransformPREVIEW<C,E,B>,E extends ClassFileElementPREVIEW,B extends ClassFileBuilderPREVIEW<E,B>>

型パラメータ:
C - 変換タイプ
E - 要素の型
B - ビルダー・タイプ
既知のすべてのサブインタフェース:
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は、JavaプラットフォームのプレビューAPIです。
プレビュー機能が有効な場合のみ、プログラムでClassFileTransformを使用できます。
プレビュー機能は、今後のリリースで削除するか、Javaプラットフォームの永続機能にアップグレードすることができます。
要素のストリームに対する変換。 変換は、クラス・ファイル・エンティティの変換中に使用されます。変換は、ClassFile.transform(ClassModel, ClassTransform)PREVIEWなどのメソッドに提供され、クラスの要素はビルダーとともに変換に表示されます。

ClassFileTransformPREVIEW (e.g., ClassTransformPREVIEW)のサブタイプは、要素および対応するビルダーを受け入れる関数型インタフェースです。 ClassFileBuilder.with(ClassFileElement)PREVIEWを使用して、任意の要素をビルダーで再現できるため、変換によって、要素を所定の位置に残したり、削除したり、置換したり、他の要素で拡張したりできます。 これにより、ローカライズされた変換を簡潔に表現できます。

変換にはatEnd(ClassFileBuilder)メソッドもあり、デフォルトの実装では何も実行されないため、要素のストリームを使い果たした後に変換で追加のビルドを実行できます。

変換は、andThen(ClassFileTransform)メソッドを介して連鎖できるため、ある変換の出力が別の変換への入力になります。 これにより、より小さな変換単位を取得して再利用できます。

一部の変換はステートフルです。たとえば、クラスに注釈を注入する変換は、RuntimeVisibleAnnotationsAttributePREVIEW要素を監視し、見つかった場合は変換しますが、見つからない場合は、atEnd(ClassFileBuilder)ハンドラから注入された注釈を含むRuntimeVisibleAnnotationsAttributePREVIEW要素を生成します。 これを行うには、変換は、エンド・ハンドラが何をすべきかを認識できるように、トラバーサル中に一部の状態を蓄積する必要があります。 このような変換を再利用する場合は、その状態をトラバースごとにリセットする必要があります。これは、変換がClassTransform.ofStateful(Supplier)PREVIEW (または、他のクラス・ファイルのロケーションに対応するメソッド。)で作成される場合に自動的に行われます

コード変換がステートフルであるクラス変換のサンプル:

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

複数の変換を連鎖させる複雑なクラス・インストゥルメンテーションのサンプル:

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

シール済クラス階層グラフ:
ClassFileTransformのシール済クラス階層グラフClassFileTransformのシール済クラス階層グラフ
導入されたバージョン:
22
  • ネストされたクラスのサマリー

    ネストされたクラス
    修飾子と型
    インタフェース
    説明
    static interface 
    Preview.
    変換をビルダーにバインドした結果。
  • メソッドのサマリー

    修飾子と型
    メソッド
    説明
    void
    accept(B builder, E element)
    ビルダーで適切なアクションを実行して、要素を変換します。
    andThen(C next)
    この変換を別の変換と連鎖します。この変換のビルダーに提示される要素は、次の変換への入力になります。
    default void
    atEnd(B builder)
    クラス・ファイル・エンティティの変換中に、最終的なアクションを実行します。
    default void
    atStart(B builder)
    クラス・ファイル・エンティティの変換中に、事前アクションを実行します。
    resolve(B builder)
    変換をビルダーにバインドします。
  • メソッドの詳細

    • accept

      void accept(B builder, E element)
      ビルダーで適切なアクションを実行して、要素を変換します。 クラス・ファイル・エンティティ(クラス、メソッド、フィールド、メソッド本文。)の変換時に使用されます。 変換が不要な場合は、要素をClassFileBuilder.with(ClassFileElement)PREVIEWに提示できます。 要素を削除する場合、アクションは必要ありません。
      パラメータ:
      builder - 新しいエンティティのビルダー
      element - 要素
    • atEnd

      default void atEnd(B builder)
      クラス・ファイル・エンティティの変換中に、最終的なアクションを実行します。 クラスのすべての要素がaccept(ClassFileBuilder, ClassFileElement)に提示された後に呼び出されます。
      実装要件:
      デフォルト実装は何も実行しません。
      パラメータ:
      builder - 新しいエンティティのビルダー
    • atStart

      default void atStart(B builder)
      クラス・ファイル・エンティティの変換中に、事前アクションを実行します。 クラスの要素がaccept(ClassFileBuilder, ClassFileElement)に提示される前に呼び出されます。
      実装要件:
      デフォルト実装は何も実行しません。
      パラメータ:
      builder - 新しいエンティティのビルダー
    • andThen

      C andThen(C next)
      この変換を別の変換と連鎖します。この変換のビルダーに提示される要素は、次の変換への入力になります。
      パラメータ:
      next - ダウンストリーム変換
      戻り値:
      連鎖した変換
    • resolve

      変換をビルダーにバインドします。 変換が連鎖している場合、チェーン・リンクごとに中間ビルダーが作成されます。 変換がステートフル(see, e.g., ClassTransform.ofStateful(Supplier)PREVIEW)の場合、新しい変換オブジェクトを取得するためにサプライヤが呼び出されます。

      このメソッドは、ユーザー・コードではほとんど使用されない低レベルのメソッドです。ほとんどの場合、ユーザー・コードは、変換を解決し、現在のビルダーで実行するClassFileBuilder.transform(CompoundElement, ClassFileTransform)PREVIEWを優先する必要があります。

      パラメータ:
      builder - バインドするビルダー
      戻り値:
      バインドされた結果