Merge pull request #13382 from thingsboard/tbel_isMap_isList

tbel_Add utility methods to check for Map, List, and Array types
This commit is contained in:
Andrew Shvayka 2025-05-14 15:19:55 +03:00 committed by GitHub
commit f7572a0289
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 153 additions and 27 deletions

View File

@ -33,6 +33,9 @@ import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest { class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
@ -2345,6 +2348,83 @@ class TbelInvokeDocsIoTest extends AbstractTbelInvokeTest {
assertEquals(expected, actual); assertEquals(expected, actual);
} }
@Test
public void isMap_Test() throws ExecutionException, InterruptedException {
msgStr = """
{}
""";
decoderStr = """
return isMap(msg);
""";
Object actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertTrue((Boolean) actual);
decoderStr = """
return isList(msg);
""";
actual = invokeScript(evalScript(decoderStr), msgStr);
assertInstanceOf(Boolean.class, actual);
assertFalse((Boolean) actual);
}
@Test
public void isList_Test() throws ExecutionException, InterruptedException {
msgStr = """
{}
""";
decoderStr = String.format("""
var list = [];
list.add(0x35);
return isList(list);
""");
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
public void isArray_Test() throws ExecutionException, InterruptedException {
msgStr = """
{}
""";
decoderStr = """
var array = new int[3];
array[0] = 1;
array[1] = 2;
array[2] = 3;
return isArray(array);
""";
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);
}
private List splice(List oldList, int start, int deleteCount, Object... values) { private List splice(List oldList, int start, int deleteCount, Object... values) {
start = initStartIndex(oldList, start); start = initStartIndex(oldList, start);
deleteCount = deleteCount < 0 ? 0 : Math.min(deleteCount, (oldList.size() - start)); deleteCount = deleteCount < 0 ? 0 : Math.min(deleteCount, (oldList.size() - start));

View File

@ -380,6 +380,12 @@ public class TbUtils {
double.class, double.class, String.class))); double.class, double.class, String.class)));
parserConfig.addImport("isInsideCircle", new MethodStub(TbUtils.class.getMethod("isInsideCircle", parserConfig.addImport("isInsideCircle", new MethodStub(TbUtils.class.getMethod("isInsideCircle",
double.class, double.class, String.class))); double.class, double.class, String.class)));
parserConfig.addImport("isMap", new MethodStub(TbUtils.class.getMethod("isMap",
Object.class)));
parserConfig.addImport("isList", new MethodStub(TbUtils.class.getMethod("isList",
Object.class)));
parserConfig.addImport("isArray", new MethodStub(TbUtils.class.getMethod("isArray",
Object.class)));
} }
public static String btoa(String input) { public static String btoa(String input) {
@ -1462,6 +1468,19 @@ public class TbUtils {
return range > GeoUtil.distance(entityCoordinates, perimeterCoordinates, rangeUnit); return range > GeoUtil.distance(entityCoordinates, perimeterCoordinates, rangeUnit);
} }
public static boolean isMap(Object obj) {
return obj instanceof Map;
}
public static boolean isList(Object obj) {
return obj instanceof List;
}
public static boolean isArray(Object obj) {
return obj != null && obj.getClass().isArray();
}
private static byte isValidIntegerToByte(Integer val) { private static byte isValidIntegerToByte(Integer val) {
if (val > 255 || val < -128) { if (val > 255 || val < -128) {
throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " + throw new NumberFormatException("The value '" + val + "' could not be correctly converted to a byte. " +

View File

@ -38,11 +38,16 @@ import java.util.Arrays;
import java.util.Base64; import java.util.Base64;
import java.util.Calendar; import java.util.Calendar;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Random; import java.util.Random;
import java.util.concurrent.ExecutionException;
import static java.lang.Character.MAX_RADIX; import static java.lang.Character.MAX_RADIX;
import static java.lang.Character.MIN_RADIX; import static java.lang.Character.MIN_RADIX;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Slf4j @Slf4j
public class TbUtilsTest { public class TbUtilsTest {
@ -315,7 +320,7 @@ public class TbUtilsTest {
TbUtils.parseBytesToFloat(floatValByte, 0, 4, true); TbUtils.parseBytesToFloat(floatValByte, 0, 4, true);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains(message)); assertTrue(e.getMessage().contains(message));
} }
// "01752B0367FA000500010488 FFFFFFFF FFFFFFFF 33"; // "01752B0367FA000500010488 FFFFFFFF FFFFFFFF 33";
@ -325,7 +330,7 @@ public class TbUtilsTest {
TbUtils.parseBytesToFloat(floatValList, 12, 4, false); TbUtils.parseBytesToFloat(floatValList, 12, 4, false);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains(message)); assertTrue(e.getMessage().contains(message));
} }
} }
@ -385,7 +390,7 @@ public class TbUtilsTest {
TbUtils.parseBytesIntToFloat(byteAT101, byteAT101.size() + 1); TbUtils.parseBytesIntToFloat(byteAT101, byteAT101.size() + 1);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("is out of bounds for array with length:")); assertTrue(e.getMessage().contains("is out of bounds for array with length:"));
} }
} }
@ -499,7 +504,7 @@ public class TbUtilsTest {
TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true); TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains(message)); assertTrue(e.getMessage().contains(message));
} }
} }
@ -601,20 +606,20 @@ public class TbUtilsTest {
String actualStr = TbUtils.bytesToString(listHex); String actualStr = TbUtils.bytesToString(listHex);
byte[] actualBytes = actualStr.getBytes(); byte[] actualBytes = actualStr.getBytes();
Assertions.assertArrayEquals(expectedBytes, actualBytes); Assertions.assertArrayEquals(expectedBytes, actualBytes);
Assertions.assertTrue(actualStr.isBlank()); assertTrue(actualStr.isBlank());
listHex = new ArrayList<>(Arrays.asList("0x21", "0x21")); listHex = new ArrayList<>(Arrays.asList("0x21", "0x21"));
expectedBytes = new byte[]{33, 33}; expectedBytes = new byte[]{33, 33};
actualStr = TbUtils.bytesToString(listHex); actualStr = TbUtils.bytesToString(listHex);
actualBytes = actualStr.getBytes(); actualBytes = actualStr.getBytes();
Assertions.assertArrayEquals(expectedBytes, actualBytes); Assertions.assertArrayEquals(expectedBytes, actualBytes);
Assertions.assertFalse(actualStr.isBlank()); assertFalse(actualStr.isBlank());
Assertions.assertEquals("!!", actualStr); Assertions.assertEquals("!!", actualStr);
listHex = new ArrayList<>(Arrays.asList("21", "0x21")); listHex = new ArrayList<>(Arrays.asList("21", "0x21"));
expectedBytes = new byte[]{21, 33}; expectedBytes = new byte[]{21, 33};
actualStr = TbUtils.bytesToString(listHex); actualStr = TbUtils.bytesToString(listHex);
actualBytes = actualStr.getBytes(); actualBytes = actualStr.getBytes();
Assertions.assertArrayEquals(expectedBytes, actualBytes); Assertions.assertArrayEquals(expectedBytes, actualBytes);
Assertions.assertFalse(actualStr.isBlank()); assertFalse(actualStr.isBlank());
Assertions.assertEquals("!", actualStr.substring(1)); Assertions.assertEquals("!", actualStr.substring(1));
Assertions.assertEquals('\u0015', actualStr.charAt(0)); Assertions.assertEquals('\u0015', actualStr.charAt(0));
Assertions.assertEquals(21, actualStr.charAt(0)); Assertions.assertEquals(21, actualStr.charAt(0));
@ -628,7 +633,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listHex); TbUtils.bytesToString(listHex);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("Value: \"FG\" is not numeric or hexDecimal format!")); assertTrue(e.getMessage().contains("Value: \"FG\" is not numeric or hexDecimal format!"));
} }
List<String> listIntString = new ArrayList<>(); List<String> listIntString = new ArrayList<>();
@ -637,7 +642,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listIntString); TbUtils.bytesToString(listIntString);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("The value '-129' could not be correctly converted to a byte. " + assertTrue(e.getMessage().contains("The value '-129' could not be correctly converted to a byte. " +
"Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!")); "Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!"));
} }
@ -646,7 +651,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listIntString); TbUtils.bytesToString(listIntString);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("The value '256' could not be correctly converted to a byte. " + assertTrue(e.getMessage().contains("The value '256' could not be correctly converted to a byte. " +
"Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!")); "Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!"));
} }
@ -656,7 +661,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listIntBytes); TbUtils.bytesToString(listIntBytes);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("The value '-129' could not be correctly converted to a byte. " + assertTrue(e.getMessage().contains("The value '-129' could not be correctly converted to a byte. " +
"Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!")); "Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!"));
} }
@ -665,7 +670,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listIntBytes); TbUtils.bytesToString(listIntBytes);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("The value '256' could not be correctly converted to a byte. " + assertTrue(e.getMessage().contains("The value '256' could not be correctly converted to a byte. " +
"Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!")); "Integer to byte conversion requires the use of only 8 bits (with a range of min/max = -128/255)!"));
} }
@ -677,7 +682,7 @@ public class TbUtilsTest {
TbUtils.bytesToString(listObjects); TbUtils.bytesToString(listObjects);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("The value '[0xFD]' could not be correctly converted to a byte. " + assertTrue(e.getMessage().contains("The value '[0xFD]' could not be correctly converted to a byte. " +
"Must be a HexDecimal/String/Integer/Byte format !")); "Must be a HexDecimal/String/Integer/Byte format !"));
} }
} }
@ -798,25 +803,25 @@ public class TbUtilsTest {
input = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF3"; input = "0x01752B0367FA000500010488FFFFFFFFFFFFFFFF3";
TbUtils.hexToBytes(ctx, input); TbUtils.hexToBytes(ctx, input);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Assertions.assertTrue(e.getMessage().contains("Hex string must be even-length.")); assertTrue(e.getMessage().contains("Hex string must be even-length."));
} }
try { try {
input = "0x01752B0367KA000500010488FFFFFFFFFFFFFFFF33"; input = "0x01752B0367KA000500010488FFFFFFFFFFFFFFFF33";
TbUtils.hexToBytes(ctx, input); TbUtils.hexToBytes(ctx, input);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
Assertions.assertTrue(e.getMessage().contains("Value: \"" + input + "\" is not numeric or hexDecimal format!")); assertTrue(e.getMessage().contains("Value: \"" + input + "\" is not numeric or hexDecimal format!"));
} }
try { try {
input = ""; input = "";
TbUtils.hexToBytes(ctx, input); TbUtils.hexToBytes(ctx, input);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Assertions.assertTrue(e.getMessage().contains("Hex string must be not empty")); assertTrue(e.getMessage().contains("Hex string must be not empty"));
} }
try { try {
input = null; input = null;
TbUtils.hexToBytes(ctx, input); TbUtils.hexToBytes(ctx, input);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Assertions.assertTrue(e.getMessage().contains("Hex string must be not empty")); assertTrue(e.getMessage().contains("Hex string must be not empty"));
} }
} }
@ -905,14 +910,14 @@ public class TbUtilsTest {
TbUtils.raiseError(message); TbUtils.raiseError(message);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2. A value of 4 is invalid.")); assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2. A value of 4 is invalid."));
} }
message = "frequency_weighting_type must be 0, 1 or 2."; message = "frequency_weighting_type must be 0, 1 or 2.";
try { try {
TbUtils.raiseError(message); TbUtils.raiseError(message);
Assertions.fail("Should throw NumberFormatException"); Assertions.fail("Should throw NumberFormatException");
} catch (RuntimeException e) { } catch (RuntimeException e) {
Assertions.assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2.")); assertTrue(e.getMessage().contains("frequency_weighting_type must be 0, 1 or 2."));
} }
} }
@ -1114,7 +1119,7 @@ public class TbUtilsTest {
String emptyInput = Base64.getEncoder().encodeToString(new byte[]{}); String emptyInput = Base64.getEncoder().encodeToString(new byte[]{});
actual = TbUtils.base64ToBytesList(ctx, emptyInput); actual = TbUtils.base64ToBytesList(ctx, emptyInput);
Assertions.assertTrue(actual.isEmpty()); assertTrue(actual.isEmpty());
String invalidInput = "NotAValidBase64String"; String invalidInput = "NotAValidBase64String";
Assertions.assertThrows(IllegalArgumentException.class, () -> { Assertions.assertThrows(IllegalArgumentException.class, () -> {
TbUtils.base64ToBytesList(ctx, invalidInput); TbUtils.base64ToBytesList(ctx, invalidInput);
@ -1146,28 +1151,50 @@ public class TbUtilsTest {
@Test @Test
public void isNaN() { public void isNaN() {
Assertions.assertFalse(TbUtils.isNaN(doubleVal)); assertFalse(TbUtils.isNaN(doubleVal));
Assertions.assertTrue(TbUtils.isNaN(Double.NaN)); assertTrue(TbUtils.isNaN(Double.NaN));
} }
@Test @Test
public void isInsidePolygon() { public void isInsidePolygon() {
// outside the polygon // outside the polygon
String perimeter = "[[[50.75581142688204,29.097910166341073],[50.16785158177623,29.35066098977171],[50.164329922384674,29.773743889862114],[50.16785158177623,30.801230932938843],[50.459245308833495,30.92760634465418],[50.486522489629564,30.68548421850448],[50.703612031034005,30.872660513473573]],[[50.606017492632766,29.36165015600782],[50.54317104075835,29.762754723626013],[50.41021974600505,29.455058069014804]]]"; String perimeter = "[[[50.75581142688204,29.097910166341073],[50.16785158177623,29.35066098977171],[50.164329922384674,29.773743889862114],[50.16785158177623,30.801230932938843],[50.459245308833495,30.92760634465418],[50.486522489629564,30.68548421850448],[50.703612031034005,30.872660513473573]],[[50.606017492632766,29.36165015600782],[50.54317104075835,29.762754723626013],[50.41021974600505,29.455058069014804]]]";
Assertions.assertFalse(TbUtils.isInsidePolygon(50.50869555168039, 30.80123093293884, perimeter)); assertFalse(TbUtils.isInsidePolygon(50.50869555168039, 30.80123093293884, perimeter));
// inside the polygon // inside the polygon
Assertions.assertTrue(TbUtils.isInsidePolygon(50.50520628167696, 30.339685951022016, perimeter)); assertTrue(TbUtils.isInsidePolygon(50.50520628167696, 30.339685951022016, perimeter));
// inside the hole // inside the hole
Assertions.assertFalse(TbUtils.isInsidePolygon(50.52265651287081, 29.488025567723156, perimeter)); assertFalse(TbUtils.isInsidePolygon(50.52265651287081, 29.488025567723156, perimeter));
} }
@Test @Test
public void isInsideCircle() { public void isInsideCircle() {
// outside the circle // outside the circle
String perimeter = "{\"latitude\":50.32254778825905,\"longitude\":28.207787701215757,\"radius\":47477.33130420423}"; String perimeter = "{\"latitude\":50.32254778825905,\"longitude\":28.207787701215757,\"radius\":47477.33130420423}";
Assertions.assertFalse(TbUtils.isInsideCircle(50.81490715736681, 28.05943395702824, perimeter)); assertFalse(TbUtils.isInsideCircle(50.81490715736681, 28.05943395702824, perimeter));
// inside the circle // inside the circle
Assertions.assertTrue(TbUtils.isInsideCircle(50.599397971892444, 28.086906872618542, perimeter)); assertTrue(TbUtils.isInsideCircle(50.599397971892444, 28.086906872618542, perimeter));
}
@Test
public void isMap() throws ExecutionException, InterruptedException {
LinkedHashMap<String, Object> msg = new LinkedHashMap<>(Map.of("temperature", 42, "nested", "508"));
assertTrue(TbUtils.isMap(msg));
assertFalse(TbUtils.isList(msg));
}
@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));
}
@Test
public void isArray() throws ExecutionException, InterruptedException {
byte [] array = new byte[]{1, 2, 3};
assertTrue(TbUtils.isArray(array));
assertFalse(TbUtils.isList(array));
} }
private static List<Byte> toList(byte[] data) { private static List<Byte> toList(byte[] data) {