Merge branch 'master' into feature/scada-symbol

This commit is contained in:
Igor Kulikov 2024-07-26 15:12:31 +03:00
commit e1a0650689
17 changed files with 433 additions and 134 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -61,6 +61,7 @@ public class TbUtils {
private static final int HEX_LEN_MIN = -1;
private static final int HEX_LEN_INT_MAX = 8;
private static final int HEX_LEN_LONG_MAX = 16;
private static final int BYTES_LEN_INT_MAX = 4;
private static final int BYTES_LEN_LONG_MAX = 8;
private static final LinkedHashMap<String, String> mdnEncodingReplacements = new LinkedHashMap<>();
@ -117,6 +118,8 @@ public class TbUtils {
String.class)));
parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat",
String.class, int.class)));
parserConfig.addImport("parseHexIntLongToFloat", new MethodStub(TbUtils.class.getMethod("parseHexIntLongToFloat",
String.class, boolean.class)));
parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble",
String.class)));
parserConfig.addImport("parseLittleEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToInt",
@ -127,10 +130,18 @@ public class TbUtils {
String.class)));
parserConfig.addImport("parseHexToInt", new MethodStub(TbUtils.class.getMethod("parseHexToInt",
String.class, boolean.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
List.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
List.class, int.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
byte[].class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
byte[].class, int.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt",
@ -143,10 +154,18 @@ public class TbUtils {
String.class)));
parserConfig.addImport("parseHexToLong", new MethodStub(TbUtils.class.getMethod("parseHexToLong",
String.class, boolean.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
List.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
List.class, int.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
byte[].class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
byte[].class, int.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToLong", new MethodStub(TbUtils.class.getMethod("parseBytesToLong",
@ -160,13 +179,21 @@ public class TbUtils {
parserConfig.addImport("parseHexToFloat", new MethodStub(TbUtils.class.getMethod("parseHexToFloat",
String.class, boolean.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
byte[].class, int.class, boolean.class)));
List.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
List.class, int.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
byte[].class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
byte[].class, int.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
List.class, int.class, boolean.class)));
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToFloat", new MethodStub(TbUtils.class.getMethod("parseBytesToFloat",
List.class, int.class)));
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseLittleEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToDouble",
String.class)));
parserConfig.addImport("parseBigEndianHexToDouble", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToDouble",
@ -176,13 +203,21 @@ public class TbUtils {
parserConfig.addImport("parseHexToDouble", new MethodStub(TbUtils.class.getMethod("parseHexToDouble",
String.class, boolean.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class, int.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class, int.class, boolean.class)));
List.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
List.class, int.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
List.class, int.class, boolean.class)));
List.class, int.class, int.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
List.class, int.class, int.class, boolean.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class, int.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class, int.class, int.class)));
parserConfig.addImport("parseBytesToDouble", new MethodStub(TbUtils.class.getMethod("parseBytesToDouble",
byte[].class, int.class, int.class, boolean.class)));
parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed",
double.class, int.class)));
parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed",
@ -201,17 +236,17 @@ public class TbUtils {
Long.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class, boolean.class)));
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
parserConfig.addImport("longToHex", new MethodStub(TbUtils.class.getMethod("longToHex",
Long.class, boolean.class, boolean.class, int.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class, int.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class, int.class, boolean.class)));
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
parserConfig.addImport("intLongToString", new MethodStub(TbUtils.class.getMethod("intLongToString",
Long.class, int.class, boolean.class, boolean.class)));
parserConfig.addImport("floatToHex", new MethodStub(TbUtils.class.getMethod("floatToHex",
Float.class)));
@ -334,10 +369,14 @@ public class TbUtils {
}
public static Integer parseInt(String value, int radix) {
if (StringUtils.isNotBlank(value)) {
String valueP = prepareNumberString(value);
return parseInt(value, radix, true);
}
private static Integer parseInt(String value, int radix, boolean bigEndian) {
String valueP = prepareNumberString(value, bigEndian);
if (valueP != null) {
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue >= 25 && radixValue <= MAX_RADIX) {
if (radixValue >= 25 && radixValue <= MAX_RADIX) {
return (Integer) compareIntLongValueMinMax(valueP, radixValue, Integer.MAX_VALUE, Integer.MIN_VALUE);
}
return switch (radixValue) {
@ -354,10 +393,14 @@ public class TbUtils {
}
public static Long parseLong(String value, int radix) {
if (StringUtils.isNotBlank(value)) {
String valueP = prepareNumberString(value);
return parseLong(value, radix, true);
}
private static Long parseLong(String value, int radix, boolean bigEndian) {
String valueP = prepareNumberString(value, bigEndian);
if (valueP != null) {
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue >= 25 && radixValue <= MAX_RADIX) {
if (radixValue >= 25 && radixValue <= MAX_RADIX) {
return (Long) compareIntLongValueMinMax(valueP, radixValue, Long.MAX_VALUE, Long.MIN_VALUE);
}
return switch (radixValue) {
@ -385,6 +428,7 @@ public class TbUtils {
return Integer.parseInt(binaryString, MIN_RADIX);
}
}
private static long parseBinaryStringAsSignedLong(String binaryString) {
if (binaryString.length() != 64) {
// Pad the binary string to 64 bits if it is not already
@ -420,34 +464,58 @@ public class TbUtils {
}
public static Float parseFloat(String value) {
return parseFloat(value, 0);
return parseFloat(value, ZERO_RADIX);
}
public static Float parseFloat(String value, int radix) {
if (StringUtils.isNotBlank(value)) {
String valueP = prepareNumberString(value);
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue == DEC_RADIX) {
return Float.parseFloat(value);
} else {
int bits = Integer.parseUnsignedInt(valueP, HEX_RADIX);
return Float.intBitsToFloat(bits);
String valueP = prepareNumberString(value, true);
if (valueP != null) {
return parseFloatFromString(value, valueP, radix);
}
return null;
}
private static Float parseFloatFromString(String value, String valueP, int radix) {
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue == HEX_RADIX) {
int bits = (int) Long.parseLong(valueP, HEX_RADIX);
// Hex representation is a standard IEEE 754 float value (eg "0x41200000" for 10.0f).
return Float.intBitsToFloat(bits);
} else {
return Float.parseFloat(value);
}
}
public static Float parseHexIntLongToFloat(String value, boolean bigEndian) {
String valueP = prepareNumberString(value, bigEndian);
if (valueP != null) {
int radixValue = isValidStringAndRadix(valueP, HEX_RADIX, value);
if (radixValue == HEX_RADIX) {
int bits = (int) Long.parseLong(valueP, HEX_RADIX);
// If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f).
float floatValue = (float) bits;
return Float.valueOf(floatValue);
}
}
return null;
}
public static Double parseDouble(String value) {
int radix = getRadix10_16(value);
return parseDouble(value, radix);
}
public static Double parseDouble(String value, int radix) {
if (value != null) {
String valueP = prepareNumberString(value);
return parseDouble(value, radix, true);
}
private static Double parseDouble(String value, int radix, boolean bigEndian) {
String valueP = prepareNumberString(value, bigEndian);
if (valueP != null) {
int radixValue = isValidStringAndRadix(valueP, radix, value);
if (radixValue == DEC_RADIX) {
return Double.parseDouble(prepareNumberString(value));
return Double.parseDouble(valueP);
} else {
long bits = Long.parseUnsignedLong(valueP, HEX_RADIX);
return Double.longBitsToDouble(bits);
@ -469,9 +537,7 @@ public class TbUtils {
}
public static Integer parseHexToInt(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? hexValue : reverseHexStringByOrder(hexValue);
return parseInt(hex, HEX_RADIX);
return parseInt(value, HEX_RADIX, bigEndian);
}
public static long parseLittleEndianHexToLong(String hex) {
@ -487,9 +553,7 @@ public class TbUtils {
}
public static Long parseHexToLong(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? value : reverseHexStringByOrder(hexValue);
return parseLong(hex, HEX_RADIX);
return parseLong(value, HEX_RADIX, bigEndian);
}
public static float parseLittleEndianHexToFloat(String hex) {
@ -505,9 +569,11 @@ public class TbUtils {
}
public static Float parseHexToFloat(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? value : reverseHexStringByOrder(hexValue);
return parseFloat(hex, HEX_RADIX);
String valueP = prepareNumberString(value, bigEndian);
if (valueP != null) {
return parseFloatFromString(value, valueP, HEX_RADIX);
}
return null;
}
public static double parseLittleEndianHexToDouble(String hex) {
@ -523,13 +589,11 @@ public class TbUtils {
}
public static double parseHexToDouble(String value, boolean bigEndian) {
String hexValue = prepareNumberString(value);
String hex = bigEndian ? value : reverseHexStringByOrder(hexValue);
return parseDouble(hex, HEX_RADIX);
return parseDouble(value, HEX_RADIX, bigEndian);
}
public static ExecutionArrayList<Byte> hexToBytes(ExecutionContext ctx, String value) {
String hex = prepareNumberString(value);
String hex = prepareNumberString(value, true);
int len = hex.length();
if (len % 2 > 0) {
throw new IllegalArgumentException("Hex string must be even-length.");
@ -575,6 +639,7 @@ public class TbUtils {
public static String longToHex(Long l) {
return prepareNumberHexString(l, true, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX);
}
public static String longToHex(Long l, boolean bigEndian) {
return prepareNumberHexString(l, bigEndian, false, HEX_LEN_MIN, HEX_LEN_LONG_MAX);
}
@ -604,7 +669,7 @@ public class TbUtils {
return Long.toString(number, radix);
}
return switch (radix) {
case MIN_RADIX -> Long.toBinaryString(number);
case MIN_RADIX -> Long.toBinaryString(number);
case OCTAL_RADIX -> Long.toOctalString(number);
case DEC_RADIX -> Long.toString(number);
case HEX_RADIX -> prepareNumberHexString(number, bigEndian, pref, -1, -1);
@ -646,13 +711,13 @@ public class TbUtils {
private static String removeLeadingZero_FF(String hex, Long number, int hexLenMax) {
String hexWithoutZero = hex.replaceFirst("^0+(?!$)", ""); // Remove leading zeros except for the last one
hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero;
hexWithoutZero = hexWithoutZero.length() % 2 > 0 ? "0" + hexWithoutZero : hexWithoutZero;
if (number >= 0) {
return hexWithoutZero;
} else {
String hexWithoutZeroFF = hexWithoutZero.replaceFirst("^F+(?!$)", "");
hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF;
if (hexWithoutZeroFF.length() > hexLenMax) {
hexWithoutZeroFF = hexWithoutZeroFF.length() % 2 > 0 ? "F" + hexWithoutZeroFF : hexWithoutZeroFF;
if (hexWithoutZeroFF.length() > hexLenMax) {
return hexWithoutZeroFF.substring(hexWithoutZeroFF.length() - hexLenMax);
} else if (hexWithoutZeroFF.length() == hexLenMax) {
return hexWithoutZeroFF;
@ -668,7 +733,7 @@ public class TbUtils {
public static String floatToHex(Float f, boolean bigEndian) {
// Convert the float to its raw integer bits representation
int bits = Float.floatToRawIntBits(f);
int bits = Float.floatToIntBits(f);
// Format the integer bits as a hexadecimal string
String result = String.format("0x%08X", bits);
@ -699,16 +764,28 @@ public class TbUtils {
return Base64.getDecoder().decode(input);
}
public static int parseBytesToInt(List<Byte> data) {
return parseBytesToInt(Bytes.toArray(data));
}
public static int parseBytesToInt(List<Byte> data, int offset) {
return parseBytesToInt(Bytes.toArray(data), offset);
}
public static int parseBytesToInt(List<Byte> data, int offset, int length) {
return parseBytesToInt(data, offset, length, true);
return parseBytesToInt(Bytes.toArray(data), offset, length);
}
public static int parseBytesToInt(List<Byte> data, int offset, int length, boolean bigEndian) {
final byte[] bytes = new byte[data.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = data.get(i);
}
return parseBytesToInt(bytes, offset, length, bigEndian);
return parseBytesToInt(Bytes.toArray(data), offset, length, bigEndian);
}
public static int parseBytesToInt(byte[] data) {
return parseBytesToInt(data, 0);
}
public static int parseBytesToInt(byte[] data, int offset) {
return parseBytesToInt(data, offset, BYTES_LEN_INT_MAX);
}
public static int parseBytesToInt(byte[] data, int offset, int length) {
@ -719,7 +796,7 @@ public class TbUtils {
if (offset > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!");
}
if (length > 4) {
if (length > BYTES_LEN_INT_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!");
}
if (offset + length > data.length) {
@ -735,16 +812,28 @@ public class TbUtils {
return bb.getInt();
}
public static long parseBytesToLong(List<Byte> data) {
return parseBytesToLong(Bytes.toArray(data));
}
public static long parseBytesToLong(List<Byte> data, int offset) {
return parseBytesToLong(Bytes.toArray(data), offset);
}
public static long parseBytesToLong(List<Byte> data, int offset, int length) {
return parseBytesToLong(data, offset, length, true);
return parseBytesToLong(Bytes.toArray(data), offset, length);
}
public static long parseBytesToLong(List<Byte> data, int offset, int length, boolean bigEndian) {
final byte[] bytes = new byte[data.size()];
for (int i = 0; i < bytes.length; i++) {
bytes[i] = data.get(i);
}
return parseBytesToLong(bytes, offset, length, bigEndian);
return parseBytesToLong(Bytes.toArray(data), offset, length, bigEndian);
}
public static long parseBytesToLong(byte[] data) {
return parseBytesToLong(data, 0);
}
public static long parseBytesToLong(byte[] data, int offset) {
return parseBytesToLong(data, offset, BYTES_LEN_LONG_MAX);
}
public static long parseBytesToLong(byte[] data, int offset, int length) {
@ -761,49 +850,118 @@ public class TbUtils {
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
var bb = ByteBuffer.allocate(8);
var bb = ByteBuffer.allocate(BYTES_LEN_LONG_MAX);
if (!bigEndian) {
bb.order(ByteOrder.LITTLE_ENDIAN);
}
bb.position(bigEndian ? 8 - length : 0);
bb.position(bigEndian ? BYTES_LEN_LONG_MAX - length : 0);
bb.put(data, offset, length);
bb.position(0);
return bb.getLong();
}
public static float parseBytesToFloat(byte[] data, int offset) {
return parseBytesToFloat(data, offset, true);
public static float parseBytesToFloat(List data) {
return parseBytesToFloat(Bytes.toArray(data), 0);
}
public static float parseBytesToFloat(List data, int offset) {
return parseBytesToFloat(data, offset, true);
return parseBytesToFloat(Bytes.toArray(data), offset, BYTES_LEN_INT_MAX);
}
public static float parseBytesToFloat(List data, int offset, boolean bigEndian) {
return parseBytesToFloat(Bytes.toArray(data), offset, bigEndian);
public static float parseBytesToFloat(List data, int offset, int length) {
return parseBytesToFloat(Bytes.toArray(data), offset, length, true);
}
public static float parseBytesToFloat(byte[] data, int offset, boolean bigEndian) {
byte[] bytesToNumber = prepareBytesToNumber(data, offset, 4, bigEndian);
return ByteBuffer.wrap(bytesToNumber).getFloat();
public static float parseBytesToFloat(List data, int offset, int length, boolean bigEndian) {
return parseBytesToFloat(Bytes.toArray(data), offset, length, bigEndian);
}
public static float parseBytesToFloat(byte[] data) {
return parseBytesToFloat(data, 0);
}
public static double parseBytesToDouble(byte[] data, int offset) {
return parseBytesToDouble(data, offset, true);
public static float parseBytesToFloat(byte[] data, int offset) {
return parseBytesToFloat(data, offset, BYTES_LEN_INT_MAX);
}
public static float parseBytesToFloat(byte[] data, int offset, int length) {
return parseBytesToFloat(data, offset, length, true);
}
public static float parseBytesToFloat(byte[] data, int offset, int length, boolean bigEndian) {
if (length > BYTES_LEN_INT_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_INT_MAX + " bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian);
if (bytesToNumber.length < BYTES_LEN_INT_MAX) {
byte[] extendedBytes = new byte[BYTES_LEN_INT_MAX];
Arrays.fill(extendedBytes, (byte) 0);
System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length);
bytesToNumber = extendedBytes;
}
float floatValue = ByteBuffer.wrap(bytesToNumber).getFloat();
if (!Float.isNaN(floatValue)) {
return floatValue;
} else {
long longValue = parseBytesToLong(bytesToNumber, 0, BYTES_LEN_INT_MAX);
BigDecimal bigDecimalValue = new BigDecimal(longValue);
return bigDecimalValue.floatValue();
}
}
public static double parseBytesToDouble(List data) {
return parseBytesToDouble(Bytes.toArray(data));
}
public static double parseBytesToDouble(List data, int offset) {
return parseBytesToDouble(data, offset, true);
return parseBytesToDouble(Bytes.toArray(data), offset);
}
public static double parseBytesToDouble(List data, int offset, boolean bigEndian) {
return parseBytesToDouble(Bytes.toArray(data), offset, bigEndian);
public static double parseBytesToDouble(List data, int offset, int length) {
return parseBytesToDouble(Bytes.toArray(data), offset, length);
}
public static double parseBytesToDouble(byte[] data, int offset, boolean bigEndian) {
byte[] bytesToNumber = prepareBytesToNumber(data, offset, BYTES_LEN_LONG_MAX, bigEndian);
return ByteBuffer.wrap(bytesToNumber).getDouble();
public static double parseBytesToDouble(List data, int offset, int length, boolean bigEndian) {
return parseBytesToDouble(Bytes.toArray(data), offset, length, bigEndian);
}
public static double parseBytesToDouble(byte[] data) {
return parseBytesToDouble(data, 0);
}
public static double parseBytesToDouble(byte[] data, int offset) {
return parseBytesToDouble(data, offset, BYTES_LEN_LONG_MAX);
}
public static double parseBytesToDouble(byte[] data, int offset, int length) {
return parseBytesToDouble(data, offset, length, true);
}
public static double parseBytesToDouble(byte[] data, int offset, int length, boolean bigEndian) {
if (length > BYTES_LEN_LONG_MAX) {
throw new IllegalArgumentException("Length: " + length + " is too large. Maximum " + BYTES_LEN_LONG_MAX + " bytes is allowed!");
}
if (offset + length > data.length) {
throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!");
}
byte[] bytesToNumber = prepareBytesToNumber(data, offset, length, bigEndian);
if (bytesToNumber.length < BYTES_LEN_LONG_MAX) {
byte[] extendedBytes = new byte[BYTES_LEN_LONG_MAX];
Arrays.fill(extendedBytes, (byte) 0);
System.arraycopy(bytesToNumber, 0, extendedBytes, 0, bytesToNumber.length);
bytesToNumber = extendedBytes;
}
double doubleValue = ByteBuffer.wrap(bytesToNumber).getDouble();
if (!Double.isNaN(doubleValue)) {
return doubleValue;
} else {
BigInteger bigInt = new BigInteger(1, bytesToNumber);
BigDecimal bigDecimalValue = new BigDecimal(bigInt);
return bigDecimalValue.doubleValue();
}
}
private static byte[] prepareBytesToNumber(byte[] data, int offset, int length, boolean bigEndian) {
@ -931,14 +1089,15 @@ public class TbUtils {
}
}
private static String prepareNumberString(String value) {
if (value != null) {
private static String prepareNumberString(String value, boolean bigEndian) {
if (StringUtils.isNotBlank(value)) {
value = value.trim();
value = value.replace("0x", "");
value = value.replace("0X", "");
value = value.replace(",", ".");
return bigEndian ? value : reverseHexStringByOrder(value);
}
return value;
return null;
}
private static int isValidStringAndRadix(String valueP, int radix, String value) {

View File

@ -30,6 +30,7 @@ import org.mvel2.execution.ExecutionArrayList;
import org.mvel2.execution.ExecutionHashMap;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -47,7 +48,7 @@ public class TbUtilsTest {
private ExecutionContext ctx;
private final float floatVal = 29.29824f;
private final Float floatVal = 29.29824f;
private final float floatValRev = -5.948442E7f;
@ -256,7 +257,7 @@ public class TbUtilsTest {
@Test
public void parseFloat() {
String floatValStr = "29.29824";
String floatValStr = floatVal.toString();
Assertions.assertEquals(java.util.Optional.of(floatVal).get(), TbUtils.parseFloat(floatValStr));
String floatValHex = "41EA62CC";
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseHexToFloat(floatValHex)));
@ -274,14 +275,49 @@ public class TbUtilsTest {
}
@Test
public void arseBytesToFloat() {
public void parseBytesToFloat() {
byte[] floatValByte = {65, -22, 98, -52};
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValByte, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, false)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false)));
List<Byte> floatVaList = Bytes.asList(floatValByte);
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatVaList, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatVaList, 0, false)));
List<Byte> floatValList = Bytes.asList(floatValByte);
Assertions.assertEquals(0, Float.compare(floatVal, TbUtils.parseBytesToFloat(floatValList, 0)));
Assertions.assertEquals(0, Float.compare(floatValRev, TbUtils.parseBytesToFloat(floatValList, 0, 4, false)));
// 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF}
floatValByte = new byte[]{-1, -1, -1, -1};
float floatExpectedBe = 4294.9673f;
float floatExpectedLe = 4.2949673E9f;
float actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 4, false)));
floatValList = Bytes.asList(floatValByte);
actualBe = TbUtils.parseBytesToFloat(floatValList, 0);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 4, false)));
// 2 143 289 344L == {0x7F, 0xC0, 0x00, 0x00}
floatValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00};
floatExpectedBe = 2143.3547f;
floatExpectedLe = -3.984375f;
actualBe = TbUtils.parseBytesToFloat(floatValByte, 0, 4, true);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValByte, 0, 2, false)));
floatValList = Bytes.asList(floatValByte);
floatExpectedLe = 4.2908055E9f;
actualBe = TbUtils.parseBytesToFloat(floatValList, 0);
Assertions.assertEquals(0, Float.compare(floatExpectedBe, actualBe / 1000000));
Assertions.assertEquals(0, Float.compare(floatExpectedLe, TbUtils.parseBytesToFloat(floatValList, 0, 3, false)));
// "01752B0367FA000500010488 FFFFFFFF FFFFFFFF 33";
String intToHexBe = "01752B0367FA000500010488FFFFFFFFFFFFFFFF33";
floatExpectedLe = 4294.9673f;
floatValList = TbUtils.hexToBytes(ctx, intToHexBe);
float actualLe = TbUtils.parseBytesToFloat(floatValList, 12, 4, false);
Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000));
actualLe = TbUtils.parseBytesToFloat(floatValList, 12 + 4, 4, false);
Assertions.assertEquals(0, Float.compare(floatExpectedLe, actualLe / 1000000));
}
@Test
@ -356,11 +392,40 @@ public class TbUtilsTest {
public void parseBytesToDouble() {
byte[] doubleValByte = {64, -101, 4, -79, 12, -78, -107, -22};
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValByte, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, false)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false)));
List<Byte> doubleVaList = Bytes.asList(doubleValByte);
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleVaList, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleVaList, 0, false)));
List<Byte> doubleValList = Bytes.asList(doubleValByte);
Assertions.assertEquals(0, Double.compare(doubleVal, TbUtils.parseBytesToDouble(doubleValList, 0)));
Assertions.assertEquals(0, Double.compare(doubleValRev, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false)));
// 4 294 967 295L == {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
doubleValByte = new byte[]{-1, -1, -1, -1, -1, -1, -1, -1};
double doubleExpectedBe = 18446.744073709553d;
double doubleExpectedLe = 1.8446744073709552E19d;
double actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe / 1000000000000000L));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false)));
doubleValList = Bytes.asList(doubleValByte);
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, TbUtils.parseBytesToDouble(doubleValList, 0) / 1000000000000000L));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 8, false)));
doubleValByte = new byte[]{0x7F, (byte) 0xC0, (byte) 0xFF, 0x00, 0x7F, (byte) 0xC0, (byte) 0xFF, 0x00};
doubleExpectedBe = 2387013.651780523d;
doubleExpectedLe = 7.234601680440024E-304d;
actualBe = TbUtils.parseBytesToDouble(doubleValByte, 0, 8, true);
BigDecimal bigDecimal = new BigDecimal(actualBe);
// We move the decimal point to the left by 301 positions
actualBe = bigDecimal.movePointLeft(301).doubleValue();
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValByte, 0, 8, false)));
doubleValList = Bytes.asList(doubleValByte);
doubleExpectedLe = 5.828674572203954E303d;
actualBe = TbUtils.parseBytesToDouble(doubleValList, 0);
bigDecimal = new BigDecimal(actualBe);
actualBe = bigDecimal.movePointLeft(301).doubleValue();
Assertions.assertEquals(0, Double.compare(doubleExpectedBe, actualBe));
Assertions.assertEquals(0, Double.compare(doubleExpectedLe, TbUtils.parseBytesToDouble(doubleValList, 0, 5, false)));
}
@Test
@ -605,15 +670,52 @@ public class TbUtilsTest {
@Test
public void floatToHex_Test() {
Float value = 20.89f;
String expectedHex = "0x41A71EB8";
String valueHexRev = "0xB81EA741";
Float value = 123456789.00f;
String expectedHex = "0x4CEB79A3";
String valueHexRev = "0xA379EB4C";
String actual = TbUtils.floatToHex(value);
Assertions.assertEquals(expectedHex, actual);
Float valueActual = TbUtils.parseHexToFloat(actual);
Assertions.assertEquals(value, valueActual);
valueActual = TbUtils.parseHexToFloat(valueHexRev, false);
Assertions.assertEquals(value, valueActual);
value = 123456789.67f;
expectedHex = "0x4CEB79A3";
valueHexRev = "0xA379EB4C";
actual = TbUtils.floatToHex(value);
Assertions.assertEquals(expectedHex, actual);
valueActual = TbUtils.parseHexToFloat(actual);
Assertions.assertEquals(value, valueActual);
valueActual = TbUtils.parseHexToFloat(valueHexRev, false);
Assertions.assertEquals(value, valueActual);
value = 10.0f;
expectedHex = "0x41200000";
valueHexRev = "0x00002041";
actual = TbUtils.floatToHex(value);
Assertions.assertEquals(expectedHex, actual);
valueActual = TbUtils.parseHexToFloat(actual);
Assertions.assertEquals(value, valueActual);
valueActual = TbUtils.parseHexToFloat(valueHexRev, false);
Assertions.assertEquals(value, valueActual);
}
// If the length is not equal to 8 characters, we process it as an integer (eg "0x0A" for 10.0f).
@Test
public void parseHexIntLongToFloat_Test() {
Float valueExpected = 10.0f;
Float valueActual = TbUtils.parseHexIntLongToFloat("0x0A", true);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseHexIntLongToFloat("0x0A", false);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseHexIntLongToFloat("0x00000A", true);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseHexIntLongToFloat("0x0A0000", false);
Assertions.assertEquals(valueExpected, valueActual);
valueExpected = 2570.0f;
valueActual = TbUtils.parseHexIntLongToFloat("0x000A0A", true);
Assertions.assertEquals(valueExpected, valueActual);
valueActual = TbUtils.parseHexIntLongToFloat("0x0A0A00", false);
Assertions.assertEquals(valueExpected, valueActual);
}
@Test

View File

@ -456,6 +456,10 @@ export class UtilsService {
return isDefined(value);
}
public isDefinedAndNotNull(value: any): boolean {
return isDefinedAndNotNull(value);
}
public defaultValue(value: any, defaultValue: any): any {
if (isDefinedAndNotNull(value)) {
return value;

View File

@ -160,7 +160,7 @@
</div>
<tb-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.chart.background"
titleText="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings

View File

@ -203,23 +203,29 @@ export class UnreadNotificationWidgetComponent implements OnInit, OnDestroy {
}
markAsRead(id: string) {
const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]);
cmd.subscribe();
if (!this.ctx.isEdit && !this.ctx.isPreview) {
const cmd = NotificationSubscriber.createMarkAsReadCommand(this.notificationWsService, [id]);
cmd.subscribe();
}
}
markAsAllRead($event: Event) {
if ($event) {
$event.stopPropagation();
if (!this.ctx.isEdit && !this.ctx.isPreview) {
if ($event) {
$event.stopPropagation();
}
const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService);
cmd.subscribe();
}
const cmd = NotificationSubscriber.createMarkAllAsReadCommand(this.notificationWsService);
cmd.subscribe();
}
viewAll($event: Event) {
if ($event) {
$event.stopPropagation();
if (!this.ctx.isEdit && !this.ctx.isPreview) {
if ($event) {
$event.stopPropagation();
}
this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {});
}
this.router.navigateByUrl(this.router.parseUrl('/notification/inbox')).then(() => {});
}
trackById(index: number, item: NotificationRequest): string {

View File

@ -171,6 +171,6 @@ export class TimeSeriesChartWidgetComponent implements OnInit, OnDestroy, AfterV
}
public toggleLegendKey(legendKey: LegendKey) {
this.timeSeriesChart.toggleKey(legendKey.dataKey);
this.timeSeriesChart.toggleKey(legendKey.dataKey, legendKey.dataIndex);
}
}

View File

@ -56,7 +56,14 @@ import {
measureAxisNameSize
} from '@home/components/widget/lib/chart/echarts-widget.models';
import { DateFormatProcessor, ValueSourceType } from '@shared/models/widget-settings.models';
import { formattedDataFormDatasourceData, formatValue, isDefinedAndNotNull, isEqual, mergeDeep } from '@core/utils';
import {
formattedDataFormDatasourceData,
formatValue,
isDefined,
isDefinedAndNotNull,
isEqual,
mergeDeep
} from '@core/utils';
import { DataKey, Datasource, DatasourceType, FormattedData, widgetType } from '@shared/models/widget.models';
import * as echarts from 'echarts/core';
import { CallbackDataParams, PiecewiseVisualMapOption } from 'echarts/types/dist/shared';
@ -300,7 +307,7 @@ export class TbTimeSeriesChart {
}
}
public toggleKey(dataKey: DataKey): void {
public toggleKey(dataKey: DataKey, dataIndex?: number): void {
const enable = dataKey.hidden;
const dataItem = this.dataItems.find(d => d.dataKey === dataKey);
if (dataItem) {
@ -320,6 +327,9 @@ export class TbTimeSeriesChart {
this.timeSeriesChart.setOption(this.timeSeriesChartOptions, this.stackMode ? {notMerge: true} : {replaceMerge: mergeList});
this.updateAxes();
dataKey.hidden = !enable;
if (isDefined(dataIndex)) {
this.ctx.defaultSubscription.updateDataVisibility(dataIndex);
}
if (enable) {
this.timeSeriesChart.dispatchAction({
type: 'highlight',

View File

@ -79,7 +79,7 @@
</div>
<tb-chart-fill-settings
formControlName="barBackgroundSettings"
title="widgets.chart.background"
titleText="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
<tb-time-series-no-aggregation-bar-width-settings

View File

@ -77,7 +77,7 @@
</div>
<tb-chart-fill-settings
formControlName="backgroundSettings"
title="widgets.chart.background"
titleText="widgets.chart.background"
fillNoneTitle="widgets.chart.fill-type-solid">
</tb-chart-fill-settings>
</ng-container>

View File

@ -18,7 +18,7 @@
<ng-container [formGroup]="fillSettingsFormGroup">
<div class="tb-form-row column">
<div class="tb-form-row no-border no-padding space-between">
<div>{{ title | translate }}</div>
<div>{{ titleText | translate }}</div>
<tb-toggle-select formControlName="type">
<tb-toggle-option *ngFor="let type of chartFillTypes" [value]="type">{{ chartFillTypeTranslationMap.get(type) | translate }}</tb-toggle-option>
</tb-toggle-select>

View File

@ -55,7 +55,7 @@ export class ChartFillSettingsComponent implements OnInit, ControlValueAccessor
disabled: boolean;
@Input()
title = 'widgets.chart.fill';
titleText = 'widgets.chart.fill';
@Input()
fillNoneTitle = 'widgets.chart.fill-type-none';

View File

@ -488,6 +488,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
if (!this.widgetContext.inited && this.isReady()) {
this.widgetContext.inited = true;
this.widgetContext.destroyed = false;
this.dashboardWidget.updateWidgetParams();
this.widgetContext.detectContainerChanges();
if (this.cafs.init) {

View File

@ -449,6 +449,8 @@ export class WidgetContext {
labelPattern.destroy();
}
this.labelPatterns.clear();
this.width = undefined;
this.height = undefined;
this.destroyed = true;
}

View File

@ -25,11 +25,17 @@ import {
SimpleChanges,
ViewChild
} from '@angular/core';
import { ControlValueAccessor, UntypedFormBuilder, UntypedFormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
ControlValueAccessor,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
UntypedFormBuilder,
UntypedFormGroup,
ValidationErrors,
Validators
} from '@angular/forms';
import { Observable } from 'rxjs';
import { filter, map, mergeMap, share, tap } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { AppState } from '@app/core/core.state';
import { TranslateService } from '@ngx-translate/core';
import { EntityType } from '@shared/models/entity-type.models';
import { BaseData } from '@shared/models/base-data';
@ -49,6 +55,11 @@ import { SubscriptSizing } from '@angular/material/form-field';
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => EntityListComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => EntityListComponent),
multi: true
}
]
})
@ -56,7 +67,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
entityListFormGroup: UntypedFormGroup;
modelValue: Array<string> | null;
private modelValue: Array<string> | null;
@Input()
entityType: EntityType;
@ -108,17 +119,16 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
private propagateChange = (v: any) => { };
constructor(private store: Store<AppState>,
public translate: TranslateService,
constructor(public translate: TranslateService,
private entityService: EntityService,
private fb: UntypedFormBuilder) {
this.entityListFormGroup = this.fb.group({
entities: [this.entities, this.required ? [Validators.required] : []],
entities: [this.entities],
entity: [null]
});
}
updateValidators() {
private updateValidators() {
this.entityListFormGroup.get('entities').setValidators(this.required ? [Validators.required] : []);
this.entityListFormGroup.get('entities').updateValueAndValidity();
}
@ -189,7 +199,13 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.dirty = true;
}
reset() {
validate(): ValidationErrors | null {
return this.entityListFormGroup.valid ? null : {
entities: {valid: false}
};
}
private reset() {
this.entities = [];
this.entityListFormGroup.get('entities').setValue(this.entities);
this.modelValue = null;
@ -201,7 +217,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.dirty = true;
}
add(entity: BaseData<EntityId>): void {
private add(entity: BaseData<EntityId>): void {
if (!this.modelValue || this.modelValue.indexOf(entity.id.id) === -1) {
if (!this.modelValue) {
this.modelValue = [];
@ -214,7 +230,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
this.clear();
}
remove(entity: BaseData<EntityId>) {
public remove(entity: BaseData<EntityId>) {
let index = this.entities.indexOf(entity);
if (index >= 0) {
this.entities.splice(index, 1);
@ -229,11 +245,11 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
}
}
displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
public displayEntityFn(entity?: BaseData<EntityId>): string | undefined {
return entity ? entity.name : undefined;
}
fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
private fetchEntities(searchText?: string): Observable<Array<BaseData<EntityId>>> {
this.searchText = searchText;
return this.entityService.getEntitiesByNameFilter(this.entityType, searchText,
@ -241,14 +257,14 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
map((data) => data ? data : []));
}
onFocus() {
public onFocus() {
if (this.dirty) {
this.entityListFormGroup.get('entity').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = false;
}
}
clear(value: string = '') {
private clear(value: string = '') {
this.entityInput.nativeElement.value = value;
this.entityListFormGroup.get('entity').patchValue(value, {emitEvent: true});
setTimeout(() => {
@ -257,8 +273,7 @@ export class EntityListComponent implements ControlValueAccessor, OnInit, AfterV
}, 0);
}
textIsNotEmpty(text: string): boolean {
public textIsNotEmpty(text: string): boolean {
return (text && text.length > 0);
}
}