diff --git a/icon.svg b/icon.svg new file mode 100644 index 0000000..8432f3f --- /dev/null +++ b/icon.svg @@ -0,0 +1,232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + 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 toDescriptor = obj -> obj instanceof Class - ? toDesc((Class) obj) + ? getDesc((Class) obj) : (String) obj; - StringBuilder sb = new StringBuilder("("); + final var sb = new StringBuilder("("); Arrays.stream(params).map(toDescriptor).forEach(sb::append); return sb.append(")").append(toDescriptor.apply(ret)).toString(); } - static String generateUniqueMethodName(ClassNode node, String name) + public static String generateUniqueMethodName(ClassNode node, String name) { - return generateUnique(ComponentWriter::isUniqueMethodName, node, name); + return generateUnique(WriterUtils::isUniqueMethodName, node, name); } - static String generateUniqueFieldName(ClassNode node, String name) + public static String generateUniqueFieldName(ClassNode node, String name) { - return generateUnique(ComponentWriter::isUniqueFieldName, node, name); + return generateUnique(WriterUtils::isUniqueFieldName, node, name); } private static String generateUnique(BiPredicate unique, ClassNode node, String name) @@ -192,7 +189,7 @@ private static boolean isUniqueFieldName(ClassNode node, String name) return node.fields == null || !node.fields.stream().map(m -> m.name).anyMatch(name::equals); } - static void addConvertToPrimitive(Type type, InsnList insn, LocalVariableNode element) + public static void addConvertToPrimitive(Type type, InsnList insn, LocalVariableNode element) { final var desc = fDesc(element.desc); switch (type.getSort()) @@ -213,8 +210,13 @@ static void addConvertToPrimitive(Type type, InsnList insn, LocalVariableNode el private static void toPrimitive(Class clazz, String castMethod, InsnList instructions, String desc) { - String owner = toInternal(clazz); + final String owner = toInternal(clazz); instructions.add(new TypeInsnNode(CHECKCAST, owner)); instructions.add(new MethodInsnNode(INVOKEVIRTUAL, owner, castMethod, desc)); } + + public static boolean isStatic(int access) + { + return (access & ACC_STATIC) != 0; + } } \ No newline at end of file diff --git a/src/module-info.java b/src/module-info.java index f16325a..f0e5279 100644 --- a/src/module-info.java +++ b/src/module-info.java @@ -1,9 +1,13 @@ module kryptonbutterfly.CacheASM { - requires transitive kryptonbutterfly.Cache; - requires kryptonbutterfly.Collections; - requires kryptonbutterfly.Functional; - requires kryptonbutterfly.Monads; + exports kryptonbutterfly.asm.cache; + + requires transitive java.xml; + + requires maven.plugin.annotations; + requires maven.plugin.api; + + requires kryptonbutterfly.Cache; requires kryptonbutterfly.System; requires kryptonbutterfly.mathUtils; requires org.objectweb.asm;