Skip to content

Instantly share code, notes, and snippets.

@Quackster
Created December 1, 2025 11:52
Show Gist options
  • Select an option

  • Save Quackster/9229651054fb87e8b2badd0e89aac9b6 to your computer and use it in GitHub Desktop.

Select an option

Save Quackster/9229651054fb87e8b2badd0e89aac9b6 to your computer and use it in GitHub Desktop.
LambdaJsonConverter.java
package asm.predicate;
/**
* A library for converting Java lambda predicates to JSON and back using ASM bytecode analysis.
*
* Due to JVM limitations with dynamically generated lambda classes, this library works best with:
* 1. SerializablePredicate lambdas (recommended for runtime lambda conversion)
* 2. PredicateBuilder for programmatic predicate construction
* 3. Static method references that can be analyzed
*
* For regular Predicate lambdas, use SerializablePredicate instead.
*/
import org.objectweb.asm.*;
import org.objectweb.asm.tree.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
import java.lang.invoke.SerializedLambda;
import java.lang.reflect.Method;
import java.util.*;
import java.util.function.Predicate;
public class LambdaJsonConverter {
// JSON representation of a predicate expression
public static class PredicateJson {
// "comparison", "logical", "methodCall", "methodChain", "constant"
public String type;
public String operator;
public Object left;
public Object right;
public String method;
public String fieldName;
public Object value;
public List<MethodCall> methodChain; // For chained method calls
public PredicateJson() {}
public PredicateJson(String type) {
this.type = type;
}
// Represents a single method call in a chain
public static class MethodCall {
public String methodName;
public List<Object> arguments;
public MethodCall(String methodName) {
this.methodName = methodName;
this.arguments = new ArrayList<>();
}
public MethodCall(String methodName, List<Object> arguments) {
this.methodName = methodName;
this.arguments = arguments;
}
}
}
/**
* Converts any predicate to JSON string using ASM bytecode analysis.
* For best results, use SerializablePredicate instead of regular Predicate.
*/
public static <T> String toJson(Predicate<T> predicate) {
try {
// Check if it's a SerializablePredicate first
if (predicate instanceof SerializablePredicate) {
return toJsonFromSerializable((SerializablePredicate) predicate);
}
// Try ASM approach for non-serializable predicates
PredicateJson json = analyzeLambda(predicate);
return toJsonString(json);
} catch (Exception e) {
throw new RuntimeException("Failed to serialize lambda. " +
"For runtime lambdas, please use SerializablePredicate<T> instead of Predicate<T>. " +
"Example: SerializablePredicate<User> p = user -> user.getAge() > 18;", e);
}
}
/**
* Converts a serializable predicate to JSON using SerializedLambda approach
*/
private static <T> String toJsonFromSerializable(SerializablePredicate<T> predicate) throws Exception {
Method writeReplace = predicate.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
SerializedLambda lambda = (SerializedLambda) writeReplace.invoke(predicate);
// Get the class that contains the lambda implementation
String implClass = lambda.getImplClass().replace('/', '.');
Class<?> implClazz = Class.forName(implClass);
// Get the method name
String implMethodName = lambda.getImplMethodName();
// Load the bytecode of the implementation class
byte[] bytecode = loadClassBytesFromResource(implClazz);
// Parse with ASM
ClassReader reader = new ClassReader(bytecode);
ClassNode classNode = new ClassNode();
reader.accept(classNode, 0);
// Find the implementation method
MethodNode implMethod = null;
for (MethodNode method : classNode.methods) {
if (method.name.equals(implMethodName)) {
implMethod = method;
break;
}
}
if (implMethod == null) {
throw new RuntimeException("Could not find lambda implementation method: " + implMethodName);
}
PredicateJson json = analyzeMethodInstructions(implMethod, classNode);
return toJsonString(json);
}
/**
* Loads bytecode from a regular class file (not a lambda class)
*/
private static byte[] loadClassBytesFromResource(Class<?> clazz) throws IOException {
String className = clazz.getName().replace('.', '/') + ".class";
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
try (InputStream is = classLoader.getResourceAsStream(className)) {
if (is == null) {
throw new IOException("Could not find class file: " + className);
}
return readAllBytes(is);
}
}
/**
* Converts a serializable predicate to JSON string
*/
public static <T> String toJson(SerializablePredicate<T> predicate) {
return toJson((Predicate<T>) predicate);
}
/**
* Analyzes lambda bytecode using ASM to extract the predicate structure
*/
private static <T> PredicateJson analyzeLambda(Predicate<T> predicate) throws Exception {
if (!(predicate instanceof Serializable serializable)) {
throw new IllegalArgumentException("Predicate must be Serializable");
}
SerializedLambda sl = extractSerializedLambda(serializable);
String implClassInternalName = sl.getImplClass();
String implMethodName = sl.getImplMethodName();
String implMethodDesc = sl.getImplMethodSignature(); // important: method type, not signature
Pair<ClassNode, MethodNode> targetMethod = findMethod(implClassInternalName, implMethodName, implMethodDesc);
if (targetMethod.value1() == null || targetMethod.value2() == null) {
throw new RuntimeException("Could not find lambda implementation method");
}
return analyzeMethodInstructions(targetMethod.value2(), targetMethod.value1());
}
static SerializedLambda extractSerializedLambda(Serializable lambda) {
try {
Method writeReplace = lambda.getClass().getDeclaredMethod("writeReplace");
writeReplace.setAccessible(true);
Object replacement = writeReplace.invoke(lambda);
if (replacement instanceof SerializedLambda sl) {
return sl;
}
throw new IllegalStateException("Not a SerializedLambda: " + replacement);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Unable to extract SerializedLambda", e);
}
}
private static Pair<ClassNode, MethodNode> findMethod(String internalName, String methodName, String methodDesc) {
String resource = internalName + ".class";
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if (cl == null) {
cl = LambdaJsonConverter.class.getClassLoader();
}
try (InputStream in = cl.getResourceAsStream(resource)) {
if (in == null) {
throw new IllegalStateException("Cannot find class bytes: " + resource);
}
ClassReader cr = new ClassReader(in);
ClassNode cn = new ClassNode();
cr.accept(cn, 0);
for (MethodNode mn : cn.methods) {
if (mn.name.equals(methodName) && mn.desc.equals(methodDesc)) {
return new Pair<>(cn, mn);
}
}
throw new IllegalStateException("Method " + methodName + methodDesc + " not found in " + internalName);
} catch (IOException e) {
throw new RuntimeException("Failed to read class bytes for " + internalName, e);
}
}
/**
* Gets bytecode from the class loader using ASM's built-in mechanism
*/
private static byte[] getBytecodeFromClassLoader(Class<?> clazz) throws IOException {
try {
String className = clazz.getName().replace('.', '/');
ClassLoader classLoader = clazz.getClassLoader();
if (classLoader == null) {
classLoader = ClassLoader.getSystemClassLoader();
}
try (InputStream is = classLoader.getResourceAsStream(className + ".class")) {
if (is != null) {
return readAllBytes(is);
}
} catch (Exception e) {
// Ignore, will try reflection approach
}
try {
return getBytecodeViaReflection(clazz);
} catch (Exception e) {
throw new IOException("Could not load bytecode for lambda class: " + clazz.getName() +
". Lambda classes are dynamically generated and may require special handling.", e);
}
} catch (Exception e) {
throw new IOException("Failed to load class bytecode", e);
}
}
/**
* Attempts to get bytecode using reflection on internal JVM structures
*/
private static byte[] getBytecodeViaReflection(Class<?> clazz) throws Exception {
try {
ClassReader reader = new ClassReader(clazz.getName());
ClassWriter writer = new ClassWriter(reader, 0);
reader.accept(writer, 0);
return writer.toByteArray();
} catch (IOException e) {
throw new Exception("Cannot access lambda bytecode directly. " +
"Lambda must be created from a defined location or use SerializablePredicate.", e);
}
}
private static byte[] readAllBytes(InputStream is) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
return baos.toByteArray();
}
// =========================
// Enhanced method analysis
// =========================
/**
* Enhanced method instruction analysis to detect method chains and logical/comparison structure.
*/
private static PredicateJson analyzeMethodInstructions(MethodNode method, ClassNode classNode) {
InsnList instructions = method.instructions;
Stack<Object> stack = new Stack<>();
List<PredicateJson> conditions = new ArrayList<>();
String logicalOp = null;
for (int i = 0; i < instructions.size(); i++) {
AbstractInsnNode insn = instructions.get(i);
switch (insn.getOpcode()) {
case Opcodes.ALOAD:
stack.push("ref");
break;
case Opcodes.INVOKEVIRTUAL:
case Opcodes.INVOKEINTERFACE: {
MethodInsnNode methodInsn = (MethodInsnNode) insn;
// Collect arguments from stack
List<Object> args = new ArrayList<>();
int argCount = countMethodArguments(methodInsn.desc);
for (int j = 0; j < argCount; j++) {
if (!stack.isEmpty()) {
args.add(0, stack.pop()); // Insert at beginning to maintain order
}
}
// Check if this is part of a method chain
Object target = stack.isEmpty() ? null : stack.pop();
if (target instanceof PredicateJson &&
("methodCall".equals(((PredicateJson) target).type) ||
"methodChain".equals(((PredicateJson) target).type))) {
// This is a chained call
PredicateJson chain = (PredicateJson) target;
if (chain.methodChain == null) {
// Convert single method call to chain
chain.type = "methodChain";
chain.methodChain = new ArrayList<>();
PredicateJson.MethodCall firstCall = new PredicateJson.MethodCall(chain.method);
chain.methodChain.add(firstCall);
chain.method = null;
}
// Add the new method to the chain
chain.methodChain.add(new PredicateJson.MethodCall(methodInsn.name, args));
stack.push(chain);
} else {
// New method call (possibly the start of a chain)
PredicateJson methodCall = new PredicateJson("methodCall");
methodCall.method = methodInsn.name;
if (!args.isEmpty()) {
// Store arguments for methods like replace("a", "b")
methodCall.methodChain = new ArrayList<>();
methodCall.methodChain.add(new PredicateJson.MethodCall(methodInsn.name, args));
}
stack.push(methodCall);
}
break;
}
case Opcodes.LDC: {
LdcInsnNode ldcInsn = (LdcInsnNode) insn;
stack.push(ldcInsn.cst);
break;
}
case Opcodes.BIPUSH:
case Opcodes.SIPUSH: {
IntInsnNode intInsn = (IntInsnNode) insn;
stack.push(intInsn.operand);
break;
}
case Opcodes.ICONST_0:
case Opcodes.ICONST_1:
case Opcodes.ICONST_2:
case Opcodes.ICONST_3:
case Opcodes.ICONST_4:
case Opcodes.ICONST_5:
stack.push(insn.getOpcode() - Opcodes.ICONST_0);
break;
case Opcodes.IF_ICMPGT:
case Opcodes.IF_ICMPLE:
case Opcodes.IF_ICMPLT:
case Opcodes.IF_ICMPGE:
case Opcodes.IF_ICMPEQ:
case Opcodes.IF_ICMPNE:
if (stack.size() >= 2) {
PredicateJson comp = new PredicateJson("comparison");
Object right = stack.pop();
Object left = stack.pop();
comp.left = convertToPredicateJson(left);
comp.right = convertToPredicateJson(right);
comp.operator = getOperatorFromOpcode(insn.getOpcode());
conditions.add(comp);
JumpInsnNode jumpInsn = (JumpInsnNode) insn;
if (logicalOp == null) {
logicalOp = detectLogicalOperator(jumpInsn, i, instructions);
}
}
break;
case Opcodes.IFEQ:
case Opcodes.IFNE:
if (!stack.isEmpty()) {
Object top = stack.pop();
if (top instanceof PredicateJson) {
PredicateJson pred = (PredicateJson) top;
// This is a boolean-returning method (possibly chained)
if (isBooleanMethod(pred)) {
conditions.add(pred);
JumpInsnNode jumpInsn = (JumpInsnNode) insn;
if (logicalOp == null) {
logicalOp = detectLogicalOperator(jumpInsn, i, instructions);
}
}
}
}
break;
}
}
// Combine conditions if we found multiple
if (conditions.size() > 1) {
PredicateJson result = conditions.get(0);
for (int i = 1; i < conditions.size(); i++) {
PredicateJson logical = new PredicateJson("logical");
logical.operator = logicalOp != null ? logicalOp : "&&";
logical.left = result;
logical.right = conditions.get(i);
result = logical;
}
return result;
} else if (conditions.size() == 1) {
return conditions.get(0);
}
return !stack.isEmpty() && stack.peek() instanceof PredicateJson
? (PredicateJson) stack.peek()
: new PredicateJson("constant");
}
private static PredicateJson convertToPredicateJson(Object obj) {
if (obj instanceof PredicateJson) {
return (PredicateJson) obj;
} else {
PredicateJson constant = new PredicateJson("constant");
constant.value = obj;
return constant;
}
}
// Helper method to count method arguments from descriptor
private static int countMethodArguments(String descriptor) {
int count = 0;
int i = 0;
// Skip until '('
while (i < descriptor.length() && descriptor.charAt(i) != '(') {
i++;
}
if (i == descriptor.length()) {
return 0;
}
i++; // past '('
while (i < descriptor.length() && descriptor.charAt(i) != ')') {
char c = descriptor.charAt(i);
if (c == 'L') {
// Object type: L...;
count++;
i++;
while (i < descriptor.length() && descriptor.charAt(i) != ';') {
i++;
}
if (i < descriptor.length()) {
i++; // skip ';'
}
} else if (c == '[') {
// Array type: consume all '[' then the element type
i++;
// Next char will be type or 'L'
} else {
// Primitive or other single-char type
if ("ZBCSIJFD".indexOf(c) >= 0) {
count++;
}
i++;
}
}
return count;
}
// Enhanced boolean method check for PredicateJson
private static boolean isBooleanMethod(PredicateJson pred) {
if (pred == null) return false;
if ("methodChain".equals(pred.type) && pred.methodChain != null && !pred.methodChain.isEmpty()) {
// Check the last method in the chain
String lastMethod = pred.methodChain.get(pred.methodChain.size() - 1).methodName;
return isBooleanMethodName(lastMethod);
} else if ("methodCall".equals(pred.type)) {
return isBooleanMethodName(pred.method);
}
return false;
}
private static boolean isBooleanMethodName(String methodName) {
return methodName != null && (methodName.startsWith("is") ||
methodName.startsWith("has") ||
methodName.equals("startsWith") ||
methodName.equals("endsWith") ||
methodName.equals("contains") ||
methodName.equals("equals") ||
methodName.equals("matches"));
}
private static String detectLogicalOperator(JumpInsnNode jumpInsn, int currentIndex, InsnList instructions) {
// Simple heuristic: if jump target is close, likely AND (short-circuit)
// if jump target is far (to return), likely OR
int targetIndex = instructions.indexOf(jumpInsn.label);
int distance = targetIndex - currentIndex;
// This is a simplified heuristic - proper detection requires more analysis
return distance < 10 ? "&&" : "||";
}
private static String getOperatorFromOpcode(int opcode) {
switch (opcode) {
case Opcodes.IF_ICMPGT:
case Opcodes.IFGT:
return ">";
case Opcodes.IF_ICMPLT:
case Opcodes.IFLT:
return "<";
case Opcodes.IF_ICMPGE:
case Opcodes.IFGE:
return ">=";
case Opcodes.IF_ICMPLE:
case Opcodes.IFLE:
return "<=";
case Opcodes.IF_ICMPEQ:
case Opcodes.IFEQ:
return "==";
case Opcodes.IF_ICMPNE:
case Opcodes.IFNE:
return "!=";
default:
return "==";
}
}
// =========================
// JSON -> Predicate
// =========================
/**
* Converts a JSON string back to a working predicate
*/
@SuppressWarnings("unchecked")
public static <T> Predicate<T> fromJson(String json, Class<T> clazz) {
PredicateJson predicateJson = fromJsonString(json);
return buildPredicate(predicateJson, clazz);
}
private static <T> Predicate<T> buildPredicate(PredicateJson json, Class<T> clazz) {
return obj -> evaluateExpression(json, obj);
}
@SuppressWarnings("unchecked")
private static <T> boolean evaluateExpression(PredicateJson json, T obj) {
try {
if (json == null) {
return false;
}
switch (json.type) {
case "comparison": {
Object leftVal = resolveValue(json.left, obj);
Object rightVal = resolveValue(json.right, obj);
return compare(leftVal, rightVal, json.operator);
}
case "logical": {
boolean leftResult = evaluateExpression((PredicateJson) json.left, obj);
if ("&&".equals(json.operator)) {
return leftResult && evaluateExpression((PredicateJson) json.right, obj);
} else {
return leftResult || evaluateExpression((PredicateJson) json.right, obj);
}
}
case "methodCall":
case "methodChain": {
Object res = resolveValue(json, obj);
if (res instanceof Boolean) {
return (Boolean) res;
}
return false;
}
case "methodCallDeprecated": // not used anymore, but kept for safety
return evaluateMethodCall(json, obj);
case "constant":
if (json.value instanceof Boolean) {
return (Boolean) json.value;
}
return false;
default:
return false;
}
} catch (Exception e) {
throw new RuntimeException("Failed to evaluate expression", e);
}
}
@SuppressWarnings("unchecked")
private static <T> Object resolveValue(Object value, T obj) throws Exception {
if (value instanceof PredicateJson) {
PredicateJson json = (PredicateJson) value;
if ("methodChain".equals(json.type)) {
return evaluateMethodChain(json, obj);
} else if ("methodCall".equals(json.type)) {
if (json.methodChain != null && !json.methodChain.isEmpty()) {
return evaluateMethodChain(json, obj);
} else {
Method method = obj.getClass().getMethod(json.method);
return method.invoke(obj);
}
} else if ("constant".equals(json.type)) {
return json.value;
}
}
return value;
}
private static boolean evaluateMethodCall(PredicateJson json, Object obj) throws Exception {
Method method = obj.getClass().getMethod(json.method);
Object result = method.invoke(obj);
if (result instanceof Boolean) {
return (Boolean) result;
}
return false;
}
// Evaluate a chain of method calls
@SuppressWarnings("unchecked")
private static <T> Object evaluateMethodChain(PredicateJson json, T obj) throws Exception {
Object current = obj;
for (PredicateJson.MethodCall methodCall : json.methodChain) {
// Convert arguments if needed
Object[] args = new Object[methodCall.arguments.size()];
for (int i = 0; i < methodCall.arguments.size(); i++) {
Object arg = methodCall.arguments.get(i);
if (arg instanceof PredicateJson) {
args[i] = resolveValue(arg, obj);
} else {
args[i] = arg;
}
}
// Find and invoke the method
Method method = findMethod(current.getClass(), methodCall.methodName, args);
current = method.invoke(current, args);
}
return current;
}
// Find method by name and argument types
private static Method findMethod(Class<?> clazz, String methodName, Object[] args) throws NoSuchMethodException {
// Try exact match first
for (Method method : clazz.getMethods()) {
if (method.getName().equals(methodName) && method.getParameterCount() == args.length) {
boolean matches = true;
Class<?>[] paramTypes = method.getParameterTypes();
for (int i = 0; i < args.length; i++) {
if (args[i] != null &&
!paramTypes[i].isAssignableFrom(args[i].getClass()) &&
!isCompatiblePrimitive(paramTypes[i], args[i].getClass())) {
matches = false;
break;
}
}
if (matches) {
return method;
}
}
}
// Fallback: try to get method with no args if args array is empty
if (args.length == 0) {
return clazz.getMethod(methodName);
}
throw new NoSuchMethodException("Method " + methodName + " not found in " + clazz.getName());
}
private static boolean isCompatiblePrimitive(Class<?> paramType, Class<?> argType) {
if (paramType == int.class) return argType == Integer.class;
if (paramType == long.class) return argType == Long.class;
if (paramType == double.class) return argType == Double.class;
if (paramType == float.class) return argType == Float.class;
if (paramType == boolean.class) return argType == Boolean.class;
if (paramType == char.class) return argType == Character.class;
if (paramType == byte.class) return argType == Byte.class;
if (paramType == short.class) return argType == Short.class;
return false;
}
@SuppressWarnings("unchecked")
private static boolean compare(Object left, Object right, String operator) {
switch (operator) {
case ">":
return ((Comparable) left).compareTo(right) > 0;
case "<":
return ((Comparable) left).compareTo(right) < 0;
case ">=":
return ((Comparable) left).compareTo(right) >= 0;
case "<=":
return ((Comparable) left).compareTo(right) <= 0;
case "==":
return Objects.equals(left, right);
case "!=":
return !Objects.equals(left, right);
case "startsWith":
return left.toString().startsWith(right.toString());
case "endsWith":
return left.toString().endsWith(right.toString());
case "contains":
return left.toString().contains(right.toString());
default:
return false;
}
}
// =========================
// JSON (de)serialization
// =========================
// Enhanced JSON serialization to handle method chains
private static String toJsonString(PredicateJson json) {
StringBuilder sb = new StringBuilder();
sb.append("{");
sb.append("\"type\":\"").append(json.type).append("\"");
if (json.operator != null) {
sb.append(",\"operator\":\"").append(json.operator).append("\"");
}
if (json.method != null) {
sb.append(",\"method\":\"").append(escapeJson(json.method)).append("\"");
}
if (json.methodChain != null && !json.methodChain.isEmpty()) {
sb.append(",\"methodChain\":[");
for (int i = 0; i < json.methodChain.size(); i++) {
if (i > 0) sb.append(",");
PredicateJson.MethodCall mc = json.methodChain.get(i);
sb.append("{\"methodName\":\"").append(escapeJson(mc.methodName)).append("\"");
if (!mc.arguments.isEmpty()) {
sb.append(",\"arguments\":[");
for (int j = 0; j < mc.arguments.size(); j++) {
if (j > 0) sb.append(",");
Object arg = mc.arguments.get(j);
if (arg instanceof PredicateJson) {
sb.append(toJsonString((PredicateJson) arg));
} else {
sb.append(toJsonValue(arg));
}
}
sb.append("]");
}
sb.append("}");
}
sb.append("]");
}
if (json.value != null) {
sb.append(",\"value\":").append(toJsonValue(json.value));
}
if (json.left != null) {
sb.append(",\"left\":");
if (json.left instanceof PredicateJson) {
sb.append(toJsonString((PredicateJson) json.left));
} else {
sb.append(toJsonValue(json.left));
}
}
if (json.right != null) {
sb.append(",\"right\":");
if (json.right instanceof PredicateJson) {
sb.append(toJsonString((PredicateJson) json.right));
} else {
sb.append(toJsonValue(json.right));
}
}
sb.append("}");
return sb.toString();
}
private static String toJsonValue(Object value) {
if (value instanceof PredicateJson) {
return toJsonString((PredicateJson) value);
}
if (value instanceof String) {
return "\"" + escapeJson((String) value) + "\"";
}
return String.valueOf(value);
}
private static String escapeJson(String str) {
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
private static PredicateJson fromJsonString(String json) {
json = json.trim();
if (json.startsWith("{") && json.endsWith("}")) {
json = json.substring(1, json.length() - 1);
}
PredicateJson result = new PredicateJson();
String[] pairs = splitJsonPairs(json);
for (String pair : pairs) {
pair = pair.trim();
if (pair.isEmpty()) continue;
int colonIndex = pair.indexOf(':');
if (colonIndex == -1) continue;
String key = pair.substring(0, colonIndex).trim().replace("\"", "");
String value = pair.substring(colonIndex + 1).trim();
switch (key) {
case "type":
result.type = value.replace("\"", "");
break;
case "operator":
result.operator = value.replace("\"", "");
break;
case "method":
result.method = value.replace("\"", "");
break;
case "value":
result.value = parseJsonValue(value);
break;
case "left":
result.left = parseJsonValue(value);
break;
case "right":
result.right = parseJsonValue(value);
break;
case "methodChain":
result.methodChain = parseMethodChainArray(value);
break;
}
}
return result;
}
private static String[] splitJsonPairs(String json) {
List<String> pairs = new ArrayList<>();
int braceCount = 0;
int bracketCount = 0;
int start = 0;
boolean inString = false;
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '"' && (i == 0 || json.charAt(i - 1) != '\\')) {
inString = !inString;
}
if (!inString) {
if (c == '{') braceCount++;
else if (c == '}') braceCount--;
else if (c == '[') bracketCount++;
else if (c == ']') bracketCount--;
else if (c == ',' && braceCount == 0 && bracketCount == 0) {
pairs.add(json.substring(start, i));
start = i + 1;
}
}
}
if (start < json.length()) {
pairs.add(json.substring(start));
}
return pairs.toArray(new String[0]);
}
private static List<String> splitJsonElements(String json) {
List<String> elements = new ArrayList<>();
int braceCount = 0;
int bracketCount = 0;
int start = 0;
boolean inString = false;
for (int i = 0; i < json.length(); i++) {
char c = json.charAt(i);
if (c == '"' && (i == 0 || json.charAt(i - 1) != '\\')) {
inString = !inString;
}
if (!inString) {
if (c == '{') braceCount++;
else if (c == '}') braceCount--;
else if (c == '[') bracketCount++;
else if (c == ']') bracketCount--;
else if (c == ',' && braceCount == 0 && bracketCount == 0) {
elements.add(json.substring(start, i));
start = i + 1;
}
}
}
if (start < json.length()) {
elements.add(json.substring(start));
}
return elements;
}
private static Object parseJsonValue(String value) {
value = value.trim();
if (value.startsWith("{")) {
return fromJsonString(value);
} else if (value.startsWith("[")) {
return parseJsonArray(value);
} else if (value.startsWith("\"")) {
return value.substring(1, value.length() - 1)
.replace("\\\"", "\"")
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\\\", "\\");
} else if (value.equals("true")) {
return true;
} else if (value.equals("false")) {
return false;
} else {
try {
if (value.contains(".")) {
return Double.parseDouble(value);
}
return Integer.parseInt(value);
} catch (NumberFormatException e) {
return value;
}
}
}
private static List<Object> parseJsonArray(String value) {
value = value.trim();
List<Object> list = new ArrayList<>();
if (!value.startsWith("[") || !value.endsWith("]")) {
return list;
}
String content = value.substring(1, value.length() - 1).trim();
if (content.isEmpty()) {
return list;
}
List<String> elements = splitJsonElements(content);
for (String elem : elements) {
list.add(parseJsonValue(elem));
}
return list;
}
private static List<PredicateJson.MethodCall> parseMethodChainArray(String value) {
value = value.trim();
List<PredicateJson.MethodCall> chain = new ArrayList<>();
if (!value.startsWith("[") || !value.endsWith("]")) {
return chain;
}
String content = value.substring(1, value.length() - 1).trim();
if (content.isEmpty()) {
return chain;
}
List<String> elements = splitJsonElements(content);
for (String elem : elements) {
PredicateJson.MethodCall mc = parseMethodCallObject(elem.trim());
if (mc != null) {
chain.add(mc);
}
}
return chain;
}
private static PredicateJson.MethodCall parseMethodCallObject(String value) {
value = value.trim();
if (value.startsWith("{") && value.endsWith("}")) {
value = value.substring(1, value.length() - 1);
}
String methodName = null;
List<Object> args = new ArrayList<>();
String[] pairs = splitJsonPairs(value);
for (String pair : pairs) {
pair = pair.trim();
if (pair.isEmpty()) continue;
int colonIndex = pair.indexOf(':');
if (colonIndex == -1) continue;
String key = pair.substring(0, colonIndex).trim().replace("\"", "");
String val = pair.substring(colonIndex + 1).trim();
if ("methodName".equals(key)) {
methodName = val.substring(1, val.length() - 1)
.replace("\\\"", "\"")
.replace("\\n", "\n")
.replace("\\r", "\r")
.replace("\\t", "\t")
.replace("\\\\", "\\");
} else if ("arguments".equals(key)) {
args = parseJsonArray(val);
}
}
if (methodName == null) {
return null;
}
return new PredicateJson.MethodCall(methodName, args);
}
/**
* Serializable predicate interface for lambda conversion
*/
@FunctionalInterface
public interface SerializablePredicate<T> extends Predicate<T>, Serializable {}
/**
* Simple Pair implementation used internally
*/
public static class Pair<A, B> {
private final A v1;
private final B v2;
public Pair(A v1, B v2) {
this.v1 = v1;
this.v2 = v2;
}
public A value1() {
return v1;
}
public B value2() {
return v2;
}
}
/**
* Builder for creating predicates programmatically
*/
public static class PredicateBuilder<T> {
private PredicateJson root;
public PredicateBuilder() {
this.root = new PredicateJson();
}
public PredicateBuilder<T> comparison(String method, String operator, Object value) {
PredicateJson comp = new PredicateJson("comparison");
comp.operator = operator;
PredicateJson methodCall = new PredicateJson("methodCall");
methodCall.method = method;
comp.left = methodCall;
PredicateJson constant = new PredicateJson("constant");
constant.value = value;
comp.right = constant;
root = comp;
return this;
}
public PredicateBuilder<T> and(PredicateBuilder<T> other) {
PredicateJson logical = new PredicateJson("logical");
logical.operator = "&&";
logical.left = this.root;
logical.right = other.root;
this.root = logical;
return this;
}
public PredicateBuilder<T> or(PredicateBuilder<T> other) {
PredicateJson logical = new PredicateJson("logical");
logical.operator = "||";
logical.left = this.root;
logical.right = other.root;
this.root = logical;
return this;
}
public String toJson() {
return toJsonString(root);
}
public Predicate<T> build(Class<T> clazz) {
return buildPredicate(root, clazz);
}
}
// Example User class for testing
public static class User {
private final int age;
private final String email;
public User(int age, String email) {
this.age = age;
this.email = email;
}
public int getAge() {
return age;
}
public String getEmail() {
return email;
}
}
// Example usage with method chaining
public static void main(String[] args) {
System.out.println("=== Test 1: PredicateBuilder (simple comparison + AND) ===");
PredicateBuilder<User> agePredicate = new PredicateBuilder<User>()
.comparison("getAge", ">", 18);
PredicateBuilder<User> emailPredicate = new PredicateBuilder<User>()
.comparison("getEmail", "startsWith", "alex");
String jsonBuilder = agePredicate.and(emailPredicate).toJson();
System.out.println("JSON from builder: " + jsonBuilder);
Predicate<User> predicateFromBuilder = LambdaJsonConverter.fromJson(jsonBuilder, User.class);
User u1 = new User(25, "[email protected]");
User u2 = new User(16, "[email protected]");
User u3 = new User(25, "[email protected]");
System.out.println("User 1 (25, alex@...): " + predicateFromBuilder.test(u1)); // true
System.out.println("User 2 (16, alex@...): " + predicateFromBuilder.test(u2)); // false
System.out.println("User 3 (25, bob@...): " + predicateFromBuilder.test(u3)); // false
System.out.println("\n=== Test 2: Method Chaining (toLowerCase().startsWith(\"alex\")) ===");
SerializablePredicate<User> chainedPredicate = user ->
user.getEmail().toLowerCase().startsWith("alex");
String json1 = LambdaJsonConverter.toJson(chainedPredicate);
System.out.println("Chained lambda JSON: " + json1);
Predicate<User> reconstructed1 = LambdaJsonConverter.fromJson(json1, User.class);
User user1 = new User(25, "[email protected]");
User user2 = new User(30, "[email protected]");
System.out.println("User 1 (ALEX@...): " + reconstructed1.test(user1)); // true
System.out.println("User 2 (BOB@...): " + reconstructed1.test(user2)); // false
System.out.println("\n=== Test 3: Complex chain with replace ===");
SerializablePredicate<User> complexChain = user ->
user.getEmail().toLowerCase().replace("@", "_at_").startsWith("alex");
String json2 = LambdaJsonConverter.toJson(complexChain);
System.out.println("Complex chain JSON: " + json2);
Predicate<User> reconstructed2 = LambdaJsonConverter.fromJson(json2, User.class);
System.out.println("User 1 result: " + reconstructed2.test(user1)); // true / false depending on email
System.out.println("User 2 result: " + reconstructed2.test(user2));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment