diff --git a/src/main/java/com/askimed/nf/test/lang/channels/ChannelItemComparator.java b/src/main/java/com/askimed/nf/test/lang/channels/ChannelItemComparator.java new file mode 100644 index 00000000..386a0ab4 --- /dev/null +++ b/src/main/java/com/askimed/nf/test/lang/channels/ChannelItemComparator.java @@ -0,0 +1,96 @@ +package com.askimed.nf.test.lang.channels; + +import java.io.File; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.Map; + +import com.askimed.nf.test.util.AnsiColors; + +public class ChannelItemComparator implements Comparator { + + @Override + @SuppressWarnings("unchecked") + public int compare(Object o1, Object o2) { + + if (o1 instanceof ArrayList) { + ArrayList tuple1 = (ArrayList) o1; + ArrayList tuple2 = (ArrayList) o2; + for (int i = 0; i < tuple1.size(); i++) { + Object a = tuple1.get(i); + Object b = tuple2.get(i); + + int result = compareObjects(a, b); + + if (result != 0) { + return result; + } + + } + } else { + return compareObjects(o1, o2); + } + + return 0; + } + + @SuppressWarnings("rawtypes") + public int compareObjects(Object a, Object b) { + + if (a.getClass() != b.getClass()) { + System.err.println(AnsiColors + .yellow("\nWarning: Cannot sort channel, order not deterministic. Objects are different types: " + + a.getClass() + " vs. " + b.getClass())); + return 1; + } + + if (a instanceof String) { + if (isPath(a)) { + return comparePaths(a.toString(), b.toString()); + } else { + return compareStrings(a.toString(), b.toString()); + } + } else if (isNumber(a)) { + return compareNumbers((Comparable) a, (Comparable) b); + } else if (a instanceof Map) { + return compareMaps((Map) a, (Map) b); + } else { + System.err.println(AnsiColors + .yellow("\nWarning: Cannot sort channel, order not deterministic. Unsupported objects types: " + + a.getClass() + " vs. " + b.getClass())); + return 1; + + } + + } + + private boolean isNumber(Object a) { + return a instanceof Integer || a instanceof Double || a instanceof Float; + } + + private boolean isPath(Object a) { + return a.toString().startsWith("/"); + } + + @SuppressWarnings({ "rawtypes" }) + private int compareMaps(Map a, Map b) { + // since we converted all nested maps to treemaps, toString returns keys sorted in deterministic order. + return compareStrings(a.toString(), b.toString()); + } + + public int comparePaths(String a, String b) { + String name1 = new File(a).getName(); + String name2 = new File(b).getName(); + return name1.compareTo(name2); + } + + public int compareStrings(String a, String b) { + return a.compareTo(b); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public int compareNumbers(Comparable a, Comparable b) { + return a.compareTo(b); + } + +} diff --git a/src/main/java/com/askimed/nf/test/lang/channels/Channels.java b/src/main/java/com/askimed/nf/test/lang/channels/Channels.java new file mode 100644 index 00000000..9453c380 --- /dev/null +++ b/src/main/java/com/askimed/nf/test/lang/channels/Channels.java @@ -0,0 +1,56 @@ +package com.askimed.nf.test.lang.channels; + +import java.io.File; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import com.askimed.nf.test.util.AnsiText; +import com.askimed.nf.test.util.MapUtil; + +import groovy.json.JsonOutput; +import groovy.json.JsonSlurper; + +public class Channels extends TreeMap { + + private static final String OUTPUT_CHANNEL_PREFIX = "output_"; + + private static final long serialVersionUID = 1L; + + public void loadFromFolder(File folder, boolean autoSort) { + for (File file : folder.listFiles()) { + if (file.getName().startsWith(OUTPUT_CHANNEL_PREFIX)) { + loadFromFile(file, autoSort); + } + } + } + + @SuppressWarnings("unchecked") + public void loadFromFile(File file, boolean autoSort) { + JsonSlurper jsonSlurper = new JsonSlurper(); + Map map = (Map) jsonSlurper.parse(file); + //convert map to treemap to sort keys of nested objects + map = MapUtil.convertToTreeMap(map); + if (autoSort) { + for (Object key : map.keySet()) { + Object value = map.get(key); + List channel = (List) value; + sortChannel(channel); + } + } + putAll(map); + } + + public void view() { + System.out.println(AnsiText.padding(JsonOutput.prettyPrint(JsonOutput.toJson(this)), 4)); + } + + public void sortChannel(List channel) { + channel.sort(new ChannelItemComparator()); + } + + @Override + public Object get(Object key) { + return super.get(key.toString()); + } +} diff --git a/src/main/java/com/askimed/nf/test/lang/process/ChannelsOutput.java b/src/main/java/com/askimed/nf/test/lang/process/ChannelsOutput.java deleted file mode 100644 index 5f90845a..00000000 --- a/src/main/java/com/askimed/nf/test/lang/process/ChannelsOutput.java +++ /dev/null @@ -1,127 +0,0 @@ -package com.askimed.nf.test.lang.process; - -import java.io.File; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import com.askimed.nf.test.util.AnsiText; -import com.askimed.nf.test.util.AnsiColors; - -import groovy.json.JsonSlurper; - -public class ChannelsOutput extends TreeMap { - - private static final long serialVersionUID = 1L; - - public void loadFromFolder(File folder, boolean autoSort) { - for (File file : folder.listFiles()) { - if (file.getName().startsWith("output_")) { - Map channel = loadFromFile(file, autoSort); - putAll(channel); - } - } - } - - public Map loadFromFile(File file, boolean autoSort) { - JsonSlurper jsonSlurper = new JsonSlurper(); - Map map = (Map) jsonSlurper.parse(file); - if (autoSort) { - for (Object key : map.keySet()) { - Object value = map.get(key); - List channel = (List) value; - sortChannel(channel); - } - } - return map; - } - - public void view() { - System.out - .println(AnsiText.padding(groovy.json.JsonOutput.prettyPrint(groovy.json.JsonOutput.toJson(this)), 4)); - } - - public void sortChannel(List channel) { - channel.sort(new Comparator() { - - @Override - public int compare(Object o1, Object o2) { - - if (o1 instanceof ArrayList) { - - ArrayList tuple1 = (ArrayList) o1; - ArrayList tuple2 = (ArrayList) o2; - for (int i = 0; i < tuple1.size(); i++) { - Object a = tuple1.get(i); - Object b = tuple2.get(i); - - int result = compareObjects(a, b); - - if (result != 0) { - return result; - } - - } - } else { - - return compareObjects(o1, o2); - - } - - return 0; - } - - }); - } - - public int compareObjects(Object a, Object b) { - - // TODO: check classA == classB - - if (a.getClass() != b.getClass()) { - System.err.println(AnsiColors.yellow( - "\nWarning: Cannot sort channel, order not deterministic. Objects are different types: " - + a.getClass() + " vs. " + b.getClass() - )); - return 1; - } - - if (a instanceof String) { - if (a.toString().startsWith("/")) { - return comparePaths(a.toString(), b.toString()); - } else { - return compareStrings(a.toString(), b.toString()); - } - } else if (a instanceof Integer || a instanceof Double || a instanceof Float) { - return compareNumbers((Comparable) a, (Comparable) b); - } else { - System.err.println(AnsiColors.yellow( - "\nWarning: Cannot sort channel, order not deterministic. Unsupported objects types: " - + a.getClass() + " vs. " + b.getClass() - )); - return 1; - } - - } - - public int comparePaths(String a, String b) { - String name1 = new File(a).getName(); - String name2 = new File(b).getName(); - return name1.compareTo(name2); - } - - public int compareStrings(String a, String b) { - return a.compareTo(b); - } - - public int compareNumbers(Comparable a, Comparable b) { - return a.compareTo(b); - } - - @Override - public Object get(Object key) { - return super.get(key.toString()); - } -} diff --git a/src/main/java/com/askimed/nf/test/lang/process/Process.java b/src/main/java/com/askimed/nf/test/lang/process/Process.java index f4422980..b0783179 100644 --- a/src/main/java/com/askimed/nf/test/lang/process/Process.java +++ b/src/main/java/com/askimed/nf/test/lang/process/Process.java @@ -1,10 +1,11 @@ package com.askimed.nf.test.lang.process; import com.askimed.nf.test.lang.WorkflowMeta; +import com.askimed.nf.test.lang.channels.Channels; public class Process extends WorkflowMeta{ - private ChannelsOutput out = new ChannelsOutput(); + private Channels out = new Channels(); private String mapping = ""; @@ -22,7 +23,7 @@ public String getMapping() { return mapping; } - public ChannelsOutput getOut() { + public Channels getOut() { return out; } diff --git a/src/main/java/com/askimed/nf/test/lang/workflow/Workflow.java b/src/main/java/com/askimed/nf/test/lang/workflow/Workflow.java index 469f9cce..82b1ae06 100644 --- a/src/main/java/com/askimed/nf/test/lang/workflow/Workflow.java +++ b/src/main/java/com/askimed/nf/test/lang/workflow/Workflow.java @@ -3,7 +3,7 @@ import java.beans.Transient; import com.askimed.nf.test.lang.WorkflowMeta; -import com.askimed.nf.test.lang.process.ChannelsOutput; +import com.askimed.nf.test.lang.channels.Channels; public class Workflow extends WorkflowMeta { @@ -11,7 +11,7 @@ public class Workflow extends WorkflowMeta { private String name = "workflow"; - private ChannelsOutput out = new ChannelsOutput(); + private Channels out = new Channels(); public void setName(String name) { this.name = name; @@ -26,7 +26,7 @@ public String getMapping() { return mapping; } - public ChannelsOutput getOut() { + public Channels getOut() { return out; } diff --git a/src/main/java/com/askimed/nf/test/util/MapUtil.java b/src/main/java/com/askimed/nf/test/util/MapUtil.java new file mode 100644 index 00000000..e47a66bc --- /dev/null +++ b/src/main/java/com/askimed/nf/test/util/MapUtil.java @@ -0,0 +1,46 @@ +package com.askimed.nf.test.util; + +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.Vector; + +public class MapUtil { + + public static TreeMap convertToTreeMap(Map hashMap) { + TreeMap treeMap = new TreeMap<>(); + convertToTreeMap(hashMap, treeMap); + return treeMap; + } + + @SuppressWarnings("unchecked") + private static void convertToTreeMap(Map sourceMap, TreeMap targetMap) { + for (Map.Entry entry : sourceMap.entrySet()) { + String currentKey = entry.getKey(); + + if (entry.getValue() instanceof Map) { + // nested map + TreeMap nestedTreeMap = new TreeMap<>(); + targetMap.put(currentKey, nestedTreeMap); + convertToTreeMap((Map) entry.getValue(), nestedTreeMap); + } else if (entry.getValue() instanceof List) { + // and array + List targetList = new Vector(); + for (Object item : (List) entry.getValue()) { + // nested map in array + if (item instanceof Map) { + TreeMap treeMap = new TreeMap<>(); + convertToTreeMap((Map) item, treeMap); + targetList.add(treeMap); + } else { + targetList.add(item); + } + } + targetMap.put(currentKey, targetList); + } else { + targetMap.put(currentKey, entry.getValue()); + } + } + } + +} diff --git a/src/test/java/com/askimed/nf/test/lang/channels/ChannelsOutputTest.java b/src/test/java/com/askimed/nf/test/lang/channels/ChannelsOutputTest.java new file mode 100644 index 00000000..e0de17f1 --- /dev/null +++ b/src/test/java/com/askimed/nf/test/lang/channels/ChannelsOutputTest.java @@ -0,0 +1,146 @@ +package com.askimed.nf.test.lang.channels; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import com.askimed.nf.test.lang.channels.Channels; + +public class ChannelsOutputTest { + + @Test + public void testIntegerChannel() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/integer.json"), false); + assertTrue(out.containsKey("outputCh")); + + List expected = Arrays.asList(9, 8, 7, 6, 5, 4, 3, 2, 1); + assertEquals(expected, (List) out.get("outputCh")); + } + + @Test + public void testIntegerChannelAutoSort() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/integer.json"), true); + assertTrue(out.containsKey("outputCh")); + + List expected = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); + assertEquals(expected, (List) out.get("outputCh")); + } + + @Test + public void testStringChannel() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/string.json"), false); + assertTrue(out.containsKey("outputCh")); + + List expected = Arrays.asList("z", "y", "x", "c", "b", "a"); + assertEquals(expected, (List) out.get("outputCh")); + } + + @Test + public void testStringChannelAutoSort() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/string.json"), true); + assertTrue(out.containsKey("outputCh")); + + List expected = Arrays.asList("a","b","c","x","y","z"); + assertEquals(expected, (List) out.get("outputCh")); + } + + @Test + public void testTuples() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/tuples.json"), true); + assertTrue(out.containsKey("outputCh")); + + // List expected = Arrays.asList(9, 8, 7, 6, 5, 4, 3, 2, 1); + // assertEquals(expected, (List) out.get("outputCh")); + } + + @Test + public void testNestedObjects() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/nested-objects.json"), true); + assertTrue(out.containsKey("outputCh")); + + Channels out2 = new Channels(); + out2.loadFromFile(new File("test-data/channels/nested-objects-random.json"), true); + assertTrue(out2.containsKey("outputCh")); + + assertEquals((List) out.get("outputCh"), (List) out2.get("outputCh")); + } + + @Test + public void testNestedObjectsWithoutSorting() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/nested-objects.json"), false); + assertTrue(out.containsKey("outputCh")); + + Channels out2 = new Channels(); + out2.loadFromFile(new File("test-data/channels/nested-objects-random.json"), false); + assertTrue(out2.containsKey("outputCh")); + + assertNotEquals((List) out.get("outputCh"), (List) out2.get("outputCh")); + } + + + @Test + public void testObjects() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/objects.json"), true); + assertTrue(out.containsKey("outputCh")); + + Channels out2 = new Channels(); + out2.loadFromFile(new File("test-data/channels/objects-random.json"), true); + assertTrue(out2.containsKey("outputCh")); + + assertEquals((List) out.get("outputCh"), (List) out2.get("outputCh")); + } + + @Test + public void testObjectsWithoutSorting() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/objects.json"), false); + assertTrue(out.containsKey("outputCh")); + + Channels out2 = new Channels(); + out2.loadFromFile(new File("test-data/channels/objects-random.json"), false); + assertTrue(out2.containsKey("outputCh")); + + assertNotEquals((List) out.get("outputCh"), (List) out2.get("outputCh")); + } + + @Test + public void testObjectsList() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/objects-list.json"), true); + assertTrue(out.containsKey("outputCh")); + + Channels out2 = new Channels(); + out2.loadFromFile(new File("test-data/channels/objects-list-random.json"), true); + assertTrue(out2.containsKey("outputCh")); + + assertEquals((List) out.get("outputCh"), (List) out2.get("outputCh")); + } + + @Test + public void testObjectsListWithoutSorting() { + Channels out = new Channels(); + out.loadFromFile(new File("test-data/channels/objects-list.json"), false); + assertTrue(out.containsKey("outputCh")); + + Channels out2 = new Channels(); + out2.loadFromFile(new File("test-data/channels/objects-list-random.json"), false); + assertTrue(out2.containsKey("outputCh")); + + assertNotEquals((List) out.get("outputCh"), (List) out2.get("outputCh")); + } + +} diff --git a/test-data/channels/integer.json b/test-data/channels/integer.json new file mode 100644 index 00000000..46483d11 --- /dev/null +++ b/test-data/channels/integer.json @@ -0,0 +1,13 @@ +{ + "outputCh": [ + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1 + ] +} \ No newline at end of file diff --git a/test-data/channels/nested-objects-random.json b/test-data/channels/nested-objects-random.json new file mode 100644 index 00000000..1e35f973 --- /dev/null +++ b/test-data/channels/nested-objects-random.json @@ -0,0 +1,53 @@ +{ + "outputCh": [ + [ + "z", + { + "id": 1, + "name": "lukas" + } + ], + [ + "y", + { + "id": 2 + } + ], + [ + "x", + { + "id": 3 + } + ], + [ + "c", + { + "id": 4 + } + ], + [ + "b", + { + "id": 5 + } + ], + [ + "a", + { + "id": 5 + } + ], + [ + "a", + { + "id": 6 + } + ], + [ + "a", + { + "id": 8 + } + ] + ] +} \ No newline at end of file diff --git a/test-data/channels/nested-objects.json b/test-data/channels/nested-objects.json new file mode 100644 index 00000000..beb9e0ec --- /dev/null +++ b/test-data/channels/nested-objects.json @@ -0,0 +1,53 @@ +{ + "outputCh": [ + [ + "z", + { + "name": "lukas", + "id": 1 + } + ], + [ + "y", + { + "id": 2 + } + ], + [ + "x", + { + "id": 3 + } + ], + [ + "c", + { + "id": 4 + } + ], + [ + "b", + { + "id": 5 + } + ], + [ + "a", + { + "id": 8 + } + ], + [ + "a", + { + "id": 6 + } + ], + [ + "a", + { + "id": 5 + } + ] + ] +} \ No newline at end of file diff --git a/test-data/channels/objects-list-random.json b/test-data/channels/objects-list-random.json new file mode 100644 index 00000000..d7175b1c --- /dev/null +++ b/test-data/channels/objects-list-random.json @@ -0,0 +1,36 @@ +{ + "outputCh": [ + { + "data": [ + { + "name": "b1", + "id": "b1" + }, + { + "name": "b2", + "id": "b2" + }, + { + "name": "b3", + "id": "b3" + } + ] + }, + { + "data": [ + { + "name": "n1", + "id": "1" + }, + { + "name": "n2", + "id": "2" + }, + { + "name": "n3", + "id": "3" + } + ] + } + ] +} \ No newline at end of file diff --git a/test-data/channels/objects-list.json b/test-data/channels/objects-list.json new file mode 100644 index 00000000..50e73c18 --- /dev/null +++ b/test-data/channels/objects-list.json @@ -0,0 +1,36 @@ +{ + "outputCh": [ + { + "data": [ + { + "id": "1", + "name": "n1" + }, + { + "id": "2", + "name": "n2" + }, + { + "id": "3", + "name": "n3" + } + ] + }, + { + "data": [ + { + "id": "b1", + "name": "b1" + }, + { + "id": "b2", + "name": "b2" + }, + { + "id": "b3", + "name": "b3" + } + ] + } + ] +} \ No newline at end of file diff --git a/test-data/channels/objects-random.json b/test-data/channels/objects-random.json new file mode 100644 index 00000000..db2f51b3 --- /dev/null +++ b/test-data/channels/objects-random.json @@ -0,0 +1,40 @@ +{ + "outputCh": [ + { + "id": 1 + }, + { + "data": { + "age": 40, + "blabla": "abc" + }, + "a": [ + { + "id": "item1", + "ab": "item1" + } + ], + "name": "sebastian", + "id": 5 + }, + { + "id": 2 + }, + { + "id": 3 + }, + { + "id": 4 + }, + { + "id": 5, + "name": "lukas" + }, + { + "id": 8 + }, + { + "id": 6 + } + ] +} \ No newline at end of file diff --git a/test-data/channels/objects.json b/test-data/channels/objects.json new file mode 100644 index 00000000..fdb2229a --- /dev/null +++ b/test-data/channels/objects.json @@ -0,0 +1,40 @@ +{ + "outputCh": [ + { + "id": 1 + }, + { + "id": 2 + }, + { + "id": 3 + }, + { + "id": 4 + }, + { + "id": 5, + "name": "lukas" + }, + { + "id": 8 + }, + { + "id": 6 + }, + { + "a": [ + { + "ab": "item1", + "id": "item1" + } + ], + "id": 5, + "name": "sebastian", + "data": { + "blabla": "abc", + "age": 40 + } + } + ] +} \ No newline at end of file diff --git a/test-data/channels/string.json b/test-data/channels/string.json new file mode 100644 index 00000000..03a4b62b --- /dev/null +++ b/test-data/channels/string.json @@ -0,0 +1,10 @@ +{ + "outputCh": [ + "z", + "y", + "x", + "c", + "b", + "a" + ] +} \ No newline at end of file diff --git a/test-data/channels/tuples.json b/test-data/channels/tuples.json new file mode 100644 index 00000000..66c14aa0 --- /dev/null +++ b/test-data/channels/tuples.json @@ -0,0 +1,36 @@ +{ + "outputCh": [ + [ + "z", + 1 + ], + [ + "y", + 2 + ], + [ + "x", + 3 + ], + [ + "c", + 4 + ], + [ + "b", + 5 + ], + [ + "a", + 1 + ], + [ + "a", + 6 + ], + [ + "a", + 5 + ] + ] +} \ No newline at end of file