Merge pull request #13707 from thingsboard/tbel_add_set

tbel_add_ExecutionLinkedHashSet
This commit is contained in:
Andrew Shvayka 2025-07-16 17:53:47 +03:00 committed by GitHub
commit 413a924e69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 376 additions and 31 deletions

View File

@ -26,8 +26,10 @@ import java.util.Base64;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@ -750,6 +752,284 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual);
}
// Sets
@Test
public void setsCreateNewSetFromMap_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "A", "C", "A"]}
""";
decoderStr = """
var originalMap = {};
var set1 = originalMap.entrySet(); // create new Set from map, Empty
var set2 = set1.clone(); // clone new Set, Empty
var result1 = set1.addAll(msg.list); // addAll list, no sort, size = 3 ("A" - duplicate)
return {set1: set1,
set2: set2,
result1: result1
}
""";
Set expectedSet1 = new LinkedHashSet(List.of("B", "A", "C", "A"));
Set expectedSet2 = new LinkedHashSet();
Map<String, Object> expected = new LinkedHashMap<>();
expected.put("set1", expectedSet1);
expected.put("set2", expectedSet2);
expected.put("result1", true);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void setsCreateNewSetFromCreateSetTbMethod_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "A", "C", "A"]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new Set from toSet() with list, no sort, size = 3 ("A" - duplicate)
var set2 = newSet(); // create new Set from newSet(), Empty
return {set1: set1,
set2: set2
}
""";
Set expectedSet1 = new LinkedHashSet(List.of("B", "A", "C", "A"));
Set expectedSet2 = new LinkedHashSet();
Map<String, Object> expected = new LinkedHashMap<>();
expected.put("set1", expectedSet1);
expected.put("set2", expectedSet2);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void setsForeachForLoop_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["A", "B", "C"]}
""";
decoderStr = """
var set2 = toSet(msg.list); // create new from list, size = 3
var set2_0 = set2.toArray()[0]; // return "A", value with index = 0 from Set
var set2Size = set2.size(); // return size = 3
var smthForeach = "";
foreach (item : set2) { // foreach for Set
smthForeach += item; // return "ABC"
}
var smthForLoop= "";
var set2Array = set2.toArray(); // for loop for Set (Set to array))
for (var i =0; i < set2.size; i++) {
smthForLoop += set2Array[i]; // return "ABC"
}
return {
set2: set2,
set2_0: set2_0,
set2Size: set2Size,
smthForeach: smthForeach,
smthForLoop: smthForLoop
}
""";
Set expectedSet2 = new LinkedHashSet(List.of("A", "B", "C"));
Map<String, Object> expected = new LinkedHashMap<>();
expected.put("set2", expectedSet2);
expected.put("set2_0", expectedSet2.toArray()[0]);
expected.put("set2Size", expectedSet2.size());
AtomicReference<String> smth = new AtomicReference<>("");
expectedSet2.forEach(s -> smth.updateAndGet(v -> v + s));
expected.put("smthForeach", smth.get());
expected.put("smthForLoop", smth.get());
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
/**
* add
* delete/remove
* setCreate, setCreatList
*/
@Test
public void setsAddRemove_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["B", "C", "A", "B", "C", "hello", 34567]}
""";
decoderStr = """
// add
var setAdd = toSet(["thigsboard", 4, 67]); // create new, size = 3
var setAdd1_value = setAdd.clone(); // clone setAdd, size = 3
var setAdd2_result = setAdd.add(35); // add value = 35, result = true
var setAdd2_value = setAdd.clone(); // clone setAdd (fixing the result add = 35), size = 4
var setAddList1 = toSet(msg.list); // create new from list without duplicate value ("B" and "C" - only one), size = 5
var setAdd3_result = setAdd.addAll(setAddList1); // add all without duplicate values, result = true
var setAdd3_value = setAdd.clone(); // clone setAdd (with addAll), size = 9
var setAdd4_result = setAdd.add(35); // add duplicate value = 35, result = false
var setAdd4_value = setAdd.clone(); // clone setAdd (after add duplicate value = 35), size = 9
var setAddList2 = toSet(msg.list); // create new from list without duplicate value ("B" and "C" - only one), start: size = 5, finish: size = 7
var setAdd5_result1 = setAddList2.add(72); // add is not duplicate value = 72, result = true
var setAdd5_result2 = setAddList2.add(72); // add duplicate value = 72, result = false
var setAdd5_result3 = setAddList2.add("hello25"); // add is not duplicate value = "hello25", result = true
var setAdd5_value = setAddList2.clone(); // clone setAddList2, size = 7
var setAdd6_result = setAdd.addAll(setAddList2); // add all with duplicate values, result = true
var setAdd6_value = setAdd.clone(); // clone setAdd (after addAll setAddList2), before size = 9, after size = 11, added only is not duplicate values {"hello25", 72}
// remove
var setAdd7_value = setAdd6_value.clone(); // clone setAdd6_value, before size = 11, after remove value = 4 size = 10
var setAdd7_result = setAdd7_value.remove(4); // remove value = 4, result = true
var setAdd8_value = setAdd7_value.clone(); // clone setAdd7_value, before size = 10, after clear size = 0
setAdd8_value.clear(); // setAdd8_value clear, result size = 0
return {
"setAdd1_value": setAdd1_value,
"setAdd2_result": setAdd2_result,
"setAdd2_value": setAdd2_value,
"setAddList1": setAddList1,
"setAdd3_result": setAdd3_result,
"setAdd3_value": setAdd3_value,
"setAdd4_result": setAdd4_result,
"setAdd4_value": setAdd4_value,
"setAdd5_result1": setAdd5_result1,
"setAdd5_result2": setAdd5_result2,
"setAdd5_result3": setAdd5_result3,
"setAddList2": setAddList2,
"setAdd5_value": setAdd5_value,
"setAdd6_result": setAdd6_result,
"setAdd6_value": setAdd6_value,
"setAdd7_result": setAdd7_result,
"setAdd7_value": setAdd7_value,
"setAdd8_value": setAdd8_value
};
""";
ArrayList<Object> list = new ArrayList<>(List.of("B", "C", "A", "B", "C", "hello", 34567));
ArrayList<Object> listAdd = new ArrayList<>(List.of("thigsboard", 4, 67));
Set<Object> setAdd = new LinkedHashSet<>(listAdd);
Set setAdd1_value = new LinkedHashSet<>(setAdd);
boolean setAdd2_result = setAdd.add(35);
Set<Object> setAdd2_value = new LinkedHashSet<>(setAdd);
Set<Object> setAddList1 = new LinkedHashSet<>(list);
boolean setAdd3_result = setAdd.addAll(setAddList1);
Set<Object> setAdd3_value = new LinkedHashSet<>(setAdd);
boolean setAdd4_result = setAdd.add(35);
Set<Object> setAdd4_value = new LinkedHashSet<>(setAdd);
Set<Object> setAddList2 = new LinkedHashSet<>(list);
boolean setAdd5_result1 = setAddList2.add(72);
boolean setAdd5_result2 = setAddList2.add(72);
boolean setAdd5_result3 = setAddList2.add("hello25");
Set<Object> setAdd5_value = new LinkedHashSet<>(setAddList2);
boolean setAdd6_result = setAdd.addAll(setAddList2);
Set<Object> setAdd6_value = new LinkedHashSet<>(setAdd);
// remove
Set<Object> setAdd7_value = new LinkedHashSet<>(setAdd6_value);
boolean setAdd7_result = setAdd7_value.remove(4);
Set<Object> setAdd8_value = new LinkedHashSet<>(setAdd7_value);
setAdd8_value.clear();
LinkedHashMap<String, Object> expected = new LinkedHashMap<>();
expected.put("setAdd1_value", setAdd1_value);
expected.put("setAdd2_result", setAdd2_result);
expected.put("setAdd2_value", setAdd2_value);
expected.put("setAddList1", setAddList1);
expected.put("setAdd3_result", setAdd3_result);
expected.put("setAdd3_value", setAdd3_value);
expected.put("setAdd4_result", setAdd4_result);
expected.put("setAdd4_value", setAdd4_value);
expected.put("setAdd5_result1", setAdd5_result1);
expected.put("setAdd5_result2", setAdd5_result2);
expected.put("setAdd5_result3", setAdd5_result3);
expected.put("setAddList2", setAddList2);
expected.put("setAdd5_value", setAdd5_value);
expected.put("setAdd6_result", setAdd6_result);
expected.put("setAdd6_value", setAdd6_value);
expected.put("setAdd7_result", setAdd7_result);
expected.put("setAdd7_value", setAdd7_value);
expected.put("setAdd8_value", setAdd8_value);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expected.toString(), actual.toString());
}
@Test
public void setsSort_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var set2 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var set1_asc = set1.clone(); // clone set1, size = 6
var set1_desc = set1.clone(); // clone set1, size = 6
set1.sort(); // sort set1 -> asc
set1_asc.sort(true); // sort set1_asc -> asc
set1_desc.sort(false); // sort set1_desc -> desc
var set3 = set2.toSorted(); // toSorted set3 -> asc
var set3_asc = set2.toSorted(true); // toSorted set3 -> asc
var set3_desc = set2.toSorted(false); // toSorted set3 -> desc
return {
"set1": set1,
"set1_asc": set1_asc,
"set1_desc": set1_desc,
"set2": set2,
"set3": set3,
"set3_asc": set3_asc,
"set3_desc": set3_desc,
}
""";
ArrayList<Object> list = new ArrayList<>(List.of("C", "B", "A", 34567, "hello", 34));
Set<Object> expected = new LinkedHashSet<>(list);
ArrayList<Object> listSortAsc = new ArrayList<>(List.of(34, 34567, "A", "B", "C", "hello"));
Set<Object> expectedAsc = new LinkedHashSet<>(listSortAsc);
ArrayList<Object> listSortDesc = new ArrayList<>(List.of("hello", "C", "B", "A", 34567, 34));
Set<Object> expectedDesc = new LinkedHashSet<>(listSortDesc);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>)actual).get("set1_desc").toString());
assertEquals(expected.toString(), ((LinkedHashMap<?, ?>)actual).get("set2").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3").toString());
assertEquals(expectedAsc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3_asc").toString());
assertEquals(expectedDesc.toString(), ((LinkedHashMap<?, ?>)actual).get("set3_desc").toString());
}
@Test
public void setsContains_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var result1 = set1.contains("A"); // return true
var result2 = set1.contains("H"); // return false
return {
"set1": set1,
"result1": result1,
"result2": result2
}
""";
List<Object> listOrigin = new ArrayList<>(List.of("C", "B", "A", 34567, "B", "C", "hello", 34));
Set<Object> expectedSet = new LinkedHashSet<>(listOrigin);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(true, ((LinkedHashMap<?, ?>)actual).get("result1"));
assertEquals(false, ((LinkedHashMap<?, ?>)actual).get("result2"));
}
@Test
public void setsToList_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
var set1 = toSet(msg.list); // create new from method toSet(List list) no sort, size = 6 ("A" and "C" is duplicated)
var tolist = set1.toList(); // create new List from Set, size = 6
return {
"list": msg.list,
"set1": set1,
"tolist": tolist
}
""";
List<Object> listOrigin = new ArrayList<>(List.of("C", "B", "A", 34567, "B", "C", "hello", 34));
Set<Object> expectedSet = new LinkedHashSet<>(listOrigin);
List<Object> expectedToList = new ArrayList<>(expectedSet);
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertEquals(listOrigin.toString(), ((LinkedHashMap<?, ?>)actual).get("list").toString());
assertEquals(expectedSet.toString(), ((LinkedHashMap<?, ?>)actual).get("set1").toString());
assertEquals(expectedToList.toString(), ((LinkedHashMap<?, ?>)actual).get("tolist").toString());
}
@Test
public void arraysWillCauseArrayIndexOutOfBoundsException_Test() throws ExecutionException, InterruptedException {
msgStr = """
@ -2399,25 +2679,19 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
list.add(0x35);
return isList(list);
""");
}
@Test
public void isSet_Test() throws ExecutionException, InterruptedException {
msgStr = """
{"list": ["C", "B", "A", 34567, "B", "C", "hello", 34]}
""";
decoderStr = """
return isSet(toSet(msg.list)); // return true
""";
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) actual);
decoderStr = String.format("""
var list = [];
list.add(0x35);
return isMap(list);
""");
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
decoderStr = String.format("""
var list = [];
list.add(0x35);
return isArray(list);
""");
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
}
@Test
@ -2435,16 +2709,6 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) actual);
decoderStr = """
var array = new int[3];
array[0] = 1;
array[1] = 2;
array[2] = 3;
return isList(array);
""";
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
}
@Test

View File

@ -23,6 +23,7 @@ import org.mvel2.ExecutionContext;
import org.mvel2.ParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.execution.ExecutionLinkedHashSet;
import org.mvel2.util.MethodStub;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.common.util.geo.Coordinates;
@ -46,6 +47,7 @@ import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@ -386,6 +388,12 @@ public class TbUtils {
Object.class)));
parserConfig.addImport("isArray", new MethodStub(TbUtils.class.getMethod("isArray",
Object.class)));
parserConfig.addImport("newSet", new MethodStub(TbUtils.class.getMethod("newSet",
ExecutionContext.class)));
parserConfig.addImport("toSet", new MethodStub(TbUtils.class.getMethod("toSet",
ExecutionContext.class, List.class)));
parserConfig.addImport("isSet", new MethodStub(TbUtils.class.getMethod("isSet",
Object.class)));
}
public static String btoa(String input) {
@ -1481,6 +1489,19 @@ public class TbUtils {
return obj != null && obj.getClass().isArray();
}
public static <E> Set<E> newSet(ExecutionContext ctx) {
return new ExecutionLinkedHashSet<>(ctx);
}
public static <E> Set<E> toSet(ExecutionContext ctx, List<E> list) {
Set<E> newSet = new LinkedHashSet<>(list);
return new ExecutionLinkedHashSet<>(newSet, ctx);
}
public static boolean isSet(Object obj) {
return obj instanceof Set;
}
private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +

View File

@ -28,6 +28,7 @@ import org.mvel2.ParserContext;
import org.mvel2.SandboxedParserConfiguration;
import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import org.mvel2.execution.ExecutionLinkedHashSet;
import java.io.IOException;
import java.math.BigDecimal;
@ -39,14 +40,18 @@ import java.util.Base64;
import java.util.Calendar;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import static java.lang.Character.MAX_RADIX;
import static java.lang.Character.MIN_RADIX;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Slf4j
@ -1184,10 +1189,11 @@ public class TbUtilsTest {
@Test
public void isList() throws ExecutionException, InterruptedException {
List<Integer> liat = List.of(0x35);
assertTrue(TbUtils.isList(liat));
assertFalse(TbUtils.isMap(liat));
assertFalse(TbUtils.isArray(liat));
List<Integer> list = List.of(0x35);
assertTrue(TbUtils.isList(list));
assertFalse(TbUtils.isMap(list));
assertFalse(TbUtils.isArray(list));
assertFalse(TbUtils.isSet(list));
}
@Test
@ -1195,6 +1201,52 @@ public class TbUtilsTest {
byte [] array = new byte[]{1, 2, 3};
assertTrue(TbUtils.isArray(array));
assertFalse(TbUtils.isList(array));
assertFalse(TbUtils.isSet(array));
}
@Test
public void isSet() throws ExecutionException, InterruptedException {
Set<Byte> set = toSet(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA});
assertTrue(TbUtils.isSet(set));
assertFalse(TbUtils.isList(set));
assertFalse(TbUtils.isArray(set));
}
@Test
public void setTest() throws ExecutionException, InterruptedException {
Set actual = TbUtils.newSet(ctx);
Set expected = toSet(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xCC});
actual.add((byte) 0xDD);
actual.add((byte) 0xCC);
actual.add((byte) 0xCC);
assertTrue(expected.containsAll(actual));
List list = toList(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA});
actual.addAll(list);
assertEquals(4, actual.size());
assertTrue(actual.containsAll(expected));
actual = TbUtils.toSet(ctx, list);
expected = toSet(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xDA});
actual.add((byte) 0xDA);
actual.remove((byte) 0xBB);
actual.remove((byte) 0xAA);
assertTrue(expected.containsAll(actual));
assertEquals(actual.size(), 3);
actual.clear();
assertTrue(actual.isEmpty());
actual = TbUtils.toSet(ctx, list);
Set actualClone = TbUtils.toSet(ctx, list);
Set actualClone_asc = TbUtils.toSet(ctx, list);
Set actualClone_desc = TbUtils.toSet(ctx, list);
((ExecutionLinkedHashSet<?>)actualClone).sort();
((ExecutionLinkedHashSet<?>)actualClone_asc).sort(true);
((ExecutionLinkedHashSet<?>)actualClone_desc).sort(false);
assertEquals(list.toString(), actual.toString());
assertNotEquals(list.toString(), actualClone.toString());
Collections.sort(list);
assertEquals(list.toString(), actualClone.toString());
assertEquals(list.toString(), actualClone_asc.toString());
Collections.sort(list, Collections.reverseOrder());
assertNotEquals(list.toString(), actualClone_asc.toString());
assertEquals(list.toString(), actualClone_desc.toString());
}
private static List<Byte> toList(byte[] data) {
@ -1204,5 +1256,13 @@ public class TbUtilsTest {
}
return result;
}
private static Set<Byte> toSet(byte[] data) {
Set<Byte> result = new LinkedHashSet<>();
for (Byte b : data) {
result.add(b);
}
return result;
}
}

View File

@ -86,7 +86,7 @@
<zookeeper.version>3.9.3</zookeeper.version>
<protobuf.version>3.25.5</protobuf.version> <!-- A Major v4 does not support by the pubsub yet-->
<grpc.version>1.63.0</grpc.version>
<tbel.version>1.2.6</tbel.version>
<tbel.version>1.2.7</tbel.version>
<lombok.version>1.18.32</lombok.version>
<paho.client.version>1.2.5</paho.client.version>
<paho.mqttv5.client.version>1.2.5</paho.mqttv5.client.version>