インタフェース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
ClassFileTransform
は、JavaプラットフォームのプレビューAPIです。
ClassFile.transform(ClassModel, ClassTransform)
PREVIEWなどのメソッドに提供され、クラスの要素はビルダーとともに変換に表示されます。
ClassFileTransformPREVIEW (e.g., ClassTransform
PREVIEW)のサブタイプは、要素および対応するビルダーを受け入れる関数型インタフェースです。 ClassFileBuilder.with(ClassFileElement)
PREVIEWを使用して、任意の要素をビルダーで再現できるため、変換によって、要素を所定の位置に残したり、削除したり、置換したり、他の要素で拡張したりできます。 これにより、ローカライズされた変換を簡潔に表現できます。
変換にはatEnd(ClassFileBuilder)
メソッドもあり、デフォルトの実装では何も実行されないため、要素のストリームを使い果たした後に変換で追加のビルドを実行できます。
変換は、andThen(ClassFileTransform)
メソッドを介して連鎖できるため、ある変換の出力が別の変換への入力になります。 これにより、より小さな変換単位を取得して再利用できます。
一部の変換はステートフルです。たとえば、クラスに注釈を注入する変換は、RuntimeVisibleAnnotationsAttribute
PREVIEW要素を監視し、見つかった場合は変換しますが、見つからない場合は、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)))));
}
-
ネストされたクラスのサマリー
ネストされたクラス修飾子と型インタフェース説明static interface
Preview.変換をビルダーにバインドした結果。 -
メソッドのサマリー
修飾子と型メソッド説明void
ビルダーで適切なアクションを実行して、要素を変換します。この変換を別の変換と連鎖します。この変換のビルダーに提示される要素は、次の変換への入力になります。default void
クラス・ファイル・エンティティの変換中に、最終的なアクションを実行します。default void
クラス・ファイル・エンティティの変換中に、事前アクションを実行します。変換をビルダーにバインドします。
-
メソッドの詳細
-
accept
ビルダーで適切なアクションを実行して、要素を変換します。 クラス・ファイル・エンティティ(クラス、メソッド、フィールド、メソッド本文。)の変換時に使用されます。 変換が不要な場合は、要素を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
-
resolve
ClassFileTransform.ResolvedTransformPREVIEW<E> resolve(B builder) 変換をビルダーにバインドします。 変換が連鎖している場合、チェーン・リンクごとに中間ビルダーが作成されます。 変換がステートフル(see, e.g.,ClassTransform.ofStateful(Supplier)
PREVIEW)の場合、新しい変換オブジェクトを取得するためにサプライヤが呼び出されます。このメソッドは、ユーザー・コードではほとんど使用されない低レベルのメソッドです。ほとんどの場合、ユーザー・コードは、変換を解決し、現在のビルダーで実行する
ClassFileBuilder.transform(CompoundElement, ClassFileTransform)
PREVIEWを優先する必要があります。- パラメータ:
builder
- バインドするビルダー- 戻り値:
- バインドされた結果
-
ClassFileTransform
を使用できます。