Interface ClassFileTransform<C extends ClassFileTransformPREVIEW<C,E,B>,E extends ClassFileElementPREVIEW,B extends ClassFileBuilderPREVIEW<E,B>>
- Type Parameters:
C
- the transform typeE
- the element typeB
- the builder type
- All Known Subinterfaces:
ClassRemapperPREVIEW
,ClassTransformPREVIEW
,CodeLocalsShifterPREVIEW
,CodeRelabelerPREVIEW
,CodeStackTrackerPREVIEW
,CodeTransformPREVIEW
,FieldTransformPREVIEW
,MethodTransformPREVIEW
ClassFileTransform
is a preview API of the Java platform.
ClassFile.transformClass(ClassModel, ClassTransform)
PREVIEW, and the elements of the class,
along with a builder, are presented to the transform.
The subtypes of ClassFileTransform (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
RuntimeVisibleAnnotationsAttribute 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().transformClass(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().transformClass(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.REFERENCE, 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)))));
}
-
Method Summary
Modifier and TypeMethodDescriptionvoid
Transform an element by taking the appropriate actions on the builder.Chain this transform with another; elements presented to the builder of this transform will become the input to the next transform.default void
Take any final action during transformation of a classfile entity.default void
Take any preliminary action during transformation of a classfile entity.
-
Method Details
-
accept
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 toClassFileBuilder.with(ClassFileElement)
PREVIEW. If the element is to be dropped, no action is required.- Parameters:
builder
- the builder for the new entityelement
- the element
-
atEnd
Take any final action during transformation of a classfile entity. Called after all elements of the class are presented toaccept(ClassFileBuilder, ClassFileElement)
.- Implementation Requirements:
- The default implementation does nothing.
- Parameters:
builder
- the builder for the new entity
-
atStart
Take any preliminary action during transformation of a classfile entity. Called before any elements of the class are presented toaccept(ClassFileBuilder, ClassFileElement)
.- Implementation Requirements:
- The default implementation does nothing.
- Parameters:
builder
- the builder for the new entity
-
andThen
-
ClassFileTransform
when preview features are enabled.