diff --git a/ast/pom.xml b/ast/pom.xml
index af6f6cabea..8ab119a4e4 100644
--- a/ast/pom.xml
+++ b/ast/pom.xml
@@ -20,14 +20,6 @@
com.github.gumtreediff
core
-
- org.junit.jupiter
- junit-jupiter-api
-
-
- org.assertj
- assertj-core
-
diff --git a/canonicalize/pom.xml b/canonicalize/pom.xml
index 86af982020..c59de75ff5 100644
--- a/canonicalize/pom.xml
+++ b/canonicalize/pom.xml
@@ -15,4 +15,47 @@
UTF-8
+
+
+ info.picocli
+ picocli
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 22
+ --enable-preview
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+
+
+ canonicalize
+
+ shade
+
+ package
+
+ canonicalize
+ false
+
+
+
+ org.example.canonicalize.BytecodeTransformer
+
+
+
+
+
+
+
+
+
diff --git a/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformer.java b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformer.java
new file mode 100644
index 0000000000..061a1ff760
--- /dev/null
+++ b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformer.java
@@ -0,0 +1,35 @@
+package org.example.canonicalize;
+
+import static org.example.canonicalize.BytecodeTransformerCore.writeClassfile;
+
+import java.io.File;
+import java.util.concurrent.Callable;
+import picocli.CommandLine;
+
+@CommandLine.Command(
+ name = "bytecode-transformer",
+ mixinStandardHelpOptions = true,
+ description = "Transforms Java bytecode")
+public class BytecodeTransformer implements Callable {
+
+ @CommandLine.Parameters(index = "0", description = "The Java class file to transform")
+ private File javaClassfile;
+
+ @CommandLine.Option(
+ names = {"-o", "--output"},
+ description = "The output file to write the transformed bytecode",
+ required = true)
+ private File outputFile;
+
+ @Override
+ public Integer call() throws Exception {
+ BytecodeTransformerCore transformer = new BytecodeTransformerCore(javaClassfile);
+ writeClassfile(outputFile, transformer.getTransformedBytes());
+ return 0;
+ }
+
+ public static void main(String[] args) {
+ int exitCode = new CommandLine(new BytecodeTransformer()).execute(args);
+ System.exit(exitCode);
+ }
+}
diff --git a/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformerCore.java b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformerCore.java
new file mode 100644
index 0000000000..4e8d8101cc
--- /dev/null
+++ b/canonicalize/src/main/java/org/example/canonicalize/BytecodeTransformerCore.java
@@ -0,0 +1,33 @@
+package org.example.canonicalize;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.ClassFileBuilder;
+import java.lang.classfile.ClassModel;
+import java.nio.file.Files;
+
+public class BytecodeTransformerCore {
+ private File javaClassfile;
+
+ public BytecodeTransformerCore(File javaClassfile) {
+ this.javaClassfile = javaClassfile;
+ }
+
+ public byte[] getTransformedBytes() throws IOException {
+ ClassModel bytecodeModel = readClassFile(javaClassfile);
+ ClassModel transformedModel = ConstantPoolTransform.transform(bytecodeModel);
+
+ // implementation copied from java.lang.classfile.ClassTransform.ACCEPT_ALL
+ // ClassFileBuilder::with transformer does not modify the class file
+ return ClassFile.of().transform(transformedModel, ClassFileBuilder::with);
+ }
+
+ private static ClassModel readClassFile(File file) throws IOException {
+ return ClassFile.of().parse(Files.readAllBytes(file.toPath()));
+ }
+
+ public static void writeClassfile(File file, byte[] bytes) throws IOException {
+ Files.write(file.toPath(), bytes);
+ }
+}
diff --git a/canonicalize/src/main/java/org/example/canonicalize/ConstantPoolTransform.java b/canonicalize/src/main/java/org/example/canonicalize/ConstantPoolTransform.java
new file mode 100644
index 0000000000..08609bdb45
--- /dev/null
+++ b/canonicalize/src/main/java/org/example/canonicalize/ConstantPoolTransform.java
@@ -0,0 +1,122 @@
+package org.example.canonicalize;
+
+import java.lang.classfile.AccessFlags;
+import java.lang.classfile.ClassBuilder;
+import java.lang.classfile.ClassElement;
+import java.lang.classfile.ClassFile;
+import java.lang.classfile.ClassFileVersion;
+import java.lang.classfile.ClassModel;
+import java.lang.classfile.ClassTransform;
+import java.lang.classfile.CustomAttribute;
+import java.lang.classfile.FieldModel;
+import java.lang.classfile.Interfaces;
+import java.lang.classfile.MethodModel;
+import java.lang.classfile.Superclass;
+import java.lang.classfile.attribute.CompilationIDAttribute;
+import java.lang.classfile.attribute.DeprecatedAttribute;
+import java.lang.classfile.attribute.EnclosingMethodAttribute;
+import java.lang.classfile.attribute.InnerClassesAttribute;
+import java.lang.classfile.attribute.ModuleAttribute;
+import java.lang.classfile.attribute.ModuleHashesAttribute;
+import java.lang.classfile.attribute.ModuleMainClassAttribute;
+import java.lang.classfile.attribute.ModulePackagesAttribute;
+import java.lang.classfile.attribute.ModuleResolutionAttribute;
+import java.lang.classfile.attribute.ModuleTargetAttribute;
+import java.lang.classfile.attribute.NestHostAttribute;
+import java.lang.classfile.attribute.NestMembersAttribute;
+import java.lang.classfile.attribute.PermittedSubclassesAttribute;
+import java.lang.classfile.attribute.RecordAttribute;
+import java.lang.classfile.attribute.RuntimeInvisibleAnnotationsAttribute;
+import java.lang.classfile.attribute.RuntimeInvisibleTypeAnnotationsAttribute;
+import java.lang.classfile.attribute.RuntimeVisibleAnnotationsAttribute;
+import java.lang.classfile.attribute.RuntimeVisibleTypeAnnotationsAttribute;
+import java.lang.classfile.attribute.SignatureAttribute;
+import java.lang.classfile.attribute.SourceDebugExtensionAttribute;
+import java.lang.classfile.attribute.SourceFileAttribute;
+import java.lang.classfile.attribute.SourceIDAttribute;
+import java.lang.classfile.attribute.SyntheticAttribute;
+import java.lang.classfile.attribute.UnknownAttribute;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class ConstantPoolTransform {
+
+ private static List correctOrder = new ArrayList<>();
+ private static Map oldIndexToNewIndex = new HashMap<>();
+
+ public static ClassModel transform(ClassModel classModel) {
+ // this_class gets index 0 (1 in constant pool)
+ correctOrder.add(classModel.thisClass().index());
+ establishCorrectOrder(classModel);
+
+ return classModel;
+ }
+
+ private static void establishCorrectOrder(ClassModel classModel) {
+ ClassFile.of().transform(classModel, new ClassTransform() {
+
+ @Override
+ public void accept(ClassBuilder builder, ClassElement element) {
+ switch (element) {
+ case AccessFlags accessFlags -> {
+ // Access flags are not in the constant pool
+ }
+ case ClassFileVersion classFileVersion -> {
+ // Class file version is not in the constant pool
+ }
+ case CustomAttribute customAttribute -> {}
+ case FieldModel fieldModel -> {
+ correctOrder.add(fieldModel.fieldName().index());
+ correctOrder.add(fieldModel.fieldType().index());
+ }
+ case Interfaces interfaces -> {
+ correctOrder.addAll(interfaces.interfaces().stream()
+ .map(i -> i.index())
+ .collect(Collectors.toUnmodifiableList()));
+ }
+ case MethodModel methodModel -> {
+ correctOrder.add(methodModel.methodName().index());
+ correctOrder.add(methodModel.methodType().index());
+
+ // methodModel.code().ifPresent(code -> {
+ // correctOrder.add(code.elementList());
+ // });
+
+ }
+ case Superclass superclass -> {
+ correctOrder.add(superclass.superclassEntry().index());
+ }
+ case CompilationIDAttribute compilationIDAttribute -> {}
+ case DeprecatedAttribute deprecatedAttribute -> {}
+ case EnclosingMethodAttribute enclosingMethodAttribute -> {}
+ case InnerClassesAttribute innerClassesAttribute -> {}
+ case ModuleAttribute moduleAttribute -> {}
+ case ModuleHashesAttribute moduleHashesAttribute -> {}
+ case ModuleMainClassAttribute moduleMainClassAttribute -> {}
+ case ModulePackagesAttribute modulePackagesAttribute -> {}
+ case ModuleResolutionAttribute moduleResolutionAttribute -> {}
+ case ModuleTargetAttribute moduleTargetAttribute -> {}
+ case NestHostAttribute nestHostAttribute -> {}
+ case NestMembersAttribute nestMembersAttribute -> {}
+ case PermittedSubclassesAttribute permittedSubclassesAttribute -> {}
+ case RecordAttribute recordAttribute -> {}
+ case RuntimeInvisibleAnnotationsAttribute runtimeInvisibleAnnotationsAttribute -> {}
+ case RuntimeInvisibleTypeAnnotationsAttribute runtimeInvisibleTypeAnnotationsAttribute -> {}
+ case RuntimeVisibleAnnotationsAttribute runtimeVisibleAnnotationsAttribute -> {}
+ case RuntimeVisibleTypeAnnotationsAttribute runtimeVisibleTypeAnnotationsAttribute -> {}
+ case SignatureAttribute signatureAttribute -> {}
+ case SourceDebugExtensionAttribute sourceDebugExtensionAttribute -> {}
+ case SourceFileAttribute sourceFileAttribute -> {
+ correctOrder.add(sourceFileAttribute.sourceFile().index());
+ }
+ case SourceIDAttribute sourceIDAttribute -> {}
+ case SyntheticAttribute syntheticAttribute -> {}
+ case UnknownAttribute unknownAttribute -> {}
+ }
+ }
+ });
+ }
+}
diff --git a/canonicalize/src/test/java/org/example/canonicalize/ConstantPoolTransformerTest.java b/canonicalize/src/test/java/org/example/canonicalize/ConstantPoolTransformerTest.java
new file mode 100644
index 0000000000..1279c40ee6
--- /dev/null
+++ b/canonicalize/src/test/java/org/example/canonicalize/ConstantPoolTransformerTest.java
@@ -0,0 +1,19 @@
+package org.example.canonicalize;
+
+import static org.example.canonicalize.BytecodeTransformerCore.writeClassfile;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import org.junit.jupiter.api.Test;
+
+public class ConstantPoolTransformerTest {
+
+ private static final Path TEST_RESOURCE = Path.of("src/test/resources");
+
+ @Test
+ void test() throws IOException {
+ BytecodeTransformerCore transformer =
+ new BytecodeTransformerCore(TEST_RESOURCE.resolve("First.class").toFile());
+ writeClassfile(TEST_RESOURCE.resolve("First-transformed.class").toFile(), transformer.getTransformedBytes());
+ }
+}
diff --git a/canonicalize/src/test/resources/First.class b/canonicalize/src/test/resources/First.class
new file mode 100644
index 0000000000..a2fe7747eb
Binary files /dev/null and b/canonicalize/src/test/resources/First.class differ
diff --git a/pom.xml b/pom.xml
index 0a479ad47c..446202540d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,22 +25,29 @@
commons-csv
1.11.0
-
-
- org.junit.jupiter
- junit-jupiter-api
- 5.10.3
- test
-
- org.assertj
- assertj-core
- 3.26.3
- test
+ info.picocli
+ picocli
+ 4.7.6
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.10.3
+ test
+
+
+ org.assertj
+ assertj-core
+ 3.26.3
+ test
+
+
+