package io.trygvis.rules.acme; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import org.drools.core.common.DefaultFactHandle; import org.kie.api.KieBase; import org.kie.api.runtime.rule.FactHandle; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.List; import java.util.TreeMap; import java.util.function.Function; @SuppressWarnings("unchecked") public class AcmeIo { private final ObjectMapper mapper; public AcmeIo(KieBase kieBase) { var factory = new YAMLFactory(); factory.enable(YAMLGenerator.Feature.USE_NATIVE_TYPE_ID); factory.enable(YAMLGenerator.Feature.USE_NATIVE_OBJECT_ID); mapper = new ObjectMapper(factory); var typeFactory = TypeFactory.defaultInstance() .withClassLoader(new AcmeClassLoader(kieBase)); mapper.setTypeFactory(typeFactory); mapper.findAndRegisterModules(); } public List load(String file) throws IOException { var parser = mapper.getFactory().createParser(new File(file)); var objects = mapper.readValues(parser, AcmeObject.class).readAll(new ArrayList<>()); List items = new ArrayList<>(objects.size()); for (AcmeObject object : objects) { try { var type = mapper.getTypeFactory().findClass(object.type); var x = mapper.treeToValue(object.data, type); items.add(x); } catch (ClassNotFoundException e) { throw new IOException(e); } } return items; } public void dump(String s, Collection factHandles) throws IOException { dump(s, factHandles, (o) -> true); } // This should just sort by all getters instead. static class FactCollection { public final Class type; public final List values; public FactCollection(Class type) { this.type = type; this.values = new ArrayList<>(); } public void sort() { var comparator = comparable(type, "key"); if (comparator == null) { comparator = comparable(type, "name"); } if (comparator == null) { comparator = comparable(type, "fqdn"); } if (comparator == null) { comparator = Comparator.comparingInt(System::identityHashCode); } this.values.sort(comparator); } } private static > Comparator comparable(Class klass, String name) { try { var method = klass.getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1)); if (!method.isAccessible()) { if (!method.trySetAccessible()) return null; } return (a, b) -> { try { var x = (T) method.invoke(a); var y = (T) method.invoke(b); if (x == null && y == null) { return 0; } if (x == null) { return -1; } else if (y == null) { return 1; } return x.compareTo(y); } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException(e); } }; } catch (NoSuchMethodException ignored) { } try { var field = klass.getField(name); return (a, b) -> { try { var x = (T) field.get(a); var y = (T) field.get(b); return x.compareTo(y); } catch (IllegalAccessException e) { throw new RuntimeException(e); } }; } catch (NoSuchFieldException ignored) { } return null; } public void dump(String s, Collection factHandles, Function filter) throws IOException { var out = new File("out"); if (!out.isDirectory()) { if (!out.mkdirs()) { throw new IOException("Could not create directory: " + out); } } var facts = new TreeMap, FactCollection>(Comparator.comparing(Class::getName)); for (var handle : factHandles) { if (handle instanceof DefaultFactHandle h) { var obj = h.getObject(); if (!filter.apply(obj)) { continue; } Class type = obj.getClass(); var collection = facts.get(type); if (collection == null) { collection = new FactCollection(type); facts.put(type, collection); } collection.values.add(obj); } } var factory = mapper.getFactory(); try (var writer = new FileWriter(new File(out, s + ".yaml")); var g = factory.createGenerator(writer)) { for (var e : facts.entrySet()) { var name = e.getKey().getName(); var collection = e.getValue(); collection.sort(); for (var fact : collection.values) { g.writeObject(new AcmeObject(name, mapper.valueToTree(fact))); } } } } private static class AcmeClassLoader extends ClassLoader { private final KieBase kieBase; public AcmeClassLoader(KieBase kieBase) {this.kieBase = kieBase;} @Override public Class loadClass(String name) throws ClassNotFoundException { try { return super.loadClass(name); } catch (ClassNotFoundException e) { var i = name.lastIndexOf('.'); String pkg, klass; if (i == -1) { pkg = null; klass = name; } else { pkg = name.substring(0, i); klass = name.substring(i + 1); } var clazz = kieBase.getFactType(pkg, klass); if (clazz == null) { throw e; } return clazz.getFactClass(); } } } }