Package java.lang.classfile.components
java.lang.classfile.components
is a preview API 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 ofClassRemapper
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
-
ClassDescriptionPreview.A printer of classfiles and its elements.Preview.A leaf node holding single printable value.Preview.A tree node holding
List
of nested nodes.Preview.A tree node holdingMap
of nested nodes.Preview.Named, traversable, and printable node parent.Preview.Level of detail to print or export.Preview.ClassRemapper
is aClassTransform
PREVIEW,FieldTransform
PREVIEW,MethodTransform
PREVIEW andCodeTransform
PREVIEW deeply re-mapping all class references in any form, according to given map or map function.Preview.CodeLocalsShifter
PREVIEW is aCodeTransform
PREVIEW shifting locals to newly allocated positions to avoid conflicts during code injection.Preview.A code relabeler is aCodeTransform
PREVIEW replacing all occurrences ofLabel
PREVIEW in the transformed code with new instances.Preview.CodeStackTracker
PREVIEW is aCodeTransform
PREVIEW tracking stack content and calculating max stack size.
java.lang.classfile.components
when preview features are enabled.