it works but somehow not
This commit is contained in:
parent
974165580f
commit
b48a51fc74
11 changed files with 272 additions and 1 deletions
|
|
@ -1,3 +1,4 @@
|
||||||
# Apples
|
# Apples
|
||||||
* universal compare tool
|
* universal compare tool
|
||||||
* compares any to any and shows the diff
|
* compares any to any and shows the diff
|
||||||
|
* no reflection
|
||||||
|
|
|
||||||
5
pom.xml
5
pom.xml
|
|
@ -15,6 +15,11 @@
|
||||||
<version>5.9.3</version>
|
<version>5.9.3</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ow2.asm</groupId>
|
||||||
|
<artifactId>asm-tree</artifactId>
|
||||||
|
<version>9.4</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
||||||
134
src/main/java/nl/sander/apples/AppleFactory.java
Normal file
134
src/main/java/nl/sander/apples/AppleFactory.java
Normal file
|
|
@ -0,0 +1,134 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassVisitor;
|
||||||
|
import org.objectweb.asm.MethodVisitor;
|
||||||
|
import org.objectweb.asm.tree.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import static org.objectweb.asm.Opcodes.*;
|
||||||
|
|
||||||
|
class AppleFactory extends ClassVisitor {
|
||||||
|
|
||||||
|
public static final String SUPER = javaName(BaseApple.class.getName());
|
||||||
|
|
||||||
|
public static final String INIT = "<init>";
|
||||||
|
public static final String ZERO_ARGS_VOID = "()V";
|
||||||
|
|
||||||
|
private boolean isRecord = false;
|
||||||
|
|
||||||
|
public AppleFactory() {
|
||||||
|
super(ASM9);
|
||||||
|
}
|
||||||
|
|
||||||
|
final ClassNode classNode = new ClassNode();
|
||||||
|
|
||||||
|
private String classToMap;
|
||||||
|
|
||||||
|
private MethodNode compareMethod;
|
||||||
|
|
||||||
|
private int localVarIndex = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
|
||||||
|
if (superName.equals("java/lang/Record")) {
|
||||||
|
isRecord = true;
|
||||||
|
}
|
||||||
|
this.classToMap = name;
|
||||||
|
classNode.name = "Apple" + UUID.randomUUID();
|
||||||
|
classNode.superName = SUPER;
|
||||||
|
classNode.version = V20;
|
||||||
|
classNode.access = ACC_PUBLIC;
|
||||||
|
MethodNode constructor = new MethodNode(ACC_PUBLIC, INIT, ZERO_ARGS_VOID, null, null);
|
||||||
|
constructor.instructions.add(new VarInsnNode(ALOAD, 0));
|
||||||
|
constructor.instructions.add(new MethodInsnNode(INVOKESPECIAL, SUPER, INIT, ZERO_ARGS_VOID));
|
||||||
|
constructor.instructions.add(new InsnNode(RETURN));
|
||||||
|
classNode.methods.add(constructor);
|
||||||
|
|
||||||
|
compareMethod = new MethodNode(ACC_PUBLIC,
|
||||||
|
"compare", "(Ljava/lang/Object;Ljava/lang/Object;)Lnl/sander/apples/Result;", null, null);
|
||||||
|
classNode.methods.add(compareMethod);
|
||||||
|
add(new VarInsnNode(ALOAD,0));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodVisitor visitMethod(int access, String methodname,
|
||||||
|
String desc, String signature, String[] exceptions) {
|
||||||
|
if (!hasArgs(desc) && access == Modifier.PUBLIC && isRecord ||
|
||||||
|
(methodname.startsWith("get") || (methodname.startsWith("is")) && desc.equals("()Z"))) {
|
||||||
|
int startIndex;
|
||||||
|
if (isRecord) {
|
||||||
|
startIndex = 0;
|
||||||
|
} else {
|
||||||
|
if (methodname.startsWith("is")) {
|
||||||
|
startIndex = 2;
|
||||||
|
} else {
|
||||||
|
startIndex = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
visitGetter(correctName(methodname, startIndex), getReturnType(desc));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitGetter(String getterMethodName, String returnType) {
|
||||||
|
add(new VarInsnNode(ALOAD, 1));
|
||||||
|
add(new TypeInsnNode(CHECKCAST, javaName(classToMap)));
|
||||||
|
add(new MethodInsnNode(INVOKEVIRTUAL, classToMap, getterMethodName, "()" + returnType));
|
||||||
|
|
||||||
|
add(new VarInsnNode(ALOAD, 2));
|
||||||
|
add(new TypeInsnNode(CHECKCAST, javaName(classToMap)));
|
||||||
|
add(new MethodInsnNode(INVOKEVIRTUAL, classToMap, getterMethodName, "()" + returnType));
|
||||||
|
|
||||||
|
add(new MethodInsnNode(INVOKESTATIC, "nl/sander/apples/Apples", "compare", "(Ljava/lang/Object;Ljava/lang/Object;)Lnl/sander/apples/Result;"));
|
||||||
|
add(new VarInsnNode(ASTORE, 3 + (localVarIndex++)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String correctName(String getterMethodName, int startIndex) {
|
||||||
|
String tmp = getterMethodName.substring(startIndex);
|
||||||
|
return tmp.substring(0, 1).toLowerCase() + tmp.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitEnd() {
|
||||||
|
if (localVarIndex < 6) {
|
||||||
|
add(new InsnNode(3 + localVarIndex));
|
||||||
|
} else {
|
||||||
|
add(new LdcInsnNode(localVarIndex));
|
||||||
|
}
|
||||||
|
add(new TypeInsnNode(ANEWARRAY, "nl/sander/apples/Result"));
|
||||||
|
|
||||||
|
for (int i = 0; i < localVarIndex; i++) {
|
||||||
|
add(new InsnNode(DUP));
|
||||||
|
if (i < 6) {
|
||||||
|
add(new InsnNode(3 + i));
|
||||||
|
} else {
|
||||||
|
add(new LdcInsnNode(i));
|
||||||
|
}
|
||||||
|
add(new VarInsnNode(ALOAD, 3 + i));
|
||||||
|
add(new InsnNode(AASTORE));
|
||||||
|
}
|
||||||
|
|
||||||
|
add(new MethodInsnNode(INVOKESTATIC, "nl/sander/apples/Result", "merge", "([Lnl/sander/apples/Result;)Lnl/sander/apples/Result;"));
|
||||||
|
add(new InsnNode(ARETURN));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void add(AbstractInsnNode ins) {
|
||||||
|
compareMethod.instructions.add(ins);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getReturnType(String desc) {
|
||||||
|
return desc.substring(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean hasArgs(String desc) {
|
||||||
|
return desc.charAt(1) != ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String javaName(String className) {
|
||||||
|
return className.replaceAll("\\.", "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
package nl.sander.apples;
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
import org.objectweb.asm.ClassReader;
|
||||||
|
import org.objectweb.asm.ClassWriter;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class Apples {
|
public class Apples {
|
||||||
private final static Map<Character, String> CHAR_ESCAPES = Map.of('\t', "\\t", '\b', "\\b", '\n', "\\n", '\r', "\\r", '\f', "\\f", '\\', "\\\\");
|
private final static Map<Character, String> CHAR_ESCAPES = Map.of('\t', "\\t", '\b', "\\b", '\n', "\\n", '\r', "\\r", '\f', "\\f", '\\', "\\\\");
|
||||||
|
|
||||||
|
private static final ByteClassLoader generatedClassesLoader = new ByteClassLoader();
|
||||||
|
|
||||||
public static Result compare(Object left, Object right) {
|
public static Result compare(Object left, Object right) {
|
||||||
if (left == null) {
|
if (left == null) {
|
||||||
return Result.from(right == null, "null != " + asString(right));
|
return Result.from(right == null, "null != " + asString(right));
|
||||||
|
|
@ -22,7 +28,27 @@ public class Apples {
|
||||||
return Result.unequal(asString(left) + " != " + asString(right));
|
return Result.unequal(asString(left) + " != " + asString(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result.SAME;
|
if (left instanceof String) {
|
||||||
|
return Result.from(left.equals(right), () -> asString(left) + " != " + asString(right));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ClassReader cr = new ClassReader(left.getClass().getName());
|
||||||
|
AppleFactory appleFactory = new AppleFactory();
|
||||||
|
cr.accept(appleFactory, ClassReader.SKIP_FRAMES);
|
||||||
|
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES );
|
||||||
|
|
||||||
|
appleFactory.classNode.accept(classWriter);
|
||||||
|
byte[] byteArray = classWriter.toByteArray();
|
||||||
|
|
||||||
|
try (FileOutputStream f = new FileOutputStream("B.class")) {
|
||||||
|
f.write(byteArray);
|
||||||
|
}
|
||||||
|
generatedClassesLoader.addClass(appleFactory.classNode.name, byteArray);
|
||||||
|
BaseApple apple = (BaseApple) generatedClassesLoader.loadClass(appleFactory.classNode.name).getConstructor().newInstance();
|
||||||
|
return apple.compare(left, right);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result compare(long left, long right) {
|
public static Result compare(long left, long right) {
|
||||||
|
|
|
||||||
6
src/main/java/nl/sander/apples/BaseApple.java
Normal file
6
src/main/java/nl/sander/apples/BaseApple.java
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
abstract class BaseApple<T> {
|
||||||
|
|
||||||
|
public abstract Result compare(T left, T right);
|
||||||
|
}
|
||||||
23
src/main/java/nl/sander/apples/ByteClassLoader.java
Normal file
23
src/main/java/nl/sander/apples/ByteClassLoader.java
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
class ByteClassLoader extends ClassLoader {
|
||||||
|
|
||||||
|
private final ConcurrentMap<String, Class<?>> classes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?> findClass(String name) throws ClassNotFoundException {
|
||||||
|
Class<?> instance = classes.get(name);
|
||||||
|
if (instance == null) {
|
||||||
|
throw new ClassNotFoundException(name);
|
||||||
|
}
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addClass(String name, byte[] bytecode) {
|
||||||
|
Class<?> classDef = defineClass(name, bytecode, 0, bytecode.length);
|
||||||
|
classes.put(name, classDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package nl.sander.apples;
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public record Result(boolean areEqual, List<String> diffs) {
|
public record Result(boolean areEqual, List<String> diffs) {
|
||||||
public static Result SAME = new Result(true, List.of());
|
public static Result SAME = new Result(true, List.of());
|
||||||
|
|
@ -13,8 +16,24 @@ public record Result(boolean areEqual, List<String> diffs) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Result from(boolean areEqual, Supplier<String> messageSupplier) {
|
||||||
|
if (!areEqual) {
|
||||||
|
return new Result(areEqual, List.of(messageSupplier.get()));
|
||||||
|
} else {
|
||||||
|
return SAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Result unequal(String message) {
|
public static Result unequal(String message) {
|
||||||
return from(false, message);
|
return from(false, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Result merge(Result... result) {
|
||||||
|
boolean areEqual = Arrays.stream(result).allMatch(r -> r.areEqual);
|
||||||
|
List<String> diffs = Arrays.stream(result)
|
||||||
|
.map(Result::diffs)
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
return new Result(areEqual, diffs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
src/test/java/nl/sander/apples/Plum.java
Normal file
4
src/test/java/nl/sander/apples/Plum.java
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
public record Plum(String core, String peel, boolean juicy, int number, float price, Storage storage) {
|
||||||
|
}
|
||||||
14
src/test/java/nl/sander/apples/PlumApple.java
Normal file
14
src/test/java/nl/sander/apples/PlumApple.java
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
public class PlumApple extends BaseApple<Plum> {
|
||||||
|
public Result compare(Plum left, Plum right) {
|
||||||
|
Result core = Apples.compare(left.core(), right.core());
|
||||||
|
Result peel = Apples.compare(left.peel(), right.peel());
|
||||||
|
Result juicy = Apples.compare(left.juicy(), right.juicy());
|
||||||
|
Result price = Apples.compare(left.price(), right.price());
|
||||||
|
Result number = Apples.compare(left.number(), right.number());
|
||||||
|
Result storage = Apples.compare(left.storage(), right.storage());
|
||||||
|
|
||||||
|
return Result.merge(core, peel, juicy, price, number, storage);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/test/java/nl/sander/apples/RecordsTest.java
Normal file
33
src/test/java/nl/sander/apples/RecordsTest.java
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
|
||||||
|
class RecordsTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testExample() {
|
||||||
|
Result comparison = new PlumApple().compare(new Plum("small", "red",true, 1, 1.0F,Storage.HIGH),
|
||||||
|
new Plum("large", "green",true, 1, 1.0F,Storage.HIGH));
|
||||||
|
|
||||||
|
assertFalse(comparison.areEqual());
|
||||||
|
assertFalse(comparison.diffs().isEmpty());
|
||||||
|
assertEquals(2, comparison.diffs().size());
|
||||||
|
assertEquals("\"small\" != \"large\"", comparison.diffs().get(0));
|
||||||
|
assertEquals("\"red\" != \"green\"", comparison.diffs().get(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testRecords() {
|
||||||
|
Result comparison = Apples.compare(new Plum("small", "red",true, 1, 1.0F,Storage.HIGH),
|
||||||
|
new Plum("large", "green",true, 1, 1.0F,Storage.HIGH));
|
||||||
|
|
||||||
|
assertFalse(comparison.areEqual());
|
||||||
|
assertFalse(comparison.diffs().isEmpty());
|
||||||
|
assertEquals(2, comparison.diffs().size());
|
||||||
|
assertEquals("\"small\" != \"large\"", comparison.diffs().get(0));
|
||||||
|
assertEquals("\"red\" != \"green\"", comparison.diffs().get(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/test/java/nl/sander/apples/Storage.java
Normal file
6
src/test/java/nl/sander/apples/Storage.java
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package nl.sander.apples;
|
||||||
|
|
||||||
|
public enum Storage {
|
||||||
|
HIGH,
|
||||||
|
LOW
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue