diff --git a/icon.svg b/icon.svg
new file mode 100644
index 0000000..8432f3f
--- /dev/null
+++ b/icon.svg
@@ -0,0 +1,232 @@
+
+
+
+
diff --git a/pom.xml b/pom.xml
index ed465bc..ec61e18 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,11 +1,15 @@
-
+
4.0.0
kryptonbutterfly
cache_builder
- 2.0.0
+ 3.0.0
CacheBuilder
- A java bytecode manipulator to decorate annotated functions with a specified cache
-
+ A java bytecode manipulator to decorate annotated functions
+ with a specified cache
+ maven-plugin
+
github
@@ -13,20 +17,21 @@
https://maven.pkg.github.com/kryptonbutterfly/maven-repo
-
+
+ 3.9.0
UTF-8
UTF-8
18
-
+
github
https://maven.pkg.github.com/kryptonbutterfly/maven-repo
-
+
kryptonbutterfly
@@ -43,10 +48,32 @@
asm-tree
9.6
+
+
+ org.apache.maven
+ maven-plugin-api
+ 3.6.3
+ provided
+
+
+
+ org.apache.maven.plugin-tools
+ maven-plugin-annotations
+ 3.9.0
+ provided
+
-
+
src
+
+
+ src
+
+ **/*.java
+
+
+
@@ -74,6 +101,19 @@
18
+
+ org.apache.maven.plugins
+ maven-plugin-plugin
+ ${maven-plugin-tools.version}
+
+
+ help-mojo
+
+ helpmojo
+
+
+
+
org.apache.maven.plugins
maven-jar-plugin
diff --git a/src/kryptonbutterfly/asm/cache/DecoratorCache.java b/src/kryptonbutterfly/asm/cache/DecoratorCache.java
index 7b150a0..8ae0dce 100644
--- a/src/kryptonbutterfly/asm/cache/DecoratorCache.java
+++ b/src/kryptonbutterfly/asm/cache/DecoratorCache.java
@@ -1,6 +1,6 @@
package kryptonbutterfly.asm.cache;
-import static kryptonbutterfly.asm.cache.ComponentWriter.*;
+import static kryptonbutterfly.asm.cache.misc.WriterUtils.*;
import static kryptonbutterfly.math.utils.range.Range.*;
import java.io.DataOutputStream;
@@ -8,19 +8,21 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.InputStream;
import java.lang.invoke.CallSite;
import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Handle;
@@ -41,12 +43,15 @@
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
-import kryptonbutterfly.asm.cache.ComponentWriter.CacheSpec;
+import kryptonbutterfly.asm.cache.misc.CacheSpec;
+import kryptonbutterfly.asm.cache.misc.CacheTarget;
+import kryptonbutterfly.asm.cache.misc.WriterUtils;
import kryptonbutterfly.cache.Cache;
import kryptonbutterfly.io.FileSystemUtils;
import kryptonbutterfly.monads.opt.Opt;
-public class DecoratorCache implements Opcodes
+@Mojo(name = "decorate", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
+public class DecoratorCache extends AbstractMojo implements Opcodes, WriterUtils
{
private static final String CACHE_CAP_CONST_DESC = fDesc("V", Function.class, "I");
private static final String CACHE_CONST_DESC = fDesc("V", Function.class);
@@ -59,21 +64,52 @@ public class DecoratorCache implements Opcodes
MethodHandle.class,
MethodType.class);
- public static void main(String[] args)
+ @Parameter(property = "project.builder.outputDirectory", defaultValue = "target/classes")
+ private String outputDirectory;
+
+ @Parameter(property = "classFileExtension", defaultValue = ".class")
+ private String classFileExtension;
+
+ private final String annotationDescriptor;
+
+ public DecoratorCache()
{
- final File binDir = args.length > 0 ? new File(args[0]) : new File(".");
-
- if (binDir.isDirectory())
- {
- new FileSystemUtils().performRecursive(
- binDir,
- f -> f.isFile() ? adapt(Cache.class, f) : true,
- Boolean::logicalAnd);
- }
- else
+ super();
+ this.annotationDescriptor = getDesc(Cache.class);
+ }
+
+ private DecoratorCache(String outputDirectory, String classFileExtension, Class> annotation)
+ {
+ super();
+ this.outputDirectory = outputDirectory;
+ this.classFileExtension = classFileExtension;
+ this.annotationDescriptor = getDesc(annotation);
+ }
+
+ public void execute()
+ {
+ final var outputDir = new File(outputDirectory);
+ if (!outputDir.exists())
{
- adapt(Cache.class, binDir);
+ getLog().warn(
+ "Unable to decorate any class files: The directory/file %s does not exists!"
+ .formatted(outputDirectory));
+ return;
}
+
+ if (outputDir.isDirectory()
+ ? new FileSystemUtils().performRecursive(
+ outputDir,
+ file -> file.isFile() ? adapt(file) : false,
+ Boolean::logicalOr)
+ : adapt(outputDir))
+ getLog().info("No files with extension %s decorated.".formatted(classFileExtension));
+ }
+
+ public static void main(String[] args)
+ {
+ final var outputDir = args.length > 0 ? args[0] : ".";
+ new DecoratorCache(outputDir, ".class", Cache.class).execute();
}
/**
@@ -83,78 +119,69 @@ public static void main(String[] args)
* the class reader
* @return the adapted Opt or Opt.empty() if nothing changed.
*/
- private static Opt adapt(Class> annotation, ClassReader reader)
+ private Opt adapt(ClassReader reader)
{
- final var annotationDesc = toDesc(annotation);
- final var classNode = new ClassNode();
+ final var classNode = new ClassNode();
reader.accept(classNode, 0);
- record CacheTarget(MethodNode method, AnnotationNode annotation)
- {}
-
final var toCache = new ArrayList();
- methods:
- for (final var method : classNode.methods)
- {
+ classNode.methods.forEach(method -> {
if (method.invisibleAnnotations != null)
{
- for (final var a : method.invisibleAnnotations)
+ for (final var annotation : method.invisibleAnnotations)
{
- if (a.desc.equals(annotationDesc))
+ if (annotation.desc.equals(annotationDescriptor))
{
- method.invisibleAnnotations.remove(a);
- toCache.add(new CacheTarget(method, a));
- continue methods;
+ method.invisibleAnnotations.remove(annotation);
+ toCache.add(new CacheTarget(method, annotation));
+ return;
}
}
}
if (method.visibleAnnotations != null)
{
- for (final var a : method.visibleAnnotations)
+ for (final var annotation : method.visibleAnnotations)
{
- if (a.desc.equals(annotationDesc))
+ if (annotation.desc.equals(annotationDescriptor))
{
- method.visibleAnnotations.remove(a);
- toCache.add(new CacheTarget(method, a));
- break;
+ method.visibleAnnotations.remove(annotation);
+ toCache.add(new CacheTarget(method, annotation));
+ return;
}
}
}
- }
+ });
+
toCache.forEach(target -> createCachedVersion(classNode, target.method(), target.annotation()));
return Opt.of(classNode).filter(_c -> !toCache.isEmpty());
}
- private static boolean adapt(Class> annotation, File classFile)
+ private boolean adapt(File classFile)
{
- if (!classFile.getName().endsWith(".class"))
+ if (!classFile.getName().endsWith(classFileExtension))
{
- System.out.printf("Skipping: Not a .class file: %s\n", classFile);
+ getLog().debug("Skipping: Not a '%s' file: %s".formatted(classFileExtension, classFile));
return false;
}
- try (InputStream iStream = new FileInputStream(classFile))
+
+ try (final var iStream = new FileInputStream(classFile))
{
- final ClassReader reader;
- try
- {
- reader = new ClassReader(iStream);
- }
- catch (IllegalArgumentException e)
- {
- System.err.printf("Unable to process %s\n%s", classFile, e.getMessage());
- return false;
- }
- return adapt(annotation, reader).filter(classNode -> compile(classFile, classNode)).isPresent();
+ final var reader = new ClassReader(iStream);
+ return adapt(reader).filter(classNode -> compile(classFile, classNode)).isPresent();
+ }
+ catch (IllegalArgumentException e)
+ {
+ getLog().error("Unable to process %s".formatted(classFile), e);
}
catch (IOException e)
{
- e.printStackTrace();
+ getLog().error(e);
}
return false;
}
- private static void createCachedVersion(ClassNode classNode, MethodNode original, AnnotationNode annotation)
+ private void createCachedVersion(ClassNode classNode, MethodNode original, AnnotationNode annotation)
{
try
{
@@ -162,11 +189,11 @@ private static void createCachedVersion(ClassNode classNode, MethodNode original
final var originalName = original.name;
original.name = generateUniqueMethodName(classNode, "ω" + original.name + "$");
- MethodNode addWrapped = generateWrapped(classNode, original, originalName);
+ final var addWrapped = generateWrapped(classNode, original, originalName);
- FieldNode cacheField = createCache(classNode, addWrapped, SPEC);
+ final var cacheField = createCache(classNode, addWrapped, SPEC);
- MethodNode cached = generateCached(
+ final var cached = generateCached(
classNode,
original,
cacheField,
@@ -179,7 +206,7 @@ private static void createCachedVersion(ClassNode classNode, MethodNode original
}
catch (ClassNotFoundException e)
{
- e.printStackTrace();
+ getLog().error(e);
}
}
@@ -203,7 +230,7 @@ private static void transferAnnotations(MethodNode origin, MethodNode target)
target.invisibleAnnotations = copyList(origin.invisibleAnnotations, target.invisibleAnnotations);
}
- private static MethodNode generateWrapped(ClassNode classNode, MethodNode original, String originalName)
+ private MethodNode generateWrapped(ClassNode classNode, MethodNode original, String originalName)
{
final int access = original.access & ~ACC_PUBLIC & ~ACC_PROTECTED | ACC_PRIVATE | ACC_FINAL;
final var returnType = Type.getReturnType(original.desc);
@@ -222,10 +249,10 @@ private static MethodNode generateWrapped(ClassNode classNode, MethodNode origin
LocalVariableNode inst = null;
if (!isStatic(original.access))
{
- inst = new LocalVariableNode("this", toDesc(classNode.name), null, start, end, index++);
+ inst = new LocalVariableNode("this", getDesc(classNode.name), null, start, end, index++);
wrapped.localVariables.add(inst);
}
- final var array = new LocalVariableNode("key", toDesc(Object[].class), null, start, end, index++);
+ final var array = new LocalVariableNode("key", getDesc(Object[].class), null, start, end, index++);
wrapped.localVariables.add(array);
// setup Local variables
@@ -285,10 +312,9 @@ private static MethodNode generateWrapped(ClassNode classNode, MethodNode origin
return wrapped;
}
- @SuppressWarnings("deprecation")
private static FieldNode createCache(ClassNode classNode, MethodNode function, CacheSpec SPEC)
{
- boolean isStatic = isStatic(function.access);
+ final boolean isStatic = isStatic(function.access);
// calculate field access modifiers
int access = ACC_PRIVATE | ACC_FINAL;
if (isStatic)
@@ -296,19 +322,19 @@ private static FieldNode createCache(ClassNode classNode, MethodNode function, C
access |= ACC_STATIC;
}
- String fieldName = ComponentWriter.generateUniqueFieldName(classNode, function.name);
- FieldNode field = new FieldNode(access, fieldName, SPEC.cacheType().getDescriptor(), null, null);
+ final String fieldName = WriterUtils.generateUniqueFieldName(classNode, function.name);
+ final FieldNode field = new FieldNode(access, fieldName, SPEC.cacheType().getDescriptor(), null, null);
classNode.fields.add(field);
- MethodNode init = getOrCreateInitializer(classNode, isStatic);
- var record = false;
- final var buffer = new ArrayList();
- for (Iterator i = init.instructions.iterator(); i.hasNext();)
+ final var init = getOrCreateInitializer(classNode, isStatic);
+ boolean isRecord = false;
+ final var buffer = new ArrayList();
+ for (final var i = init.instructions.iterator(); i.hasNext();)
{
final var node = i.next();
- if (record || node.getOpcode() == RETURN)
+ if (isRecord || node.getOpcode() == RETURN)
{
- record = true;
+ isRecord = true;
i.remove();
buffer.add(node);
}
@@ -317,7 +343,7 @@ record = true;
LocalVariableNode instance = null;
if (!isStatic)
{
- instance = findLocal(init, "this").get();
+ instance = findLocal(init, "this").get(() -> null);
init.instructions.add(new VarInsnNode(ALOAD, instance.index));
}
init.instructions.add(new TypeInsnNode(NEW, SPEC.cacheType().getInternalName()));
@@ -332,12 +358,13 @@ record = true;
init.instructions.add(
new InvokeDynamicInsnNode(
"apply",
- isStatic ? fDesc(Function.class) : fDesc(Function.class, toDesc(classNode.name)),
+ isStatic ? fDesc(Function.class) : fDesc(Function.class, getDesc(classNode.name)),
new Handle(H_INVOKESTATIC, toInternal(LambdaMetafactory.class), "metafactory", METAFACTORY_DESC, false),
Type.getMethodType(fDesc(Object.class, Object.class)),
new Handle(invokeTag, classNode.name, function.name, function.desc, false),
Type.getMethodType(function.desc)));
- String invokeDesc;
+
+ final String invokeDesc;
if (SPEC.capacity() > 0)
{
init.instructions.add(new LdcInsnNode(SPEC.capacity()));
@@ -347,6 +374,7 @@ record = true;
{
invokeDesc = CACHE_CONST_DESC;
}
+
init.instructions
.add(new MethodInsnNode(INVOKESPECIAL, SPEC.cacheType().getInternalName(), "", invokeDesc));
init.instructions
@@ -362,23 +390,23 @@ private static MethodNode generateCached(
String originalName,
String CACHE_TYPE)
{
- MethodNode cached = new MethodNode(original.access, originalName, original.desc, null, null);
+ final var cached = new MethodNode(original.access, originalName, original.desc, null, null);
classNode.methods.add(cached);
final var start = new LabelNode();
final var end = new LabelNode();
- var array = new LabelNode();
+ final var array = new LabelNode();
- var index = 0;
+ int index = 0;
LocalVariableNode inst = null;
if (!isStatic(original.access))
{
- inst = new LocalVariableNode("this", toDesc(classNode.name), null, start, end, index++);
+ inst = new LocalVariableNode("this", getDesc(classNode.name), null, start, end, index++);
cached.localVariables.add(inst);
}
// generate locals from original
- int offset = isStatic(original.access) ? 0 : -1;
- int argCount = Type.getArgumentTypes(original.desc).length;
+ final int offset = isStatic(original.access) ? 0 : -1;
+ final int argCount = Type.getArgumentTypes(original.desc).length;
final var params = new LocalVariableNode[argCount];
for (final var element : range(original.localVariables))
{
@@ -387,7 +415,7 @@ private static MethodNode generateCached(
continue;
}
final var loc = element.element();
- final var i = element.index() + offset;
+ final int i = element.index() + offset;
if (i == argCount)
{
break;
@@ -396,7 +424,7 @@ private static MethodNode generateCached(
cached.localVariables.add(params[i]);
}
- LocalVariableNode argArray = new LocalVariableNode("key", toDesc(Object[].class), null, array, end, index++);
+ final var argArray = new LocalVariableNode("key", getDesc(Object[].class), null, array, end, index++);
cached.localVariables.add(argArray);
cached.instructions.add(start);
@@ -406,12 +434,12 @@ private static MethodNode generateCached(
for (final var element : range(params))
{
final var loc = element.element();
- final var arrayIndex = element.index();
+ final int arrayIndex = element.index();
cached.instructions.add(new InsnNode(DUP));
- Type type = Type.getType(loc.desc);
+ final var type = Type.getType(loc.desc);
cached.instructions.add(new LdcInsnNode(arrayIndex));
cached.instructions.add(new VarInsnNode(type.getOpcode(ILOAD), loc.index));
- Consumer> toObject = (c) -> cached.instructions
+ final Consumer> toObject = (c) -> cached.instructions
.add(new MethodInsnNode(INVOKESTATIC, toInternal(c), "valueOf", fDesc(c, loc.desc)));
switch (type.getSort())
{
@@ -449,9 +477,9 @@ private static MethodNode generateCached(
cached.instructions
.add(new MethodInsnNode(INVOKEVIRTUAL, CACHE_TYPE, "get", fDesc(Object.class, Object.class)));
- Type returnType = Type.getReturnType(original.desc);
+ final var returnType = Type.getReturnType(original.desc);
- BiConsumer, String> toPrimitive = (clazz, methodName) -> {
+ final BiConsumer, String> toPrimitive = (clazz, methodName) -> {
cached.instructions.add(new TypeInsnNode(CHECKCAST, toInternal(clazz)));
cached.instructions.add(
new MethodInsnNode(
@@ -484,12 +512,12 @@ private static MethodNode generateCached(
private static boolean compile(File classFile, ClassNode classNode)
{
- ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
+ final var cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
classNode.accept(cw);
final var parentFolder = classFile.getParentFile();
if (parentFolder.exists() || parentFolder.mkdirs())
{
- try (DataOutputStream oStream = new DataOutputStream(new FileOutputStream(classFile)))
+ try (final var oStream = new DataOutputStream(new FileOutputStream(classFile)))
{
oStream.write(cw.toByteArray());
oStream.flush();
@@ -502,9 +530,4 @@ private static boolean compile(File classFile, ClassNode classNode)
}
return false;
}
-
- private static boolean isStatic(int access)
- {
- return (access & ACC_STATIC) != 0;
- }
}
\ No newline at end of file
diff --git a/src/kryptonbutterfly/asm/cache/misc/CacheSpec.java b/src/kryptonbutterfly/asm/cache/misc/CacheSpec.java
new file mode 100644
index 0000000..053a106
--- /dev/null
+++ b/src/kryptonbutterfly/asm/cache/misc/CacheSpec.java
@@ -0,0 +1,6 @@
+package kryptonbutterfly.asm.cache.misc;
+
+import org.objectweb.asm.Type;
+
+public record CacheSpec(Type cacheType, int capacity)
+{}
\ No newline at end of file
diff --git a/src/kryptonbutterfly/asm/cache/misc/CacheTarget.java b/src/kryptonbutterfly/asm/cache/misc/CacheTarget.java
new file mode 100644
index 0000000..a27c0da
--- /dev/null
+++ b/src/kryptonbutterfly/asm/cache/misc/CacheTarget.java
@@ -0,0 +1,7 @@
+package kryptonbutterfly.asm.cache.misc;
+
+import org.objectweb.asm.tree.AnnotationNode;
+import org.objectweb.asm.tree.MethodNode;
+
+public record CacheTarget(MethodNode method, AnnotationNode annotation)
+{}
diff --git a/src/kryptonbutterfly/asm/cache/ComponentWriter.java b/src/kryptonbutterfly/asm/cache/misc/WriterUtils.java
similarity index 80%
rename from src/kryptonbutterfly/asm/cache/ComponentWriter.java
rename to src/kryptonbutterfly/asm/cache/misc/WriterUtils.java
index 1b82120..be68a46 100644
--- a/src/kryptonbutterfly/asm/cache/ComponentWriter.java
+++ b/src/kryptonbutterfly/asm/cache/misc/WriterUtils.java
@@ -1,4 +1,4 @@
-package kryptonbutterfly.asm.cache;
+package kryptonbutterfly.asm.cache.misc;
import static kryptonbutterfly.math.utils.range.Range.*;
@@ -24,19 +24,16 @@
import kryptonbutterfly.cache.CacheConstants;
import kryptonbutterfly.monads.opt.Opt;
-public class ComponentWriter implements Opcodes
+public interface WriterUtils extends Opcodes
{
- static record CacheSpec(Type cacheType, int capacity)
- {}
-
- static CacheSpec getCacheSpecFromAnnotation(AnnotationNode node) throws ClassNotFoundException
+ public static CacheSpec getCacheSpecFromAnnotation(AnnotationNode node) throws ClassNotFoundException
{
final var cacheField = "cache";
final var capacityField = "capacity";
var cacheType = Type.getType(CacheConstants.DEFAULT_CACHE);
int capacity = CacheConstants.DEFAULT_CAPACITY;
- for (int index : range(0, node.values.size(), 2))
+ for (final int index : range(0, node.values.size(), 2))
{
final var name = node.values.get(index);
final var value = node.values.get(index + 1);
@@ -62,7 +59,7 @@ else if (capacityField.equals(name) && value instanceof Integer)
return new CacheSpec(cacheType, capacity);
}
- static MethodNode getOrCreateInitializer(ClassNode node, boolean isStatic)
+ public static MethodNode getOrCreateInitializer(ClassNode node, boolean isStatic)
{
final var INIT = isStatic ? "" : "";
final var matches = findMethod(node, INIT);
@@ -71,11 +68,11 @@ static MethodNode getOrCreateInitializer(ClassNode node, boolean isStatic)
: node.access & ACC_PUBLIC | node.access & ACC_PROTECTED | node.access & ACC_PRIVATE;
return Opt.of(matches).filter(m -> !m.isEmpty()).map(m -> m.get(0)).get(() -> {
- MethodNode init = new MethodNode((isStatic ? ACC_STATIC : 0) | visibility, INIT, "()V", null, null);
+ final var init = new MethodNode((isStatic ? ACC_STATIC : 0) | visibility, INIT, "()V", null, null);
node.methods.add(0, init);
if (!isStatic)
{
- LocalVariableNode instance = new LocalVariableNode(
+ final var instance = new LocalVariableNode(
"this",
node.signature,
null,
@@ -107,7 +104,7 @@ private static ArrayList findMethod(ClassNode node, String methodNam
.get(ArrayList::new);
}
- static Opt findLocal(MethodNode node, String name)
+ public static Opt findLocal(MethodNode node, String name)
{
return Opt.of(node.localVariables)
.map(
@@ -117,12 +114,12 @@ static Opt findLocal(MethodNode node, String name)
.flatmap(Opt::convert);
}
- static String toDesc(Class> clazz)
+ public static String getDesc(Class> clazz)
{
- return toDesc(toInternal(clazz));
+ return getDesc(toInternal(clazz));
}
- static String toDesc(String name)
+ public static String getDesc(String name)
{
final var sb = new StringBuilder();
while (name.endsWith("[]"))
@@ -133,7 +130,7 @@ static String toDesc(String name)
return "%sL%s;".formatted(sb, name);
}
- static String toInternal(Class> clazz)
+ public static String toInternal(Class> clazz)
{
final var name = new StringBuilder(clazz.getPackageName()).append(".");
var innerHost = clazz;
@@ -151,25 +148,25 @@ private static String toInternal(String clazz)
return clazz.replace('.', '/');
}
- static String fDesc(Object ret, Object... params)
+ public static String fDesc(Object ret, Object... params)
{
final Function